Browse Source

Finalize work on sample dir change for now

master
Nils 8 months ago
parent
commit
ec5a02f0f6
  1. 29
      engine/instrument.py
  2. 38
      engine/main.py
  3. 2
      qtgui/auditioner.py
  4. 44
      qtgui/instrument.py
  5. 6
      qtgui/selectedinstrumentcontroller.py

29
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

38
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"

2
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)

44
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)

6
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:

Loading…
Cancel
Save