def __init__(self, client_name='jack_mixer'): self.visible = False self.nsm_client = None if os.environ.get('NSM_URL'): self.nsm_client = NSMClient( prettyName="jack_mixer", saveCallback=self.nsm_save_cb, openOrNewCallback=self.nsm_open_cb, supportsSaveStatus=False, hideGUICallback=self.nsm_hide_cb, showGUICallback=self.nsm_show_cb, exitProgramCallback=self.nsm_exit_cb, loggingLevel="error", ) self.nsm_client.announceGuiVisibility(self.visible) else: self.visible = True self.create_mixer(client_name, with_nsm=False)
def __init__(self, qtApp): super().__init__() self.qtApp = qtApp self.layout = QtWidgets.QVBoxLayout() self.layout.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft) self.setLayout(self.layout) niceTitle = "PyNSM v2 Example - JACK Noise" self.title = QtWidgets.QLabel("") self.saved = QtWidgets.QLabel("") self._value = QtWidgets.QSlider(orientation = 1) #horizontal self._value.setMinimum(0) self._value.setMaximum(100) self._value.setValue(50) #only visible for the first start. self.valueLabel = QtWidgets.QLabel("Noise Volume: " + str(self._value.value())) self._value.valueChanged.connect(lambda new: self.valueLabel.setText("Noise Volume: " + str(new))) self.layout.addWidget(self.title) self.layout.addWidget(self.saved) self.layout.addWidget(self._value) self.layout.addWidget(self.valueLabel) #Prepare the NSM Client #This has to be done as soon as possible because NSM provides paths and names for us. #and may quit if NSM environment var was not found. self.nsmClient = NSMClient(prettyName = niceTitle, #will raise an error and exit if this example is not run from NSM. supportsSaveStatus = True, saveCallback = self.saveCallback, openOrNewCallback = self.openOrNewCallback, exitProgramCallback = self.exitCallback, loggingLevel = "info", #"info" for development or debugging, "error" for production. default is error. ) #If NSM did not start up properly the program quits here with an error message from NSM. #No JACK client gets created, no Qt window can be seen. self.title.setText("<b>" + self.nsmClient.ourClientNameUnderNSM + "</b>") self.eventLoop = QtCore.QTimer() self.eventLoop.start(100) #10ms-20ms is smooth for "real time" feeling. 100ms is still ok. self.eventLoop.timeout.connect(self.nsmClient.reactToMessage)
def __init__(self, name, delayedFunctions=[], eventFunction=None): """delayedFunctions are a (timer delay in seconds, function call) list of tuples. They will be executed once. If the function is a string instead it will be evaluated in the BaseClient context, providing self. Do not give a lambda! Give eventFunction for repeated execution.""" self.nsmClient = NSMClient( prettyName= name, #will raise an error and exit if this example is not run from NSM. saveCallback=self.saveCallbackFunction, openOrNewCallback=self.openOrNewCallbackFunction, supportsSaveStatus= False, # Change this to True if your program announces it's save status to NSM exitProgramCallback=self.exitCallbackFunction, broadcastCallback=self.broadcastCallbackFunction, hideGUICallback= None, #replace with your hiding function. You need to answer in your function with nsmClient.announceGuiVisibility(False) showGUICallback= None, #replace with your showing function. You need to answer in your function with nsmClient.announceGuiVisibility(True) loggingLevel= "info", #"info" for development or debugging, "error" for production. default is error. ) if eventFunction: self.event = eventFunction for delay, func in delayedFunctions: if type(func) is str: func = eval('lambda self=self: ' + func) t = Timer(interval=delay, function=func, args=()) t.start() while True: self.nsmClient.reactToMessage() self.event(self.nsmClient) sleep(0.05)
class Main(QtWidgets.QWidget): def __init__(self, qtApp): super().__init__() self.qtApp = qtApp self.layout = QtWidgets.QVBoxLayout() self.layout.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft) self.setLayout(self.layout) niceTitle = "PyNSM v2 Example - JACK Noise" self.title = QtWidgets.QLabel("") self.saved = QtWidgets.QLabel("") self._value = QtWidgets.QSlider(orientation = 1) #horizontal self._value.setMinimum(0) self._value.setMaximum(100) self._value.setValue(50) #only visible for the first start. self.valueLabel = QtWidgets.QLabel("Noise Volume: " + str(self._value.value())) self._value.valueChanged.connect(lambda new: self.valueLabel.setText("Noise Volume: " + str(new))) self.layout.addWidget(self.title) self.layout.addWidget(self.saved) self.layout.addWidget(self._value) self.layout.addWidget(self.valueLabel) #Prepare the NSM Client #This has to be done as soon as possible because NSM provides paths and names for us. #and may quit if NSM environment var was not found. self.nsmClient = NSMClient(prettyName = niceTitle, #will raise an error and exit if this example is not run from NSM. supportsSaveStatus = True, saveCallback = self.saveCallback, openOrNewCallback = self.openOrNewCallback, exitProgramCallback = self.exitCallback, loggingLevel = "info", #"info" for development or debugging, "error" for production. default is error. ) #If NSM did not start up properly the program quits here with an error message from NSM. #No JACK client gets created, no Qt window can be seen. self.title.setText("<b>" + self.nsmClient.ourClientNameUnderNSM + "</b>") self.eventLoop = QtCore.QTimer() self.eventLoop.start(100) #10ms-20ms is smooth for "real time" feeling. 100ms is still ok. self.eventLoop.timeout.connect(self.nsmClient.reactToMessage) #self.show is called as the new/open callback. @property def value(self): return str(self._value.value()) @value.setter def value(self, new): new = int(new) self._value.setValue(new) def saveCallback(self, ourPath, sessionName, ourClientNameUnderNSM): #parameters are filled in by NSM. if self.value: with open(ourPath, "w") as f: #ourpath is taken as a filename here. We have this path name at our disposal and we know we only want one file. So we don't make a directory. This way we don't have to create a dir first. f.write(self.value) def openOrNewCallback(self, ourPath, sessionName, ourClientNameUnderNSM): #parameters are filled in by NSM. try: with open(ourPath, "r") as f: savedValue = f.read() #a string self.saved.setText("{}: {}".format(ourPath, savedValue)) self.value = savedValue #internal casting to int. Sets the slider. except FileNotFoundError: self.saved.setText("{}: No save file found. Normal for first start.".format(ourPath)) finally: self.show() def exitCallback(self, ourPath, sessionName, ourClientNameUnderNSM): """This function is a callback for NSM. We have a chance to close our clients and open connections here. If not nsmclient will just kill us no matter what """ cjack.jack_remove_properties(ctypesJackClient, ctypesJackUuid) #clean our metadata cjack.jack_client_close(ctypesJackClient) #omitting this introduces problems. in Jack1 this would mute all jack clients for several seconds. exit() #or get SIGKILLed through NSM def closeEvent(self, event): """Qt likes to quits on its own. For example when the window manager closes the main window. Ignore that request and instead send a roundtrip through NSM""" self.nsmClient.serverSendExitToSelf() event.ignore()
This is only an example after all. Nobody should use pure Xlib for a real program. Quitting a program is easily done with a real GUI Toolkit like Qt. """ cjack.jack_client_close( ctypesJackClient ) #omitting this introduces problems. in Jack1 this would mute all jack clients for several seconds. #Exit is done by NSM kill. nsmClient = NSMClient( prettyName=niceTitle, supportsSaveStatus=True, saveCallback=saveCallback, openOrNewCallback=openCallback, exitProgramCallback=exitProgram, loggingLevel= "info", #"info" for development or debugging, "error" for production. default is error. ) #If NSM did not start up properly the program quits here. No JACK client gets created, no X window can be seen. ######################################################################## #Prepare the JACK Client #We can't do that without the values we got from nsmClient: path names. ######################################################################## cjack = ctypes.cdll.LoadLibrary("libjack.so.0") clientName = nsmClient.prettyName options = 0 status = None
We could gracefully close the X11-Client here. However, this is another level of complexity: ending threads, etc. We just let it crash at the end. No harm done. This is only an example after all. Nobody should use pure Xlib for a real program. Quitting a program is easily done with a real GUI Toolkit like Qt. """ cjack.jack_client_close(ctypesJackClient) #omitting this introduces problems. in Jack1 this would mute all jack clients for several seconds. #Exit is done by NSM kill. nsmClient = NSMClient(prettyName = niceTitle, supportsSaveStatus = True, saveCallback = saveCallback, openOrNewCallback = openCallback, exitProgramCallback = exitProgram, loggingLevel = "info", #"info" for development or debugging, "error" for production. default is error. ) #If NSM did not start up properly the program quits here. No JACK client gets created, no X window can be seen. ######################################################################## #Prepare the JACK Client #We can't do that without the values we got from nsmClient: path names. ######################################################################## cjack = ctypes.cdll.LoadLibrary("libjack.so.0") clientName = nsmClient.prettyName options = 0 status = None cjack.jack_client_open.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.POINTER(ctypes.c_int)] #the two ints are enum and pointer to enum. #http://jackaudio.org/files/docs/html/group__ClientFunctions.html#gab8b16ee616207532d0585d04a0bd1d60
def hideGUICallback(): # Put your code that hides your GUI in here print("hideGUICallback") nsmClient.announceGuiVisibility( isVisible=False ) # Inform NSM that the GUI is now hidden. Put this at the end. nsmClient = NSMClient( prettyName=niceTitle, saveCallback=saveCallback, openOrNewCallback=openCallback, showGUICallback= showGUICallback, # Comment this line out if your program does not have an optional GUI hideGUICallback= hideGUICallback, # Comment this line out if your program does not have an optional GUI supportsSaveStatus= False, # Change this to True if your program announces it's save status to NSM exitProgramCallback=exitProgram, loggingLevel= "info", # "info" for development or debugging, "error" for production. default is error. ) # If NSM did not start up properly the program quits here. ######################################################################## # If your project uses JACK, activate your client here # You can use jackClientName or create your own ######################################################################## jackClientName = nsmClient.ourClientNameUnderNSM
# nsmClient.announceSaveStatus(False) # Announce your save status (dirty = False / clean = True) # nsmClient.announceGuiVisibility(isVisible=True) # Inform NSM that the GUI is now visible. Put this at the end. def hideGUICallback(): # Put your code that hides your GUI in here print("Hiding GUI not implemented yet, you have to save and close the text editor yourself.") nsmClient.announceGuiVisibility(isVisible=False) # Inform NSM that the GUI is now hidden. Put this at the end. nsmClient = NSMClient(prettyName = niceTitle, saveCallback = saveCallback, openOrNewCallback = openCallback, showGUICallback = showGUICallback, # Comment this line out if your program does not have an optional GUI hideGUICallback = hideGUICallback, # Comment this line out if your program does not have an optional GUI supportsSaveStatus = False, # Change this to True if your program announces it's save status to NSM exitProgramCallback = exitProgram, loggingLevel = "info", # "info" for development or debugging, "error" for production. default is error. ) # If NSM did not start up properly the program quits here. ######################################################################## # Start main program loop. ######################################################################## # showGUICallback() # If you want your GUI to be shown by default, uncomment this line print("Let's go!") # TODO: Get this to be able to send OSC messages to NSM.
class JackMixer(SerializedObject): # scales suitable as meter scales meter_scales = [ scale.K20(), scale.K14(), scale.IEC268(), scale.Linear70dB(), scale.IEC268Minimalistic() ] # scales suitable as volume slider scales slider_scales = [scale.Linear30dB(), scale.Linear70dB()] # name of settings file that is currently open current_filename = None _init_solo_channels = None def __init__(self, client_name='jack_mixer'): self.visible = False self.nsm_client = None if os.environ.get('NSM_URL'): self.nsm_client = NSMClient( prettyName="jack_mixer", saveCallback=self.nsm_save_cb, openOrNewCallback=self.nsm_open_cb, supportsSaveStatus=False, hideGUICallback=self.nsm_hide_cb, showGUICallback=self.nsm_show_cb, exitProgramCallback=self.nsm_exit_cb, loggingLevel="error", ) self.nsm_client.announceGuiVisibility(self.visible) else: self.visible = True self.create_mixer(client_name, with_nsm=False) def create_mixer(self, client_name, with_nsm=True): self.mixer = jack_mixer_c.Mixer(client_name) if not self.mixer: raise RuntimeError("Failed to create Mixer instance.") self.create_ui(with_nsm) self.window.set_title(client_name) self.monitor_channel = self.mixer.add_output_channel( "Monitor", True, True) self.save = False GLib.timeout_add(33, self.read_meters) GLib.timeout_add(50, self.midi_events_check) if with_nsm: GLib.timeout_add(200, self.nsm_react) def new_menu_item(self, title, callback=None, accel=None, enabled=True): menuitem = Gtk.MenuItem.new_with_mnemonic(title) menuitem.set_sensitive(enabled) if callback: menuitem.connect("activate", callback) if accel: key, mod = Gtk.accelerator_parse(accel) menuitem.add_accelerator("activate", self.menu_accelgroup, key, mod, Gtk.AccelFlags.VISIBLE) return menuitem def create_ui(self, with_nsm): self.channels = [] self.output_channels = [] self.window = Gtk.Window(type=Gtk.WindowType.TOPLEVEL) self.window.set_icon_name('jack_mixer') self.gui_factory = gui.Factory(self.window, self.meter_scales, self.slider_scales) self.gui_factory.connect('midi-behavior-mode-changed', self.on_midi_behavior_mode_changed) self.gui_factory.emit_midi_behavior_mode() self.vbox_top = Gtk.VBox() self.window.add(self.vbox_top) self.menu_accelgroup = Gtk.AccelGroup() self.window.add_accel_group(self.menu_accelgroup) self.menubar = Gtk.MenuBar() self.vbox_top.pack_start(self.menubar, False, True, 0) mixer_menu_item = Gtk.MenuItem.new_with_mnemonic("_Mixer") self.menubar.append(mixer_menu_item) edit_menu_item = Gtk.MenuItem.new_with_mnemonic('_Edit') self.menubar.append(edit_menu_item) help_menu_item = Gtk.MenuItem.new_with_mnemonic('_Help') self.menubar.append(help_menu_item) self.width = 420 self.height = 420 self.paned_position = 210 self.window.set_default_size(self.width, self.height) self.mixer_menu = Gtk.Menu() mixer_menu_item.set_submenu(self.mixer_menu) self.mixer_menu.append( self.new_menu_item('New _Input Channel', self.on_add_input_channel, "<Control>N")) self.mixer_menu.append( self.new_menu_item('New Output _Channel', self.on_add_output_channel, "<Shift><Control>N")) self.mixer_menu.append(Gtk.SeparatorMenuItem()) if not with_nsm: self.mixer_menu.append( self.new_menu_item('_Open...', self.on_open_cb, "<Control>O")) self.mixer_menu.append( self.new_menu_item('_Save', self.on_save_cb, "<Control>S")) if not with_nsm: self.mixer_menu.append( self.new_menu_item('Save _As...', self.on_save_as_cb, "<Shift><Control>S")) self.mixer_menu.append(Gtk.SeparatorMenuItem()) if with_nsm: self.mixer_menu.append( self.new_menu_item('_Hide', self.nsm_hide_cb, "<Control>W")) else: self.mixer_menu.append( self.new_menu_item('_Quit', self.on_quit_cb, "<Control>Q")) edit_menu = Gtk.Menu() edit_menu_item.set_submenu(edit_menu) self.channel_edit_input_menu_item = self.new_menu_item( '_Edit Input Channel', enabled=False) edit_menu.append(self.channel_edit_input_menu_item) self.channel_edit_input_menu = Gtk.Menu() self.channel_edit_input_menu_item.set_submenu( self.channel_edit_input_menu) self.channel_edit_output_menu_item = self.new_menu_item( 'E_dit Output Channel', enabled=False) edit_menu.append(self.channel_edit_output_menu_item) self.channel_edit_output_menu = Gtk.Menu() self.channel_edit_output_menu_item.set_submenu( self.channel_edit_output_menu) self.channel_remove_input_menu_item = self.new_menu_item( '_Remove Input Channel', enabled=False) edit_menu.append(self.channel_remove_input_menu_item) self.channel_remove_input_menu = Gtk.Menu() self.channel_remove_input_menu_item.set_submenu( self.channel_remove_input_menu) self.channel_remove_output_menu_item = self.new_menu_item( 'Re_move Output Channel', enabled=False) edit_menu.append(self.channel_remove_output_menu_item) self.channel_remove_output_menu = Gtk.Menu() self.channel_remove_output_menu_item.set_submenu( self.channel_remove_output_menu) edit_menu.append(Gtk.SeparatorMenuItem()) edit_menu.append( self.new_menu_item('Shrink Input Channels', self.on_narrow_input_channels_cb, "<Control>minus")) edit_menu.append( self.new_menu_item('Expand Input Channels', self.on_widen_input_channels_cb, "<Control>plus")) edit_menu.append(Gtk.SeparatorMenuItem()) edit_menu.append( self.new_menu_item('_Clear', self.on_channels_clear, "<Control>X")) edit_menu.append(Gtk.SeparatorMenuItem()) edit_menu.append( self.new_menu_item('_Preferences', self.on_preferences_cb, "<Control>P")) help_menu = Gtk.Menu() help_menu_item.set_submenu(help_menu) help_menu.append(self.new_menu_item('_About', self.on_about, "F1")) self.hbox_top = Gtk.HBox() self.vbox_top.pack_start(self.hbox_top, True, True, 0) self.scrolled_window = Gtk.ScrolledWindow() self.scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.hbox_inputs = Gtk.Box() self.hbox_inputs.set_spacing(0) self.hbox_inputs.set_border_width(0) self.hbox_top.set_spacing(0) self.hbox_top.set_border_width(0) self.scrolled_window.add(self.hbox_inputs) self.hbox_outputs = Gtk.Box() self.hbox_outputs.set_spacing(0) self.hbox_outputs.set_border_width(0) self.scrolled_output = Gtk.ScrolledWindow() self.scrolled_output.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.scrolled_output.add(self.hbox_outputs) self.paned = Gtk.HPaned() self.paned.set_wide_handle(True) self.hbox_top.pack_start(self.paned, True, True, 0) self.paned.pack1(self.scrolled_window, True, False) self.paned.pack2(self.scrolled_output, True, False) self.window.connect("destroy", Gtk.main_quit) self.window.connect('delete-event', self.on_delete_event) def nsm_react(self): self.nsm_client.reactToMessage() return True def nsm_hide_cb(self, *args): self.window.hide() self.visible = False self.nsm_client.announceGuiVisibility(False) def nsm_show_cb(self): width, height = self.window.get_size() self.window.show_all() self.paned.set_position(self.paned_position / self.width * width) self.visible = True self.nsm_client.announceGuiVisibility(True) def nsm_open_cb(self, path, session_name, client_name): self.create_mixer(client_name, with_nsm=True) self.current_filename = path + '.xml' if os.path.isfile(self.current_filename): f = open(self.current_filename, 'r') self.load_from_xml(f, from_nsm=True) f.close() else: f = open(self.current_filename, 'w') f.close() def nsm_save_cb(self, path, session_name, client_name): self.current_filename = path + '.xml' f = open(self.current_filename, 'w') self.save_to_xml(f) f.close() def nsm_exit_cb(self, path, session_name, client_name): Gtk.main_quit() def on_midi_behavior_mode_changed(self, gui_factory, value): self.mixer.midi_behavior_mode = value def on_delete_event(self, widget, event): if self.nsm_client: self.nsm_hide_cb() return True return self.on_quit_cb() def sighandler(self, signum, frame): log.debug("Signal %d received.", signum) if signum == signal.SIGUSR1: self.save = True elif signum == signal.SIGTERM: self.on_quit_cb() elif signum == signal.SIGINT: self.on_quit_cb() else: log.warning("Unknown signal %d received.", signum) def cleanup(self): log.debug("Cleaning jack_mixer.") if not self.mixer: return for channel in self.channels: channel.unrealize() self.mixer.destroy() def on_open_cb(self, *args): dlg = Gtk.FileChooserDialog(title='Open', parent=self.window, action=Gtk.FileChooserAction.OPEN) dlg.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK) dlg.set_default_response(Gtk.ResponseType.OK) if dlg.run() == Gtk.ResponseType.OK: filename = dlg.get_filename() try: f = open(filename, 'r') self.load_from_xml(f) except Exception as e: error_dialog(self.window, "Failed loading settings (%s)", e) else: self.current_filename = filename finally: f.close() dlg.destroy() def on_save_cb(self, *args): if not self.current_filename: return self.on_save_as_cb() f = open(self.current_filename, 'w') self.save_to_xml(f) f.close() def on_save_as_cb(self, *args): dlg = Gtk.FileChooserDialog(title='Save', parent=self.window, action=Gtk.FileChooserAction.SAVE) dlg.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK) dlg.set_default_response(Gtk.ResponseType.OK) if dlg.run() == Gtk.ResponseType.OK: self.current_filename = dlg.get_filename() self.on_save_cb() dlg.destroy() def on_quit_cb(self, *args): if not self.nsm_client and self.gui_factory.get_confirm_quit(): dlg = Gtk.MessageDialog(parent=self.window, message_type=Gtk.MessageType.QUESTION, buttons=Gtk.ButtonsType.NONE) dlg.set_markup("<b>Quit application?</b>") dlg.format_secondary_markup( "All jack_mixer ports will be closed and connections lost," "\nstopping all sound going through jack_mixer.\n\n" "Are you sure?") dlg.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_QUIT, Gtk.ResponseType.OK) response = dlg.run() dlg.destroy() if response != Gtk.ResponseType.OK: return True Gtk.main_quit() def on_narrow_input_channels_cb(self, widget): for channel in self.channels: channel.narrow() def on_widen_input_channels_cb(self, widget): for channel in self.channels: channel.widen() preferences_dialog = None def on_preferences_cb(self, widget): if not self.preferences_dialog: self.preferences_dialog = PreferencesDialog(self) self.preferences_dialog.show() self.preferences_dialog.present() def on_add_channel(self, inout="input", default_name="Input"): dialog = getattr(self, '_add_{}_dialog'.format(inout), None) values = getattr(self, '_add_{}_values'.format(inout), {}) if dialog == None: cls = NewInputChannelDialog if inout == 'input' else NewOutputChannelDialog dialog = cls(app=self) setattr(self, '_add_{}_dialog'.format(inout), dialog) names = { ch.channel_name for ch in ( self.channels if inout == 'input' else self.output_channels) } values.setdefault('name', default_name) while True: if values['name'] in names: values['name'] = add_number_suffix(values['name']) else: break dialog.fill_ui(**values) dialog.set_transient_for(self.window) dialog.show() ret = dialog.run() dialog.hide() if ret == Gtk.ResponseType.OK: result = dialog.get_result() setattr(self, '_add_{}_values'.format(inout), result) method = getattr( self, 'add_channel' if inout == 'input' else 'add_output_channel') channel = method(**result) if self.visible or self.nsm_client == None: self.window.show_all() def on_add_input_channel(self, widget): return self.on_add_channel("input", "Input") def on_add_output_channel(self, widget): return self.on_add_channel("output", "Output") def on_edit_input_channel(self, widget, channel): log.debug('Editing input channel "%s".', channel.channel_name) channel.on_channel_properties() def remove_channel_edit_input_menuitem_by_label(self, widget, label): if (widget.get_label() == label): self.channel_edit_input_menu.remove(widget) def on_remove_input_channel(self, widget, channel): log.debug('Removing input channel "%s".', channel.channel_name) self.channel_remove_input_menu.remove(widget) self.channel_edit_input_menu.foreach( self.remove_channel_edit_input_menuitem_by_label, channel.channel_name) if self.monitored_channel is channel: channel.monitor_button.set_active(False) for i in range(len(self.channels)): if self.channels[i] is channel: channel.unrealize() del self.channels[i] self.hbox_inputs.remove(channel.get_parent()) break if not self.channels: self.channel_edit_input_menu_item.set_sensitive(False) self.channel_remove_input_menu_item.set_sensitive(False) def on_edit_output_channel(self, widget, channel): log.debug('Editing output channel "%s".', channel.channel_name) channel.on_channel_properties() def remove_channel_edit_output_menuitem_by_label(self, widget, label): if (widget.get_label() == label): self.channel_edit_output_menu.remove(widget) def on_remove_output_channel(self, widget, channel): log.debug('Removing output channel "%s".', channel.channel_name) self.channel_remove_output_menu.remove(widget) self.channel_edit_output_menu.foreach( self.remove_channel_edit_output_menuitem_by_label, channel.channel_name) if self.monitored_channel is channel: channel.monitor_button.set_active(False) for i in range(len(self.channels)): if self.output_channels[i] is channel: channel.unrealize() del self.output_channels[i] self.hbox_outputs.remove(channel.get_parent()) break if not self.output_channels: self.channel_edit_output_menu_item.set_sensitive(False) self.channel_remove_output_menu_item.set_sensitive(False) def rename_channels(self, container, parameters): if (container.get_label() == parameters['oldname']): container.set_label(parameters['newname']) def on_channel_rename(self, oldname, newname): rename_parameters = {'oldname': oldname, 'newname': newname} self.channel_edit_input_menu.foreach(self.rename_channels, rename_parameters) self.channel_edit_output_menu.foreach(self.rename_channels, rename_parameters) self.channel_remove_input_menu.foreach(self.rename_channels, rename_parameters) self.channel_remove_output_menu.foreach(self.rename_channels, rename_parameters) log.debug('Renaming channel from "%s" to "%s".', oldname, newname) def on_channels_clear(self, widget): dlg = Gtk.MessageDialog( parent=self.window, modal=True, message_type=Gtk.MessageType.WARNING, text="Are you sure you want to clear all channels?", buttons=Gtk.ButtonsType.OK_CANCEL) if not widget or dlg.run() == Gtk.ResponseType.OK: for channel in self.output_channels: channel.unrealize() self.hbox_outputs.remove(channel.get_parent()) for channel in self.channels: channel.unrealize() self.hbox_inputs.remove(channel.get_parent()) self.channels = [] self.output_channels = [] self.channel_edit_input_menu = Gtk.Menu() self.channel_edit_input_menu_item.set_submenu( self.channel_edit_input_menu) self.channel_edit_input_menu_item.set_sensitive(False) self.channel_remove_input_menu = Gtk.Menu() self.channel_remove_input_menu_item.set_submenu( self.channel_remove_input_menu) self.channel_remove_input_menu_item.set_sensitive(False) self.channel_edit_output_menu = Gtk.Menu() self.channel_edit_output_menu_item.set_submenu( self.channel_edit_output_menu) self.channel_edit_output_menu_item.set_sensitive(False) self.channel_remove_output_menu = Gtk.Menu() self.channel_remove_output_menu_item.set_submenu( self.channel_remove_output_menu) self.channel_remove_output_menu_item.set_sensitive(False) dlg.destroy() def add_channel(self, name, stereo, volume_cc, balance_cc, mute_cc, solo_cc, value): try: channel = InputChannel(self, name, stereo, value) self.add_channel_precreated(channel) except Exception: error_dialog(self.window, "Channel creation failed.") return if volume_cc != -1: channel.channel.volume_midi_cc = volume_cc else: channel.channel.autoset_volume_midi_cc() if balance_cc != -1: channel.channel.balance_midi_cc = balance_cc else: channel.channel.autoset_balance_midi_cc() if mute_cc != -1: channel.channel.mute_midi_cc = mute_cc else: channel.channel.autoset_mute_midi_cc() if solo_cc != -1: channel.channel.solo_midi_cc = solo_cc else: channel.channel.autoset_solo_midi_cc() return channel def add_channel_precreated(self, channel): frame = Gtk.Frame() frame.add(channel) self.hbox_inputs.pack_start(frame, False, True, 0) channel.realize() channel_edit_menu_item = Gtk.MenuItem(label=channel.channel_name) self.channel_edit_input_menu.append(channel_edit_menu_item) channel_edit_menu_item.connect("activate", self.on_edit_input_channel, channel) self.channel_edit_input_menu_item.set_sensitive(True) channel_remove_menu_item = Gtk.MenuItem(label=channel.channel_name) self.channel_remove_input_menu.append(channel_remove_menu_item) channel_remove_menu_item.connect("activate", self.on_remove_input_channel, channel) self.channel_remove_input_menu_item.set_sensitive(True) self.channels.append(channel) for outputchannel in self.output_channels: channel.add_control_group(outputchannel) # create post fader output channel matching the input channel channel.post_fader_output_channel = self.mixer.add_output_channel( channel.channel.name + ' Out', channel.channel.is_stereo, True) channel.post_fader_output_channel.volume = 0 channel.post_fader_output_channel.set_solo(channel.channel, True) channel.connect('input-channel-order-changed', self.on_input_channel_order_changed) def on_input_channel_order_changed(self, widget, source_name, dest_name): self.channels.clear() channel_box = self.hbox_inputs frames = channel_box.get_children() for f in frames: c = f.get_child() if source_name == c._channel_name: source_frame = f break for f in frames: c = f.get_child() if (dest_name == c._channel_name): pos = frames.index(f) channel_box.reorder_child(source_frame, pos) break for frame in self.hbox_inputs.get_children(): c = frame.get_child() self.channels.append(c) def read_meters(self): for channel in self.channels: channel.read_meter() for channel in self.output_channels: channel.read_meter() return True def midi_events_check(self): for channel in self.channels + self.output_channels: channel.midi_events_check() return True def add_output_channel(self, name, stereo, volume_cc, balance_cc, mute_cc, display_solo_buttons, color, value): try: channel = OutputChannel(self, name, stereo, value) channel.display_solo_buttons = display_solo_buttons channel.color = color self.add_output_channel_precreated(channel) except Exception: error_dialog(self.window, "Channel creation failed") return if volume_cc != -1: channel.channel.volume_midi_cc = volume_cc else: channel.channel.autoset_volume_midi_cc() if balance_cc != -1: channel.channel.balance_midi_cc = balance_cc else: channel.channel.autoset_balance_midi_cc() if mute_cc != -1: channel.channel.mute_midi_cc = mute_cc else: channel.channel.autoset_mute_midi_cc() return channel def add_output_channel_precreated(self, channel): frame = Gtk.Frame() frame.add(channel) self.hbox_outputs.pack_end(frame, False, True, 0) self.hbox_outputs.reorder_child(frame, 0) channel.realize() channel_edit_menu_item = Gtk.MenuItem(label=channel.channel_name) self.channel_edit_output_menu.append(channel_edit_menu_item) channel_edit_menu_item.connect("activate", self.on_edit_output_channel, channel) self.channel_edit_output_menu_item.set_sensitive(True) channel_remove_menu_item = Gtk.MenuItem(label=channel.channel_name) self.channel_remove_output_menu.append(channel_remove_menu_item) channel_remove_menu_item.connect("activate", self.on_remove_output_channel, channel) self.channel_remove_output_menu_item.set_sensitive(True) self.output_channels.append(channel) channel.connect('output-channel-order-changed', self.on_output_channel_order_changed) def on_output_channel_order_changed(self, widget, source_name, dest_name): self.output_channels.clear() channel_box = self.hbox_outputs frames = channel_box.get_children() for f in frames: c = f.get_child() if source_name == c._channel_name: source_frame = f break for f in frames: c = f.get_child() if (dest_name == c._channel_name): pos = len(frames) - 1 - frames.index(f) channel_box.reorder_child(source_frame, pos) break for frame in self.hbox_outputs.get_children(): c = frame.get_child() self.output_channels.append(c) _monitored_channel = None def get_monitored_channel(self): return self._monitored_channel def set_monitored_channel(self, channel): if self._monitored_channel: if channel.channel.name == self._monitored_channel.channel.name: return self._monitored_channel = channel if type(channel) is InputChannel: # reset all solo/mute settings for in_channel in self.channels: self.monitor_channel.set_solo(in_channel.channel, False) self.monitor_channel.set_muted(in_channel.channel, False) self.monitor_channel.set_solo(channel.channel, True) self.monitor_channel.prefader = True else: self.monitor_channel.prefader = False self.update_monitor(channel) monitored_channel = property(get_monitored_channel, set_monitored_channel) def update_monitor(self, channel): if self._monitored_channel is not channel: return self.monitor_channel.volume = channel.channel.volume self.monitor_channel.balance = channel.channel.balance if type(self.monitored_channel) is OutputChannel: # sync solo/muted channels for input_channel in self.channels: self.monitor_channel.set_solo( input_channel.channel, channel.channel.is_solo(input_channel.channel)) self.monitor_channel.set_muted( input_channel.channel, channel.channel.is_muted(input_channel.channel)) def get_input_channel_by_name(self, name): for input_channel in self.channels: if input_channel.channel.name == name: return input_channel return None def on_about(self, *args): about = Gtk.AboutDialog() about.set_name('jack_mixer') about.set_program_name('jack_mixer') about.set_copyright( 'Copyright © 2006-2020\nNedko Arnaudov, Frédéric Péters, Arnout Engelen, Daniel Sheeler' ) about.set_license("""\ jack_mixer is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. jack_mixer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with jack_mixer; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-130159 USA""") about.set_authors([ 'Nedko Arnaudov <*****@*****.**>', 'Christopher Arndt <*****@*****.**>', 'Arnout Engelen <*****@*****.**>', 'John Hedges <*****@*****.**>', 'Olivier Humbert <*****@*****.**>', 'Sarah Mischke <*****@*****.**>', 'Frédéric Péters <*****@*****.**>', 'Daniel Sheeler <*****@*****.**>', 'Athanasios Silis <*****@*****.**>', ]) about.set_logo_icon_name('jack_mixer') about.set_version(__version__) about.set_website('https://rdio.space/jackmixer/') about.run() about.destroy() def save_to_xml(self, file): log.debug("Saving to XML...") b = XmlSerialization() s = Serializator() s.serialize(self, b) b.save(file) def load_from_xml(self, file, silence_errors=False, from_nsm=False): log.debug("Loading from XML...") self.unserialized_channels = [] b = XmlSerialization() try: b.load(file) except: if silence_errors: return raise self.on_channels_clear(None) s = Serializator() s.unserialize(self, b) for channel in self.unserialized_channels: if isinstance(channel, InputChannel): if self._init_solo_channels and channel.channel_name in self._init_solo_channels: channel.solo = True self.add_channel_precreated(channel) self._init_solo_channels = None for channel in self.unserialized_channels: if isinstance(channel, OutputChannel): self.add_output_channel_precreated(channel) del self.unserialized_channels width, height = self.window.get_size() if self.visible or not from_nsm: self.window.show_all() self.paned.set_position(self.paned_position / self.width * width) self.window.resize(self.width, self.height) def serialize(self, object_backend): width, height = self.window.get_size() object_backend.add_property('geometry', '%sx%s' % (width, height)) pos = self.paned.get_position() object_backend.add_property('paned_position', '%s' % pos) solo_channels = [] for input_channel in self.channels: if input_channel.channel.solo: solo_channels.append(input_channel) if solo_channels: object_backend.add_property( 'solo_channels', '|'.join([x.channel.name for x in solo_channels])) object_backend.add_property('visible', '%s' % str(self.visible)) def unserialize_property(self, name, value): if name == 'geometry': width, height = value.split('x') self.width = int(width) self.height = int(height) return True if name == 'solo_channels': self._init_solo_channels = value.split('|') return True if name == 'visible': self.visible = value == 'True' return True if name == 'paned_position': self.paned_position = int(value) return True return False def unserialize_child(self, name): if name == InputChannel.serialization_name(): channel = InputChannel(self, "", True) self.unserialized_channels.append(channel) return channel if name == OutputChannel.serialization_name(): channel = OutputChannel(self, "", True) self.unserialized_channels.append(channel) return channel if name == gui.Factory.serialization_name(): return self.gui_factory def serialization_get_childs(self): '''Get child objects that required and support serialization''' childs = self.channels[:] + self.output_channels[:] + [ self.gui_factory ] return childs def serialization_name(self): return "jack_mixer" def main(self): if not self.mixer: return if self.visible or self.nsm_client == None: width, height = self.window.get_size() self.window.show_all() if hasattr(self, 'paned_position'): self.paned.set_position(self.paned_position / self.width * width) signal.signal(signal.SIGUSR1, self.sighandler) signal.signal(signal.SIGTERM, self.sighandler) signal.signal(signal.SIGINT, self.sighandler) signal.signal(signal.SIGHUP, signal.SIG_IGN) Gtk.main()