From 5eb7704a2f61b43ddc4f21392535b34fa9e68f78 Mon Sep 17 00:00:00 2001
From: Nils <>
Date: Thu, 22 Jul 2021 23:38:44 +0200
Subject: [PATCH] Can now choose variant in the gui. Auditioner also respects
current variant
---
engine/api.py | 30 ++++-
engine/instrument.py | 29 ++++-
qtgui/auditioner.py | 75 ++++++++++++
qtgui/designer/mainwindow.py | 20 +++-
qtgui/designer/mainwindow.ui | 32 ++++-
qtgui/instrument.py | 34 +++++-
qtgui/mainwindow.py | 56 ++-------
qtgui/selectedinstrumentcontroller.py | 166 ++++++++++++++++++++++++++
8 files changed, 373 insertions(+), 69 deletions(-)
create mode 100644 qtgui/auditioner.py
create mode 100644 qtgui/selectedinstrumentcontroller.py
diff --git a/engine/api.py b/engine/api.py
index 4e66879..153e1ba 100644
--- a/engine/api.py
+++ b/engine/api.py
@@ -107,12 +107,10 @@ def startEngine(nsmClient):
for instrument in Instrument.allInstruments.values():
callbacks._instrumentStatusChanged(*instrument.idKey)
- #loadSamples() #DEBUG
-
logger.info("Tembro api startEngine complete")
-def loadSamples():
+def loadAllInstrumentSamples():
"""Actually load all instrument samples"""
for instrument in Instrument.allInstruments.values():
callbacks._startLoadingSamples(*instrument.idKey)
@@ -120,13 +118,37 @@ def loadSamples():
callbacks._instrumentStatusChanged(*instrument.idKey)
callbacks._dataChanged
+def unloadInstrumentSamples(idkey:tuple):
+ print ("unloading is not implemented yet")
+ instrument = Instrument.allInstruments[idkey]
+ callbacks._instrumentStatusChanged(*instrument.idKey)
+
+def loadInstrumentSamples(idkey:tuple):
+ """Load one .sfz from a library."""
+ instrument = Instrument.allInstruments[idkey]
+ callbacks._startLoadingSamples(*instrument.idKey)
+ instrument.loadSamples()
+ callbacks._instrumentStatusChanged(*instrument.idKey)
+ callbacks._dataChanged
+
+def chooseVariantByIndex(idkey:tuple, variantIndex:int):
+ """Choose a variant of an already enabled instrument"""
+ libraryId, instrumentId = idkey
+ instrument = Instrument.allInstruments[idkey]
+ instrument.chooseVariantByIndex(variantIndex)
+ callbacks._instrumentStatusChanged(*instrument.idKey)
+ callbacks._dataChanged
def auditionerInstrument(idkey:tuple):
"""Load an indendepent instance of an instrument into the auditioner port"""
libraryId, instrumentId = idkey
originalInstrument = Instrument.allInstruments[idkey]
#It does not matter if the originalInstrument has its samples loaded or not. We just want the path
- session.data.auditioner.loadInstrument(originalInstrument.tarFilePath, originalInstrument.defaultVariant)
+ if originalInstrument.currentVariant:
+ var = originalInstrument.currentVariant
+ else:
+ var = originalInstrument.defaultVariant
+ session.data.auditioner.loadInstrument(originalInstrument.tarFilePath, var)
callbacks._auditionerInstrumentChanged(libraryId, instrumentId)
def getAvailableAuditionerPorts()->dict:
diff --git a/engine/instrument.py b/engine/instrument.py
index e7172e2..583a84e 100644
--- a/engine/instrument.py
+++ b/engine/instrument.py
@@ -100,7 +100,7 @@ class Instrument(object):
self.midiInputPortName = metadata["name"]
self.variants = metadata["variants"].split(",")
self.defaultVariant = metadata["defaultVariant"]
- self.enabled = True #means loaded.
+ self.enabled = False #means loaded.
#Calfbox. The JACK ports are constructed without samples at first.
@@ -149,6 +149,7 @@ class Instrument(object):
#Dynamic data
result["currentVariant"] = self.currentVariant # str
+ result["currentVariantWithoutSfzExtension"] = self.currentVariant.rstrip(".sfz") if self.currentVariant else ""# str
result["state"] = self.enabled #bool
return result
@@ -161,29 +162,51 @@ class Instrument(object):
Please note that we don't add the default variant here. It is only important for the
external world to know what the current variant is. Which is handled by self.exportStatus()
"""
+ parentMetadata = self.parentLibrary.config["library"]
+
result = {}
result["id"] = self.metadata["id"] #int
result["id-key"] = self.idKey # tuple (int, int) redundancy for convenience.
result["name"] = self.metadata["name"] #str
+ result["description"] = self.metadata["description"] #str
result["variants"] = self.variants #list of str
+ result["variantsWithoutSfzExtension"] = [var.rstrip(".sfz") for var in self.variants] #list of str
result["defaultVariant"] = self.metadata["defaultVariant"] #str
+ result["defaultVariantWithoutSfzExtension"] = self.metadata["defaultVariant"].rstrip(".sfz") #str
result["tags"] = self.metadata["tags"].split(",") # list of str
+
#Optional Tags.
- result["license"] = self.metadata["license"] if "license" in self.metadata else "" #str
- result["vendor"] = self.metadata["vendor"] if "vendor" in self.metadata else "" #str
result["group"] = self.metadata["group"] if "group" in self.metadata else "" #str
+
+ #While license replaces the library license, vendor is an addition:
+ if "license" in self.metadata:
+ result["license"] = self.metadata["license"]
+ else:
+ result["license"] = parentMetadata["license"]
+
+ if "vendor" in self.metadata:
+ result["vendor"] = parentMetadata["vendor"] + "\n\n" + self.metadata["vendor"]
+ else:
+ result["vendor"] = parentMetadata["vendor"]
return result
def loadSamples(self):
"""Instrument is constructed without loading the sample data. But the JACK Port and
all Python objects exist. The API can instruct the loading when everything is ready,
so that the callbacks can receive load-progress messages"""
+ self.enable()
if self.startVariantSfzFilename:
self.chooseVariant(self.startVariantSfzFilename)
else:
self.chooseVariant(self.metadata["defaultVariant"])
+ def chooseVariantByIndex(self, index:int):
+ """The variant list is static. Instead of a name we can just choose by index.
+ This is convenient for functions that choose the variant by a list index"""
+ variantSfzFileName = self.variants[index]
+ self.chooseVariant(variantSfzFileName)
+
def chooseVariant(self, variantSfzFileName:str):
"""load_patch_from_tar is blocking. This function will return when the instrument is ready
to play.
diff --git a/qtgui/auditioner.py b/qtgui/auditioner.py
new file mode 100644
index 0000000..e241bf7
--- /dev/null
+++ b/qtgui/auditioner.py
@@ -0,0 +1,75 @@
+#! /usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Copyright 2021, Nils Hilbricht, Germany ( https://www.hilbricht.net )
+
+This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),
+
+This application is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+"""
+import logging; logging.info("import {}".format(__file__))
+
+#Standard Library Modules
+
+#Third Party Modules
+from PyQt5 import QtWidgets, QtCore, QtGui
+
+#Template Modules
+
+#Our modules
+import engine.api as api
+
+
+class AuditionerMidiInputComboController(object):
+
+ def __init__(self, parentMainWindow):
+ self.parentMainWindow = parentMainWindow
+ self.comboBox = parentMainWindow.ui.auditionerMidiInputComboBox
+ self.wholePanel = parentMainWindow.ui.auditionerWidget
+ self.currentInstrumentLabel = parentMainWindow.ui.auditionerCurrentInstrument_label
+ self.currentInstrumentLabel.setText("")
+
+ #if not api.isStandaloneMode():
+ #self.wholePanel.hide()
+ #return
+
+ self.wholePanel.show() #explicit is better than implicit
+ self.originalShowPopup = self.comboBox.showPopup
+ self.comboBox.showPopup = self.showPopup
+ self.comboBox.activated.connect(self._newPortChosen)
+
+ api.callbacks.auditionerInstrumentChanged.append(self.callback_auditionerInstrumentChanged)
+
+ def callback_auditionerInstrumentChanged(self, exportMetadata:dict):
+ key = exportMetadata["id-key"]
+ t = f"➜ [{key[0]}-{key[1]}] {exportMetadata['name']}"
+ self.currentInstrumentLabel.setText(t)
+
+ def _newPortChosen(self, index:int):
+ assert self.comboBox.currentIndex() == index
+ api.connectAuditionerPort(self.comboBox.currentText())
+
+ def showPopup(self):
+ """When the combobox is opened quickly update the port list before showing it"""
+ self._fill()
+ self.originalShowPopup()
+
+ def _fill(self):
+ self.comboBox.clear()
+ availablePorts = api.getAvailableAuditionerPorts()
+ self.comboBox.addItem("") # Not only a more visible seaparator than the Qt one, but also doubles as "disconnect"
+ self.comboBox.addItems(availablePorts["hardware"])
+ #self.comboBox.insertSeparator(len(availablePorts["hardware"])+1)
+ self.comboBox.addItem("") # Not only a more visible seaparator than the Qt one, but also doubles as "disconnect"
+ self.comboBox.addItems(availablePorts["software"])
diff --git a/qtgui/designer/mainwindow.py b/qtgui/designer/mainwindow.py
index 7771d75..1851bb7 100644
--- a/qtgui/designer/mainwindow.py
+++ b/qtgui/designer/mainwindow.py
@@ -77,8 +77,21 @@ class Ui_MainWindow(object):
self.details_scrollArea.setWidgetResizable(True)
self.details_scrollArea.setObjectName("details_scrollArea")
self.scrollAreaWidgetContents = QtWidgets.QWidget()
- self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 545, 158))
+ self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 598, 158))
self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
+ self.formLayout = QtWidgets.QFormLayout(self.scrollAreaWidgetContents)
+ self.formLayout.setObjectName("formLayout")
+ self.variant_label = QtWidgets.QLabel(self.scrollAreaWidgetContents)
+ self.variant_label.setObjectName("variant_label")
+ self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.variant_label)
+ self.variants_comboBox = QtWidgets.QComboBox(self.scrollAreaWidgetContents)
+ self.variants_comboBox.setObjectName("variants_comboBox")
+ self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.variants_comboBox)
+ self.info_label = QtWidgets.QLabel(self.scrollAreaWidgetContents)
+ self.info_label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
+ self.info_label.setWordWrap(True)
+ self.info_label.setObjectName("info_label")
+ self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.info_label)
self.details_scrollArea.setWidget(self.scrollAreaWidgetContents)
self.verticalLayout_3.addWidget(self.details_scrollArea)
self.verticalLayout.addWidget(self.splitter_2)
@@ -90,8 +103,6 @@ class Ui_MainWindow(object):
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
- self.actionRofl = QtWidgets.QAction(MainWindow)
- self.actionRofl.setObjectName("actionRofl")
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
@@ -108,4 +119,5 @@ class Ui_MainWindow(object):
self.instruments_treeWidget.headerItem().setText(3, _translate("MainWindow", "Variant"))
self.instruments_treeWidget.headerItem().setText(4, _translate("MainWindow", "Tags"))
self.details_groupBox.setTitle(_translate("MainWindow", "NamePlaceholder"))
- self.actionRofl.setText(_translate("MainWindow", "Rofl!"))
+ self.variant_label.setText(_translate("MainWindow", "Variants"))
+ self.info_label.setText(_translate("MainWindow", "TextLabel"))
diff --git a/qtgui/designer/mainwindow.ui b/qtgui/designer/mainwindow.ui
index c7e80a8..3476e0b 100644
--- a/qtgui/designer/mainwindow.ui
+++ b/qtgui/designer/mainwindow.ui
@@ -191,10 +191,35 @@
0
0
- 545
+ 598
158
+
+ -
+
+
+ Variants
+
+
+
+ -
+
+
+ -
+
+
+ TextLabel
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
+ true
+
+
+
+
@@ -216,11 +241,6 @@
-
-
- Rofl!
-
-
diff --git a/qtgui/instrument.py b/qtgui/instrument.py
index 9165aaf..7574bbd 100644
--- a/qtgui/instrument.py
+++ b/qtgui/instrument.py
@@ -69,7 +69,7 @@ class InstrumentTreeController(object):
self.treeWidget.header().setSortIndicator(0,0)
self.treeWidget.itemDoubleClicked.connect(self.itemDoubleClicked)
- #self.treeWidget.itemSelectionChanged = self.itemSelectionChanged
+ self.treeWidget.itemSelectionChanged.connect(self.itemSelectionChanged)
self.sortByColumnValue = 1 #by instrId
self.sortDescendingValue = 0 # Qt::SortOrder which is 0 for ascending and 1 for descending
@@ -79,6 +79,18 @@ class InstrumentTreeController(object):
api.callbacks.instrumentStatusChanged.append(self.react_instrumentStatusChanged)
+ def itemSelectionChanged(self):
+ """Only one instrument can be selected at the same time.
+ This function mostly informs other widgets that a different instrument was selected
+ """
+ selItems = self.treeWidget.selectedItems()
+ assert len(selItems) == 1, selItems #because our selection is set to single
+ item = selItems[0]
+ if type(item) is GuiInstrument:
+ self.parentMainWindow.selectedInstrumentController.instrumentChanged(item.idkey)
+ else:
+ self.parentMainWindow.selectedInstrumentController.directLibrary(item.idkey)
+
def itemDoubleClicked(self, item:QtWidgets.QTreeWidgetItem, column:int):
if type(item) is GuiInstrument:
api.auditionerInstrument(item.idkey)
@@ -198,10 +210,12 @@ class GuiLibrary(QtWidgets.QTreeWidgetItem):
def __init__(self, parentTreeController, libraryDict):
super().__init__([], type=1000) #type 0 is default qt type. 1000 is subclassed user type)
+ self.idkey = (libraryDict["id"], 0) #fake it for compatibility
+
#No dynamic data here. Everything gets created once.
self.setText(COLUMNS.index("id-key"), str(libraryDict["id"]))
self.setText(COLUMNS.index("name"), str(libraryDict["name"]))
- self.setText(COLUMNS.index("tags"), str(libraryDict["description"]))
+ self.setText(COLUMNS.index("tags"), str(libraryDict["description"])[:42]+"…")
class GuiInstrument(QtWidgets.QTreeWidgetItem):
@@ -243,16 +257,24 @@ class GuiInstrument(QtWidgets.QTreeWidgetItem):
self.toggleSwitch = ToggleSwitch()
self.toggleSwitch.setAutoFillBackground(True) #otherwise conflicts with setItemWidget
- self.toggleSwitch.toggled.connect(lambda c: print('toggled', c))
- self.toggleSwitch.clicked.connect(lambda c: print('clicked', c))
- self.toggleSwitch.pressed.connect(lambda: print('pressed'))
- self.toggleSwitch.released.connect(lambda: print('released'))
+ #self.toggleSwitch.toggled.connect(lambda c: print('toggled', c)) #triggered by engine callback as well
+ self.toggleSwitch.clicked.connect(self.instrumentSwitchOnViaGui)
+ #self.toggleSwitch.pressed.connect(lambda: print('pressed')) #literal mouse down.
+ #self.toggleSwitch.released.connect(lambda: print('released'))
#We cannot add the ToggleSwitch Widget here.
#It must be inserted after self was added to the Tree. Use self.injectToggleSwitch from parent
+ def instrumentSwitchOnViaGui(self, state):
+ """Only GUI clicks. Does not react to the engine callback that switches on instruments. For
+ example one that arrives through "load group" or "load all" """
+ if state:
+ api.loadInstrumentSamples(self.idkey)
+ else:
+ api.unloadInstrumentSamples(self.idkey)
+
def updateStatus(self, instrumentStatus:dict):
variantColumnIndex = self.columns.index("loaded")
self.currentVariant = instrumentStatus["currentVariant"]
diff --git a/qtgui/mainwindow.py b/qtgui/mainwindow.py
index 7b56563..7e1213e 100644
--- a/qtgui/mainwindow.py
+++ b/qtgui/mainwindow.py
@@ -35,7 +35,8 @@ from template.qtgui.about import About
import engine.api as api
from .instrument import InstrumentTreeController
-
+from .auditioner import AuditionerMidiInputComboController
+from .selectedinstrumentcontroller import SelectedInstrumentController
class MainWindow(TemplateMainWindow):
@@ -55,6 +56,7 @@ class MainWindow(TemplateMainWindow):
#Do not start them all with "You can..." or "...that you can", in response to the Did you know? title.
#We use injection into the class and not a parameter because this dialog gets shown by creating an object. We can't give the parameters when this is shown via the mainWindow menu.
About.didYouKnow = [
+ QtCore.QCoreApplication.translate("About", "Double click an instrument to load it into the special Auditioner slot."),
QtCore.QCoreApplication.translate("About", "There is no way to load your own instruments into this program. If you create your own instruments and would like them to be included please contact the developers for a collaboration.")
] + About.didYouKnow
@@ -62,12 +64,17 @@ class MainWindow(TemplateMainWindow):
#make the search bar smaller
self.ui.search_groupBox.setMinimumSize(30, 1)
- self.ui.splitter_2.setSizes([1,1])
+ self.ui.splitter_2.setSizes([1,1]) #just a forced update
+
+ #Make the description field at least a bit visible
+ self.ui.details_groupBox.setMinimumSize(1, 50)
+ self.ui.splitter.setSizes([1,1]) #just a forced update
self.setupMenu()
self.auditionerMidiInputComboController = AuditionerMidiInputComboController(parentMainWindow=self)
self.instrumentTreeController = InstrumentTreeController(parentMainWindow=self)
+ self.selectedInstrumentController = SelectedInstrumentController(parentMainWindow=self)
self.start() #This shows the GUI, or not, depends on the NSM gui save setting. We need to call that after the menu, otherwise the about dialog will block and then we get new menu entries, which looks strange.
@@ -77,53 +84,10 @@ class MainWindow(TemplateMainWindow):
#New menu entries and template-menu overrides
#self.menu.connectMenuEntry("actionAbout", lambda: print("About Dialog Menu deactivated")) #deactivates the original function
self.menu.addMenuEntry("menuEdit", "actionNils", "Nils", lambda: print("Merle"))
- self.menu.addMenuEntry("menuEdit", "actionLoadSamples", "LoadSamples", api.loadSamples)
+ self.menu.addMenuEntry("menuEdit", "actionLoadSamples", "Load all Instrument Samples", api.loadAllInstrumentSamples)
#self.menu.connectMenuEntry("actionNils", lambda: print("Override"))
def zoom(self, scaleFactor:float):
pass
def stretchXCoordinates(self, factor):
pass
-
-class AuditionerMidiInputComboController(object):
-
- def __init__(self, parentMainWindow):
- self.parentMainWindow = parentMainWindow
- self.comboBox = parentMainWindow.ui.auditionerMidiInputComboBox
- self.wholePanel = parentMainWindow.ui.auditionerWidget
- self.currentInstrumentLabel = parentMainWindow.ui.auditionerCurrentInstrument_label
- self.currentInstrumentLabel.setText("")
-
- #if not api.isStandaloneMode():
- #self.wholePanel.hide()
- #return
-
- self.wholePanel.show() #explicit is better than implicit
- self.originalShowPopup = self.comboBox.showPopup
- self.comboBox.showPopup = self.showPopup
- self.comboBox.activated.connect(self._newPortChosen)
-
- api.callbacks.auditionerInstrumentChanged.append(self.callback_auditionerInstrumentChanged)
-
- def callback_auditionerInstrumentChanged(self, exportMetadata:dict):
- key = exportMetadata["id-key"]
- t = f"➜ [{key[0]}-{key[1]}] {exportMetadata['name']}"
- self.currentInstrumentLabel.setText(t)
-
-
- def _newPortChosen(self, index:int):
- assert self.comboBox.currentIndex() == index
- api.connectAuditionerPort(self.comboBox.currentText())
-
- def showPopup(self):
- """When the combobox is opened quickly update the port list before showing it"""
- self._fill()
- self.originalShowPopup()
-
- def _fill(self):
- self.comboBox.clear()
- availablePorts = api.getAvailableAuditionerPorts()
- self.comboBox.addItems(availablePorts["hardware"])
- #self.comboBox.insertSeparator(len(availablePorts["hardware"])+1)
- self.comboBox.addItem("") # Not only a more visible seaparator than the Qt one, but also doubles as "disconnect"
- self.comboBox.addItems(availablePorts["software"])
diff --git a/qtgui/selectedinstrumentcontroller.py b/qtgui/selectedinstrumentcontroller.py
new file mode 100644
index 0000000..c3badf6
--- /dev/null
+++ b/qtgui/selectedinstrumentcontroller.py
@@ -0,0 +1,166 @@
+#! /usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Copyright 2021, Nils Hilbricht, Germany ( https://www.hilbricht.net )
+
+This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),
+
+This application is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+"""
+
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
+#Standard Library
+
+#Third Party
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+#Our Qt
+
+#Engine
+import engine.api as api
+
+
+
+class SelectedInstrumentController(object):
+ """Not a qt class. We externally control a collection of widgets.
+ There is only one set of widgets. We change their contents dynamically.
+ """
+
+ def __init__(self, parentMainWindow):
+ self.parentMainWindow = parentMainWindow
+ self.currentIdKey = None
+ self.engineData = {} # id-key tuple : engine library metadata dict.
+ self.statusUpdates = {} # same as engineData, but with incremental status updates. One is guranteed to exist at startup
+
+ #Our Widgets
+ self.ui = parentMainWindow.ui
+ self.ui.details_groupBox.setTitle("")
+ self.ui.details_scrollArea.hide() #until the first instrument was selected
+
+ self.ui.variants_comboBox.activated.connect(self._newVariantChosen)
+
+
+ #Callbacks
+ api.callbacks.instrumentListMetadata.append(self.react_initialInstrumentList)
+ api.callbacks.instrumentStatusChanged.append(self.react_instrumentStatusChanged)
+
+
+
+ def directLibrary(self, idkey:tuple):
+ """User clicked on a library treeItem"""
+ libraryId, instrumentId = idkey
+ self.currentIdKey = None
+ self.ui.details_scrollArea.show()
+ self.ui.variants_comboBox.hide()
+ self.ui.variant_label.hide()
+
+ metadata = self.engineData[libraryId]["library"]
+
+ self.ui.details_groupBox.setTitle(metadata["name"])
+
+
+ self.ui.info_label.setText(self._metadataToDescriptionLabel(metadata))
+
+ def _metadataToDescriptionLabel(self, metadata:dict)->str:
+ """Can work with instruments and libraries alike"""
+
+ if "variants" in metadata: #this is an instrument
+ fullText = metadata["description"] + "\n\nVendor: " + metadata["vendor"] + "\n\nLicense: " + metadata["license"]
+ else:
+ fullText = metadata["description"] + "\n\nVendor: " + metadata["vendor"]
+ return fullText
+
+ def _newVariantChosen(self, index:int):
+ """User chose a new variant through the combo box"""
+ assert self.ui.variants_comboBox.currentIndex() == index
+ assert self.currentIdKey
+ api.chooseVariantByIndex(self.currentIdKey, index)
+
+ def instrumentChanged(self, idkey:tuple):
+ """This is a GUI-internal function. The user selected a different instrument from
+ the list. Single click, arrow keys etc.
+
+ We combine static metadata, which we saved ourselves, with the current instrument status
+ (e.g. which variant was chosen).
+ """
+ libraryId, instrumentId = idkey
+ self.currentIdKey = idkey
+
+ self.ui.details_scrollArea.show()
+
+ #Static
+ instrumentData = self.engineData[libraryId][instrumentId]
+ self.ui.details_groupBox.setTitle(instrumentData["name"])
+
+ self.ui.variant_label.show()
+ self.ui.variants_comboBox.show()
+ self.ui.variants_comboBox.clear()
+ self.ui.variants_comboBox.addItems(instrumentData["variantsWithoutSfzExtension"])
+
+ self.ui.info_label.setText(self._metadataToDescriptionLabel(instrumentData))
+
+ #Dynamic
+ self.react_instrumentStatusChanged(self.statusUpdates[libraryId][instrumentId])
+
+
+ def react_instrumentStatusChanged(self, instrumentStatus:dict):
+ """Callback from the api. Has nothing to do with any GUI state or selection.
+ Data:
+ #Static ids
+ result["id"] = self.metadata["id"]
+ result["id-key"] = self.idKey #redundancy for convenience.
+
+ #Dynamic data
+ result["currentVariant"] = self.currentVariant # str
+ result["state"] = self.enabled #bool
+ """
+ idkey = instrumentStatus["id-key"]
+ libraryId, instrumentId = idkey
+
+ if not libraryId in self.statusUpdates:
+ self.statusUpdates[libraryId] = {} #empty library. status dict
+ self.statusUpdates[libraryId][instrumentId] = instrumentStatus #create or overwrite / keep up to date
+
+ loadState = instrumentStatus["state"]
+
+ instrumentData = self.engineData[libraryId][instrumentId]
+ instrumentStatus = self.statusUpdates[libraryId][instrumentId]
+ if loadState: #None if not loaded
+ self.ui.variants_comboBox.setEnabled(True)
+ currentVariantIndex = instrumentData["variantsWithoutSfzExtension"].index(instrumentStatus["currentVariantWithoutSfzExtension"])
+ self.ui.variants_comboBox.setCurrentIndex(currentVariantIndex)
+ else:
+ self.ui.variants_comboBox.setEnabled(False)
+ defaultVariantIndex = instrumentData["variantsWithoutSfzExtension"].index(instrumentData["defaultVariantWithoutSfzExtension"])
+ self.ui.variants_comboBox.setCurrentIndex(defaultVariantIndex)
+
+
+
+
+ def react_initialInstrumentList(self, data:dict):
+ """For data form see docstring of instrument.py buildTree()
+ Summary:
+ libraryid : dict ->
+ instrumentid : metadatadict
+
+ Dict-Keys are always the same. Some always have data, some can be empty.
+
+ We receive this once at program start and build our permanent GUI widgets from it.
+ The additional status update callback for dynamic data is handled in
+ self.react_instrumentStatusChanged
+ """
+ self.engineData = data
+
+