diff --git a/engine/api.py b/engine/api.py index 16f1783..f171f14 100644 --- a/engine/api.py +++ b/engine/api.py @@ -169,6 +169,10 @@ def startEngine(nsmClient, additionalData): callbacks._auditionerVolumeChanged() + #Maybe autoconnect the mixer and auditioner. + if session.standaloneMode and additionalData["autoconnectMixer"]: #this is only for startup. + connectMixerToSystemPorts() + atexit.register(unloadAllInstrumentSamples) #this will handle all python exceptions, but not segfaults of C modules. logger.info("Tembro api startEngine complete") @@ -176,6 +180,8 @@ def startEngine(nsmClient, additionalData): def _instr(idKey:tuple)->Instrument: return session.data.libraries[idKey[0]].instruments[idKey[1]] +def connectMixerToSystemPorts(): + session.data.connectMixerToSystemPorts() def rescanSampleDirectory(newBaseSamplePath): """If the user wants to change the sample dir during runtime we use this function diff --git a/engine/auditioner.py b/engine/auditioner.py index 2ae799f..aea811b 100644 --- a/engine/auditioner.py +++ b/engine/auditioner.py @@ -61,12 +61,8 @@ class Auditioner(object): #Create Stereo Audio Ouput Ports #Connect to our own pair but also to a generic mixer port that is in Data() - if self.parentData.parentSession.standaloneMode: - jackAudioOutLeft = cbox.JackIO.create_audio_output(self.midiInputPortName+"_L", "#1") #add "#1" as second parameter for auto-connection to system out 1 - jackAudioOutRight = cbox.JackIO.create_audio_output(self.midiInputPortName+"_R", "#2") #add "#1" as second parameter for auto-connection to system out 2 - else: - jackAudioOutLeft = cbox.JackIO.create_audio_output(self.midiInputPortName+"_L") - jackAudioOutRight = cbox.JackIO.create_audio_output(self.midiInputPortName+"_R") + jackAudioOutLeft = cbox.JackIO.create_audio_output(self.midiInputPortName+"_L") + jackAudioOutRight = cbox.JackIO.create_audio_output(self.midiInputPortName+"_R") self.outputMergerRouter = cbox.JackIO.create_audio_output_router(jackAudioOutLeft, jackAudioOutRight) self.outputMergerRouter.set_gain(-3.0) diff --git a/engine/main.py b/engine/main.py index 08e29cc..3aa7afe 100644 --- a/engine/main.py +++ b/engine/main.py @@ -184,22 +184,33 @@ class Data(TemplateData): def _createGlobalPorts(self): """Create two mixer ports, for stereo. Each instrument will not only create their own jack out ports but also connect to these left/right. - If we are not in an NSM Session auto-connect them to the system ports for convenience. Also create an additional stereo port pair to pre-listen to on sample instrument alone, the Auditioner. """ - assert not self.parentSession.standaloneMode is None - if self.parentSession.standaloneMode: - self.lmixUuid = cbox.JackIO.create_audio_output('left_mix', "#1") #add "#1" as second parameter for auto-connection to system out 1 - self.rmixUuid = cbox.JackIO.create_audio_output('right_mix', "#2") #add "#2" as second parameter for auto-connection to system out 2 - else: - self.lmixUuid = cbox.JackIO.create_audio_output('left_mix') - self.rmixUuid = cbox.JackIO.create_audio_output('right_mix') + self.lmixUuid = cbox.JackIO.create_audio_output('left_mix') + self.rmixUuid = cbox.JackIO.create_audio_output('right_mix') self.auditioner = Auditioner(self) + def connectMixerToSystemPorts(self): + """Also the auditioner.""" + hardwareAudioPorts = cbox.JackIO.get_ports("system*", cbox.JackIO.AUDIO_TYPE, cbox.JackIO.PORT_IS_SINK | cbox.JackIO.PORT_IS_PHYSICAL) #don't sort. This is correctly sorted. Another sorted will do 1, 10, 11, + + clientName = cbox.JackIO.status().client_name + mix_l = f"{clientName}:left_mix" + mix_r = f"{clientName}:right_mix" + + aud_l = f"{clientName}:{self.auditioner.midiInputPortName}_L" + aud_r = f"{clientName}:{self.auditioner.midiInputPortName}_R" + + cbox.JackIO.port_connect(mix_l, hardwareAudioPorts[0]) + cbox.JackIO.port_connect(mix_r, hardwareAudioPorts[1]) + + cbox.JackIO.port_connect(aud_l, hardwareAudioPorts[0]) + cbox.JackIO.port_connect(aud_r, hardwareAudioPorts[1]) + def exportMetadata(self)->dict: """Data we sent in callbacks. This is the 'build-the-instrument-database' function. Each first level dict contains another dict with instruments, but also a special key diff --git a/qtgui/mainwindow.py b/qtgui/mainwindow.py index 9395c74..53a6da9 100644 --- a/qtgui/mainwindow.py +++ b/qtgui/mainwindow.py @@ -161,6 +161,11 @@ class MainWindow(TemplateMainWindow): if settings.contains("showOnlyLoadedInstruments"): self.ui.actionShowOnlyLoadedInstruments.setChecked(settings.value("showOnlyLoadedInstruments", type=bool)) + autoconnectMixer = False + if api.isStandaloneMode() and settings.contains("autoconnectMixer"): + autoconnectMixer = settings.value("autoconnectMixer", type=bool) + additionalData["autoconnectMixer"] = autoconnectMixer + 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. 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. @@ -195,7 +200,23 @@ class MainWindow(TemplateMainWindow): self.menu.addMenuEntry("menuView", "actionPianoRollVisible", QtCore.QCoreApplication.translate("Menu", "Piano Roll"), self.pianoRollToggleVisibleAndRemember, shortcut="Ctrl+R", checkable=True, startChecked=True) #function receives check state as automatic parameter self.menu.addMenuEntry("menuView", "actionPianoVisible", QtCore.QCoreApplication.translate("Menu", "Piano"), self.pianoToggleVisibleAndRemember, shortcut="Ctrl+P", checkable=True, startChecked=True) #function receives check state as automatic parameter - self.menu.orderSubmenus(["menuFile", "menuEdit", "menuView", "menuHelp"]) + if api.isStandaloneMode(): + settings = QtCore.QSettings("LaborejoSoftwareSuite", METADATA["shortName"]) + autoconnectMixer = settings.value("autoconnectMixer", type=bool) if settings.contains("autoconnectMixer") else False + + self.menu.addSubmenu("menuSettings", QtCore.QCoreApplication.translate("mainWindow", "Settings")) + self.menu.addMenuEntry( + submenu = "menuSettings", + actionAsString = "actionAutoconnectMixer", + text = QtCore.QCoreApplication.translate("Menu", "Autoconnect Mixer and Auditioner ports"), + connectedFunction = self.react_autoconnectMixerCheckbox, + tooltip = QtCore.QCoreApplication.translate("Menu", "Wether to autoconnect the mixer and auditioner ports on program start. Not for NSM."), + checkable=True, + startChecked=autoconnectMixer, + ) + self.menu.orderSubmenus(["menuFile", "menuEdit", "menuView", "menuSettings", "menuHelp"]) + else: + self.menu.orderSubmenus(["menuFile", "menuEdit", "menuView", "menuHelp"]) def react_rescanSampleDir(self): """instructs the GUI to forget all cached data and start fresh. @@ -205,6 +226,13 @@ class MainWindow(TemplateMainWindow): The program start happens without that and just sends data into a prepared but empty GUI.""" self.instrumentTreeController.reset() + def react_autoconnectMixerCheckbox(self, state:bool): + assert api.isStandaloneMode() + settings = QtCore.QSettings("LaborejoSoftwareSuite", METADATA["shortName"]) + settings.setValue("autoconnectMixer", state) + if state: + api.connectMixerToSystemPorts() + def toggleShowOnlyLoadedInstrumentsAndRemember(self, state:bool): """ This function can only be called after the instrument tree has been build.