self.scene.status().layers[0].get_instrument().engine.load_patch_from_string(0,"","","")#fill with null instruments, hopefully replacing the loaded sfz data.
self.currentVariant=None
self.currentKeySwitch=None
defgetAvailablePorts(self)->dict:
"""This function queries JACK each time it is called.
self.enabled=False#At startup no samples and no jack-ports. But we already have all metadata ready so a GUI can build a database and offer Auditioner choices.
@ -143,6 +140,30 @@ class Instrument(object):
returnresult
defcopyStateFrom(self,otherInstrument):
"""Use another instrument instance to copy the soft values.
assertvariantSfzFileNameinstatus.name,(status.name,variantSfzFileName)#this is NOT the same. Salamander Drumkit status.name == 'drumkit/ALL.sfz', variantSfzFileName == 'ALL.sfz'
session=self.parentSession#self.parentSession is already defined in template.data. We just want to work conveniently in init with it by creating a local var.
"""Called first by api.startEngine, which receives the global sample path from
theGUI.
#basePath = pathlib.Path("/home/nils/samples/Tembro/out/") #TODO: replace with system-finder /home/xy or /usr/local/share or /usr/share etc.
basePath=pathlib.Path(baseSamplePath)#self.baseSamplePath is injected by api.startEngine.
logger.info(f"Start loading samples from {baseSamplePath}")
Latercalledbysampledirrescan.
"""
#Since this is a function called by the user, or at least the GUI,
#we do some error checking.
ifnotbaseSamplePath:
raiseValueError(f"Wrong format for argument baseSamplePath. Should be a path-string or path-like object but was: {baseSamplePath}")
basePath=pathlib.Path(baseSamplePath)
ifnotbasePath.exists():
raiseOSError(f"{basePath} does not exists to load samples from.")
ifnotbasePath.is_dir():
raiseOSError(f"{basePath} is not a directory..")
firstRun=notself.libraries
iffirstRun:#in case of re-scan we don't need to do this a second time. The default lib cannot be updated through the download manager and will always be present.
logger.error("There were no sample libraries to parse! This is correct on the first run, since you still need to choose a sample directory.")
#TODO: Is this still valid with the guaranteed 000 - Default.tar?
logger.error("There were no sample libraries to parse! This is correct on an empty run, since you still need to choose a sample directory.")
self.instrumentMidiNoteOnActivity=None# the api will inject a callback function here which takes (libId, instrId) as parameter to indicate midi noteOn activity for non-critical information like a GUI LED blinking or checking for new keyswitch states. The instruments individiual midiprocessor will call this as a parent-call.
self._createGlobalPorts()#in its own function for readability
self.instrumentMidiNoteOnActivity=instrumentMidiNoteOnActivity# the api will inject a callback function here which takes (libId, instrId) as parameter to indicate midi noteOn activity for non-critical information like a GUI LED blinking or checking for new keyswitch states. The instruments individiual midiprocessor will call this as a parent-call.
iffirstRun:#in case of re-scan we don't need to do this a second time. The default lib cannot be updated through the download manager and will always be present.
self._createGlobalPorts()#in its own function for readability
delself.libraries[libIdToDel]#garbage collect all children instruments as well
def_createGlobalPorts(self):
"""Create two mixer ports, for stereo. Each instrument will not only create their own jack
outportsbutalsoconnecttotheseleft/right.
@ -133,12 +197,12 @@ class Data(TemplateData):
#order = {portName:index for index, portName in enumerate(track.sequencerInterface.cboxPortName() for track in self.tracks if track.sequencerInterface.cboxMidiOutUuid)}
assertnewInstrument.defaultVariant==self.instruments[newInstrId].defaultVariant,(newInstrument.defaultVariant,self.instruments[newInstrId].defaultVariant)#this is by Tembro-Design. Never change existing instruments.
logger.info(f"Found a new variant {newVariant} while parsing updated version of library {newLib.tarFilePath} and instrument {newInstrument.metadata['name']}")
raiseValueError(f"""Attempted to 'update' library {self.tarFilePath} version {self.config["library"]["version"]} with older version {newLib.config["library"]["version"]}. This is not allowed in Tembro during runtime. Aborting program.""")
else:
#otherwise this is literally the same file with the same id and same version. -> no action required.
pass
deftransferOldState(self,oldLib):#oldLib is type Library / self
"""We are a newly parsed Library that will replace an existing one.
libDict["version"]=self.config["library"]["version"]#this is not the upstream sfz version of the sample creator but our own library index. flat integers as counters. higher is newer.
"""This chooses the auditioner. Callbacks are handled in the auditioner widget itself"""
iftype(item)isGuiInstrument:
api.auditionerInstrument(item.idkey)
api.auditionerInstrument(item.idKey)
@ -262,18 +273,18 @@ class InstrumentTreeController(object):
#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.mixSendDial.setUpdatesEnabled(False)#this is a hack to make the widget disappear. Because hiding a QTreeWidgetItem does not work.
@ -446,10 +458,10 @@ class GuiInstrument(QtWidgets.QTreeWidgetItem):
def_mixSendDialContextMenuEvent(self,event):
ifself._cachedInstrumentStatus["mixerEnabled"]:
mixerMuteText=QtCore.QCoreApplication.translate("InstrumentMixerLevelContextMenu","Mute/Disable Mixer-Send for {}".format(self.instrumentDict["name"]))
mixerMuteText=QtCore.QCoreApplication.translate("InstrumentMixerLevelContextMenu","Unmute/Enable Mixer-Send for {}".format(self.instrumentDict["name"]))
@ -99,6 +99,8 @@ class MainWindow(TemplateMainWindow):
else:
additionalData["baseSamplePath"]="/tmp"#TODO: At least give a message.
api.callbacks.rescanSampleDir.append(self.react_rescanSampleDir)#This only happens on actual, manually instructed rescanning through the api. We instruct this through our Rescan-Dialog.
self.start(additionalData)#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.
#Statusbar will show possible actions, such as "use scrollwheel to transpose"
@ -128,6 +130,17 @@ class MainWindow(TemplateMainWindow):