From ec5a02f0f64f4caf2b44f5f5c9811c6a98e658f1 Mon Sep 17 00:00:00 2001 From: Nils <> Date: Thu, 27 Jan 2022 18:56:06 +0100 Subject: [PATCH] Finalize work on sample dir change for now --- engine/instrument.py | 29 ++---------------- engine/main.py | 38 ++++++++++++++++++----- qtgui/auditioner.py | 2 +- qtgui/instrument.py | 44 +++++++++++++-------------- qtgui/selectedinstrumentcontroller.py | 6 ++-- 5 files changed, 59 insertions(+), 60 deletions(-) diff --git a/engine/instrument.py b/engine/instrument.py index 09b5235..1df2128 100644 --- a/engine/instrument.py +++ b/engine/instrument.py @@ -127,7 +127,7 @@ class Instrument(object): #Static ids result["id"] = int(self.metadata["id"]) - result["id-key"] = self.idKey #redundancy for convenience. + result["idKey"] = self.idKey #redundancy for convenience. #Dynamic data result["currentVariant"] = self.currentVariant # str @@ -139,31 +139,6 @@ class Instrument(object): result["currentKeySwitch"] = self.currentKeySwitch return result - - def copyStateFrom(self, otherInstrument): - """Use another instrument instance to copy the soft values. - Not really sampler internal CC but everything we control on our own""" - assert otherInstrument.idKey == self.idKey, (otherInstrument.idKey, self.idKey) - - if otherInstrument.enabled: - print (self.idKey, otherInstrument.idKey) - self.enable() - self.chooseVariant(otherInstrument.currentVariant) - if self.currentKeySwitch: - self.setKeySwitch(self.currentKeySwitch) - self.mixerLevel = otherInstrument.mixerLevel - else: - self.disable() - - #jack conections - #Ist die alte Verbindung noch hier? Dann können wir nicht jack connections machen. Das teil kriegt sogar den falschen namen! - #Ich könnte die funktion hier doch wieder in lib packen und dann die jack connections parsen, - #die alte lib deaktivieren und dann die neuen instrumente erst aktivieren. schwierig. Das braucht einen Test ob - #die jack connections einen falschen namen kriegen (pretty names ausmachen) - - - - def exportMetadata(self)->dict: """This gets called before the samples are loaded. Only static data, that does not get changed during runtime, is included here. @@ -175,7 +150,7 @@ class Instrument(object): result = {} result["id"] = int(self.metadata["id"]) #int - result["id-key"] = self.idKey # tuple (int, int) redundancy for convenience. + result["idKey"] = 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 diff --git a/engine/main.py b/engine/main.py index 1f3831c..18b8992 100644 --- a/engine/main.py +++ b/engine/main.py @@ -94,7 +94,7 @@ class Data(TemplateData): s = pathlib.Path(PATHS["share"]) defaultLibraryPath = s.joinpath("000 - Default.tar") logger.info(f"Loading Default Instrument Library from {defaultLibraryPath}. This message must only appear once in the log.") - defaultLib = Library(parentData=self, tarFilePath=defaultLibraryPath) + defaultLib = Library(parentData=self, tarFilePath=defaultLibraryPath) #If this fails we let the program crash. The default samples must exist and be accessible. self.libraries[defaultLib.id] = defaultLib assert defaultLib.id == 0, defaultLib.id @@ -107,7 +107,12 @@ class Data(TemplateData): for f in basePath.glob('*.tar'): if f.is_file() and f.suffix == ".tar": #First load the library (this is .ini parsing, not sample loading, so it is cheap) and create a library object - lib = Library(parentData=self, tarFilePath=f) + #It will not create jack ports + try: + lib = Library(parentData=self, tarFilePath=f) + except PermissionError as e: + logger.error(f"Library {f} could not be loaded. The reason follows: {e}") + continue #Then compare if this is actually a file we already knew: #we loaded this before and it still exists. We will NOT delete it below. @@ -121,15 +126,14 @@ class Data(TemplateData): else: #we already know this id oldLib = self.libraries[lib.id] - print (lib.tarFilePath, oldLib.tarFilePath, lib.tarFilePath.samefile(oldLib.tarFilePath)) if lib.tarFilePath == oldLib.tarFilePath or lib.tarFilePath.samefile(oldLib.tarFilePath): #Same id, same file path, or (sym)link. We update the old instrument and maybe there are new variants to load. #Loaded state will remain the same. Tembro instruments don't change with updates. self.libraries[lib.id].updateWithNewParse(lib) else: #Same id, different file path. We treat it as a different lib and unload/reload completely. - self._unloadLibrary(lib.id) #remove old lib instance lib.transferOldState(oldLib) #at least reactivate the already loaded instruments. + self._unloadLibrary(lib.id) #remove old lib instance self.libraries[lib.id] = lib #this is the new lib instance. @@ -303,6 +307,7 @@ class Library(object): needTarData = True if needTarData: with tarfile.open(name=tarFilePath, mode='r:') as opentarfile: + #PermissionErrors are caught by the constructing line in main/Data above iniFileObject = TextIOWrapper(opentarfile.extractfile("library.ini")) self.config = configparser.ConfigParser() self.config.read_file(iniFileObject) @@ -345,7 +350,6 @@ class Library(object): discarded. """ assert self.tarFilePath == newLib.tarFilePath or newLib.tarFilePath.samefile(self.tarFilePath), (self.tarFilePath, newLib.tarFilePath) - print ("update new parse", (self.tarFilePath, newLib.tarFilePath)) if newLib.config["library"]["version"] > self.config["library"]["version"]: self.config = newLib.config for newInstrId, newInstrument in newLib.instruments.items(): @@ -385,9 +389,29 @@ class Library(object): data from the old one, before discarding it. """ - print ("transferOldState", self.tarFilePath, oldLib.tarFilePath) for instrId, oldInstrument in oldLib.instruments.items(): - self.instruments[instrId].copyStateFrom(oldInstrument) + #Use another instrument instance to copy the soft values. + #Not really sampler internal CC but at least everything we control on our own. + ourNewInstrument = self.instruments[instrId] + + assert oldInstrument.idKey == ourNewInstrument.idKey, (oldInstrument.idKey, ourNewInstrument.idKey) + + if oldInstrument.enabled: + #We need to deactivate the old lib before activating the new one because we have the + #same ids and names, which results in the same jack ports. + tmpCurVar = oldInstrument.currentVariant + tmpKeySw = oldInstrument.currentKeySwitch + tmpMix = oldInstrument.mixerLevel + + oldInstrument.disable() #frees jack port names + ourNewInstrument.enable() + ourNewInstrument.chooseVariant(tmpCurVar) + if tmpKeySw: + ourNewInstrument.setKeySwitch(tmpKeySw) + ourNewInstrument.mixerLevel = tmpMix + elif ourNewInstrument.enabled and not oldInstrument.enabled: + ourNewInstrument.disable() + def exportMetadata(self)->dict: """Return a dictionary with each key is an instrument id, but also a special key "library" diff --git a/qtgui/auditioner.py b/qtgui/auditioner.py index 3062bcf..f17f359 100644 --- a/qtgui/auditioner.py +++ b/qtgui/auditioner.py @@ -70,7 +70,7 @@ class AuditionerMidiInputComboController(object): self.currentInstrumentLabel.setText(self.defaultText) else: self.parentMainWindow.qtApp.restoreOverrideCursor() #We assume the cursor was set to a loading animation - key = exportMetadata["id-key"] + key = exportMetadata["idKey"] t = f"➜ [{key[0]}-{key[1]}] {exportMetadata['name']}" self.currentInstrumentLabel.setText(t) diff --git a/qtgui/instrument.py b/qtgui/instrument.py index b95899a..6b8eafa 100644 --- a/qtgui/instrument.py +++ b/qtgui/instrument.py @@ -33,7 +33,7 @@ from template.qtgui.helper import ToggleSwitch,FancySwitch import engine.api as api -COLUMNS = ("state", "id-key", "mixSend", "name", "loaded", "group", "tags" ) #Loaded = Variant +COLUMNS = ("state", "idKey", "mixSend", "name", "loaded", "group", "tags" ) #Loaded = Variant class InstrumentTreeController(object): @@ -56,8 +56,8 @@ class InstrumentTreeController(object): #Includes: #self._cachedData = None #self._cachedLastInstrumentStatus = {} # instrument idKey : status Dict - #self.guiLibraries = {} # id-key : GuiLibrary - #self.guiInstruments = {} # id-key : GuiInstrument + #self.guiLibraries = {} # idKey : GuiLibrary + #self.guiInstruments = {} # idKey : GuiInstrument self.currentlyNested = None #is the view nested in libraries or just all instruments? @@ -102,8 +102,8 @@ class InstrumentTreeController(object): self._cachedData = None self._cachedLastInstrumentStatus = {} # instrument idKey : status Dict #The next two will delete all children through the garbage collector. - self.guiLibraries = {} # id-key : GuiLibrary - self.guiInstruments = {} # id-key : GuiInstrument + self.guiLibraries = {} # idKey : GuiLibrary + self.guiInstruments = {} # idKey : GuiInstrument def itemExpandedOrCollapsed(self, libraryItem:QtWidgets.QTreeWidgetItem): @@ -149,7 +149,7 @@ class InstrumentTreeController(object): {'0': {'0': {'group': 'strings', 'id': '0', - 'id-key': ('0', '0'), + 'idKey': ('0', '0'), 'license': 'https://unlicense.org/', 'name': 'Sine Wave', 'tags': ['sine', 'basic'], @@ -157,7 +157,7 @@ class InstrumentTreeController(object): 'vendor': 'Test entry to provide more vendor information'}, '1': {'group': 'strings', 'id': '1', - 'id-key': ('0', '1'), + 'idKey': ('0', '1'), 'license': '', 'name': 'Square Wave', 'tags': ['square', 'basic'], @@ -165,7 +165,7 @@ class InstrumentTreeController(object): 'vendor': ''}, '2': {'group': 'brass', 'id': '2', - 'id-key': ('0', '2'), + 'idKey': ('0', '2'), 'license': '', 'name': 'Saw Wave', 'tags': ['saw', 'basic'], @@ -173,7 +173,7 @@ class InstrumentTreeController(object): 'vendor': ''}, '3': {'group': '', 'id': '3', - 'id-key': ('0', '3'), + 'idKey': ('0', '3'), 'license': '', 'name': 'Triangle Wave', 'tags': ['triangle', 'complex'], @@ -196,8 +196,8 @@ class InstrumentTreeController(object): """ #Reset everything except our cached data. self.treeWidget.clear() #will delete the C++ objects. We need to delete the PyQt objects ourselves, like so: - self.guiLibraries = {} # id-key : GuiLibrary - self.guiInstruments = {} # id-key : GuiInstrument + self.guiLibraries = {} # idKey : GuiLibrary + self.guiInstruments = {} # idKey : GuiInstrument self.currentlyNested = nested if data: @@ -235,9 +235,9 @@ class InstrumentTreeController(object): else: self.treeWidget.addTopLevelItem(gi) gi.injectWidgets() #only possible after gi.init() was done and item inserted. - self.guiInstruments[instrumentDict["id-key"]] = gi - if instrumentDict["id-key"] in self._cachedLastInstrumentStatus: - gi.updateStatus(self._cachedLastInstrumentStatus[instrumentDict["id-key"]]) + self.guiInstruments[instrumentDict["idKey"]] = gi + if instrumentDict["idKey"] in self._cachedLastInstrumentStatus: + gi.updateStatus(self._cachedLastInstrumentStatus[instrumentDict["idKey"]]) self._adjustColumnSize() @@ -266,12 +266,12 @@ class InstrumentTreeController(object): def react_instrumentStatusChanged(self, instrumentStatus:dict): self.parentMainWindow.qtApp.restoreOverrideCursor() #Sometimes the instrument was loaded with a cursor animation - gi = self.guiInstruments[instrumentStatus["id-key"]] + gi = self.guiInstruments[instrumentStatus["idKey"]] gi.updateStatus(instrumentStatus) self._adjustColumnSize() #We also cache the last status, as we cache the initial data. This way we can delete and recreate TreeItems without requesting new status data from the engine - self._cachedLastInstrumentStatus[instrumentStatus["id-key"]] = instrumentStatus + self._cachedLastInstrumentStatus[instrumentStatus["idKey"]] = instrumentStatus def react_startLoadingSamples(self, idKey:tuple): """Will be overriden by instrument status change / variant chosen""" @@ -300,8 +300,8 @@ class GuiLibrary(QtWidgets.QTreeWidgetItem): self.name = libraryDict["name"] #No dynamic data here. Everything gets created once. - #self.setText(COLUMNS.index("id-key"), str(libraryDict["id"]).zfill(leadingZeroesForZfill)) - self.setData(COLUMNS.index("id-key"), 0, int(libraryDict["id"])) #set data allows sorting by actual numbers. 0 is the data role, which is just "display text". + #self.setText(COLUMNS.index("idKey"), str(libraryDict["id"]).zfill(leadingZeroesForZfill)) + self.setData(COLUMNS.index("idKey"), 0, int(libraryDict["id"])) #set data allows sorting by actual numbers. 0 is the data role, which is just "display text". self.setText(COLUMNS.index("name"), str(libraryDict["name"])) self.setText(COLUMNS.index("tags"), str(libraryDict["description"])[:42]+"…") @@ -337,9 +337,9 @@ class GuiInstrument(QtWidgets.QTreeWidgetItem): def __init__(self, parentTreeController, instrumentDict): - GuiInstrument.allItems[instrumentDict["id-key"]] = self + GuiInstrument.allItems[instrumentDict["idKey"]] = self self.parentTreeController = parentTreeController - self.idKey = instrumentDict["id-key"] + self.idKey = instrumentDict["idKey"] #Start with empty columns. We fill in later in _writeColumns super().__init__([], type=1000) #type 0 is default qt type. 1000 is subclassed user type) @@ -350,7 +350,7 @@ class GuiInstrument(QtWidgets.QTreeWidgetItem): #nameColumnIndex = self.columns.index("prettyName") #self.setText(nameColumnIndex, "hello") - self.setTextAlignment(self.columns.index("id-key"), QtCore.Qt.AlignHCenter) + self.setTextAlignment(self.columns.index("idKey"), QtCore.Qt.AlignHCenter) self.state = None #by self.update... self.instrumentDict = None @@ -512,7 +512,7 @@ class GuiInstrument(QtWidgets.QTreeWidgetItem): t = ", ".join(instrumentDict[key]) self.setText(index, t) - elif key == "id-key": #tuple + elif key == "idKey": #tuple libId, instrId = instrumentDict[key] zeros = int(instrumentDict["instrumentsInLibraryCount"]/10)+1 instIdZFilled = str(instrId).zfill(zeros) diff --git a/qtgui/selectedinstrumentcontroller.py b/qtgui/selectedinstrumentcontroller.py index 624a3cb..cc7b2c5 100644 --- a/qtgui/selectedinstrumentcontroller.py +++ b/qtgui/selectedinstrumentcontroller.py @@ -41,7 +41,7 @@ class SelectedInstrumentController(object): def __init__(self, parentMainWindow): self.parentMainWindow = parentMainWindow self.currentIdKey = None - self.engineData = {} # id-key tuple : engine library metadata dict. + self.engineData = {} # idKey tuple : engine library metadata dict. self.statusUpdates = {} # same as engineData, but with incremental status updates. One is guranteed to exist at startup #Our Widgets @@ -166,7 +166,7 @@ class SelectedInstrumentController(object): Data: #Static ids result["id"] = self.metadata["id"] - result["id-key"] = self.idKey #redundancy for convenience. + result["idKey"] = self.idKey #redundancy for convenience. #Dynamic data result["currentVariant"] = self.currentVariant # str @@ -180,7 +180,7 @@ class SelectedInstrumentController(object): This callback is called again by the GUI directly when switching the instrument with a mouseclick in instrumentChanged and the GUI will use the cached data. """ - idkey = instrumentStatus["id-key"] + idkey = instrumentStatus["idKey"] libraryId, instrumentId = idkey if not libraryId in self.statusUpdates: