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.
self.mixerEnabled=None#not only is the mixer disabled, but it is unavailable.
self.currentVariant:str=""#This is the currently loaded variant. Only set after actual loading samples. That means it is "" even from a savefile and only set later.
#We could call self.loadSamples() now, but we delay that for the user experience. See docstring.
self.currentVariantKeySwitches=None#set by _parseKeySwitches through chooseVariant . Ttuple: dict, sw_lokey, sw_highkey . See docstring of parseKeySwitches
self.currentKeySwitch:int=None# Midi pitch. Default is set on load.
result["keySwitches"]=self.currentVariantKeySwitches[0]ifself.currentVariantKeySwitcheselse{}#Internally this is a tuple with [0] being a dict: Unordered!! dict with midiPitch: (opcode, label). You need the opcode to see if it is a momentary switch or permanent.
#We do NOT use the program number, that was a mistake in the past. Set programNumber to 0 so it overwrites the current file.
self.program=self.instrumentLayer.engine.load_patch_from_tar(0,self.tarFilePath,self.rootPrefixPath+variantSfzFileName,self.metadata["name"])#program number fixed to 0, tar_name, sfz_name, display_name
#self.program = self.instrumentLayer.engine.get_patches()[0][1] workaround in the past, when load_patch_from_tar returned None by mistake.
#Turns out we do not even need set_patch.
#self.instrumentLayer.engine.set_patch(1, 0) #1 is supposed to be the channel, 0 is the program. But it listens to all 16 channels anyway.
assertvariantSfzFileNameinstatus.name,(status.name,variantSfzFileName)#this is NOT the same. Salamander Drumkit status.name == 'drumkit/ALL.sfz', variantSfzFileName == 'ALL.sfz'
logger.warning(f"Something tried to set the keyswitch but this instrument {self.name}{self.currentVariant} is currently not enabled. Nothing was changed.")
return
ifnotself.currentVariant:
logger.warning(f"Something tried to parse keyswitches but this instrument {self.name}{self.currentVariant} currently has no variant loaded. Nothing was changed.")
logger.error(f"Instrument {self.name}{self.currentVariant} has multiple different sw_default values. We will use the last one encountered. This conflict: {writeInOthers['sw_default']} vs {data['sw_default']}")
writeInOthers["sw_default"]=data["sw_default"]
if"sw_last"indata:
midiPitch=midiName2midiPitch[data["sw_last"]]
writeInResult[midiPitch]="sw_last",label
elif"sw_down"indata:
midiPitch=midiName2midiPitch[data["sw_down"]]
writeInResult[midiPitch]="sw_down",label
elif"sw_up"indata:
midiPitch=midiName2midiPitch[data["sw_up"]]
writeInResult[midiPitch]="sw_up",label
logger.info(f"Start parsing possible keyswitches in the current variant/cbox-program for {self.name}{self.currentVariant}")
logger.info(f"Finished parsing possible keyswitches in the current variant/cbox-program for {self.name}{self.currentVariant}. Found: {len(result)} keyswitches.")
logger.warning(f"Tried to set the keyswitch but this instrument {self.name} is currently not enabled. Nothing was changed.")
return
ifnotself.currentVariant:
logger.warning(f"Tried to parse keyswitches but this instrument {self.name} currently has no variant loaded. Nothing was changed.")
return
cboxReportedKeySwitchesRange=self.program.get_keyswitch_groups()[0]# This is ALWAYS two values. It is a range!
keySwitchDict=self.currentVariantKeySwitches[0]
ifnotkeySwitchMidiPitchinkeySwitchDict:
logger.warning(f"Tried setting instrument {self.name} to key switch {keySwitchMidiPitch} but that switch was not parsed on load. Nothing was changed. We parsed: {keySwitchDict} in the range from/to {cboxReportedKeySwitchesRange}")
return
self.scene.send_midi_event(0x90,keySwitchMidiPitch,64)#note on with vel 64
currentKeySwitch=self.instrumentLayer.engine.get_keyswitch_state(1,0)#midi channel indexFrom1, keyswitch group
#Confirm that we changed the switch for both development and release version
logger.error(f"Tried setting instrument {self.name} to key switch {keySwitchMidiPitch} but afterwards we are switch {currentKeySwitch}. Cause unknown. Not sure if this is a problem or not.")
returnself.updateCurrentKeySwitch()
defupdateCurrentKeySwitch(self,force=None):
"""This is either called directly by setKeySwitch after a user change
raiseRuntimeError(f"{self.name} tried to switch to enabled, but it already was. This is not a trivial error. Please make sure no user-function can reach this state.")
self.program=None#return object from self.instrumentLayer.engine.load_patch_from_tar
#self.scene.status().layers[0].set_ignore_program_changes(1) #TODO: ignore different channels. We only want one channel per scene/instrument/port. #TODO: Add generic filters to filter out redundant tasks like mixing and panning, which should be done in an audio mixer.
#self.instrumentLayer.engine.set_polyphony(int)
#Create Stereo Audio Ouput Ports
#Connect to our own pair but also to a generic mixer port that is in Data()
instrument.get_output_slot(0).rec_wet.attach(self.outputMergerRouter)#output_slot is 0 based and means a pair. Most sfz instrument have only one stereo pair. #TODO: And what if not?
cbox.JackIO.set_appsink_for_midi_input(self.cboxMidiPortUid,True)#This sounds like a program wide sink, but it is needed for every port.
cbox.JackIO.route_midi_input(self.cboxMidiPortUid,self.scene.uuid)#Route midi input to the scene. Without this we have no sound, but the python processor would still work.
self.midiProcessor=MidiProcessor(parentInput=self)#works through self.cboxMidiPortUid
raiseRuntimeError(f"{self.name} tried to switch to disabled, but it already was. This is not a trivial error. Please make sure no user-function can reach this state.")
self.scene.status().layers[0].get_instrument().engine.load_patch_from_string(0,"","","")#fill with null instruments, hopefully replacing the loaded sfz data.
instrument=self.sfzSamplerLayer.get_instrument()
instrument.get_output_slot(0).rec_wet.detach(self.outputMergerRouter)#output_slot is 0 based and means a pair. Most sfz instrument have only one stereo pair. #TODO: And what if not?
"currentVariant":self.currentVariant,#string. Since currentVariant is set to "" when disabling an instrument this is also our marker for the instrument loaded state.
#Do NOT save "self.enabled". This is just an internal convenience switch. currentVariant is the data that tells us if there was an actively loaded instrument, inluding loaded samples.