2 # -*- coding: utf-8 -*-
4 # This file is part of jack_mixer
6 # Copyright (C) 2006-2009 Nedko Arnaudov <nedko@arnaudov.name>
7 # Copyright (C) 2009 Frederic Peters <fpeters@0d.be>
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; version 2 of the License
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 from argparse
import ArgumentParser
29 gi
.require_version('Gtk', '3.0')
30 from gi
.repository
import Gtk
31 from gi
.repository
import GObject
32 from gi
.repository
import GLib
34 # temporary change Python modules lookup path to look into installation
35 # directory ($prefix/share/jack_mixer/)
37 sys
.path
.insert(0, os
.path
.join(os
.path
.dirname(sys
.argv
[0]), '..', 'share', 'jack_mixer'))
44 from nsmclient
import NSMClient
45 from serialization_xml
import XmlSerialization
46 from serialization
import SerializedObject
, Serializator
47 from preferences
import PreferencesDialog
49 # restore Python modules lookup path
51 log
= logging
.getLogger("jack_mixer")
54 class JackMixer(SerializedObject
):
56 # scales suitable as meter scales
57 meter_scales
= [scale
.IEC268(), scale
.Linear70dB(), scale
.IEC268Minimalistic()]
59 # scales suitable as volume slider scales
60 slider_scales
= [scale
.Linear30dB(), scale
.Linear70dB()]
62 # name of settngs file that is currently open
63 current_filename
= None
65 _init_solo_channels
= None
67 def __init__(self
, client_name
='jack_mixer'):
69 self
.nsm_client
= None
71 if os
.environ
.get('NSM_URL'):
72 self
.nsm_client
= NSMClient(prettyName
= "jack_mixer",
73 saveCallback
= self
.nsm_save_cb
,
74 openOrNewCallback
= self
.nsm_open_cb
,
75 supportsSaveStatus
= False,
76 hideGUICallback
= self
.nsm_hide_cb
,
77 showGUICallback
= self
.nsm_show_cb
,
78 exitProgramCallback
= self
.nsm_exit_cb
,
79 loggingLevel
= "error",
81 self
.nsm_client
.announceGuiVisibility(self
.visible
)
85 self
.create_mixer(client_name
, with_nsm
= False)
87 def create_mixer(self
, client_name
, with_nsm
= True):
88 self
.mixer
= jack_mixer_c
.Mixer(client_name
)
89 self
.create_ui(with_nsm
)
93 self
.window
.set_title(client_name
)
95 self
.monitor_channel
= self
.mixer
.add_output_channel("Monitor", True, True)
98 GLib
.timeout_add(80, self
.read_meters
)
100 GLib
.timeout_add(200, self
.nsm_react
)
101 GLib
.timeout_add(50, self
.midi_events_check
)
103 def new_menu_item(self
, title
, callback
=None, accel
=None, enabled
=True):
104 menuitem
= Gtk
.MenuItem
.new_with_mnemonic(title
)
105 menuitem
.set_sensitive(enabled
)
107 menuitem
.connect("activate", callback
)
109 key
, mod
= Gtk
.accelerator_parse(accel
)
110 menuitem
.add_accelerator("activate", self
.menu_accelgroup
, key
, mod
,
111 Gtk
.AccelFlags
.VISIBLE
)
114 def create_ui(self
, with_nsm
):
116 self
.output_channels
= []
117 self
.window
= Gtk
.Window(type=Gtk
.WindowType
.TOPLEVEL
)
118 self
.window
.set_icon_name('jack_mixer')
119 self
.gui_factory
= gui
.Factory(self
.window
, self
.meter_scales
, self
.slider_scales
)
120 self
.gui_factory
.connect('midi-behavior-mode-changed', self
.on_midi_behavior_mode_changed
)
121 self
.gui_factory
.emit_midi_behavior_mode()
123 self
.vbox_top
= Gtk
.VBox()
124 self
.window
.add(self
.vbox_top
)
126 self
.menu_accelgroup
= Gtk
.AccelGroup()
127 self
.window
.add_accel_group(self
.menu_accelgroup
)
129 self
.menubar
= Gtk
.MenuBar()
130 self
.vbox_top
.pack_start(self
.menubar
, False, True, 0)
132 mixer_menu_item
= Gtk
.MenuItem
.new_with_mnemonic("_Mixer")
133 self
.menubar
.append(mixer_menu_item
)
134 edit_menu_item
= Gtk
.MenuItem
.new_with_mnemonic('_Edit')
135 self
.menubar
.append(edit_menu_item
)
136 help_menu_item
= Gtk
.MenuItem
.new_with_mnemonic('_Help')
137 self
.menubar
.append(help_menu_item
)
141 self
.paned_position
= 210
142 self
.window
.set_default_size(self
.width
, self
.height
)
144 self
.mixer_menu
= Gtk
.Menu()
145 mixer_menu_item
.set_submenu(self
.mixer_menu
)
147 self
.mixer_menu
.append(self
.new_menu_item('New _Input Channel',
148 self
.on_add_input_channel
, "<Control>N"))
149 self
.mixer_menu
.append(self
.new_menu_item('New Output _Channel',
150 self
.on_add_output_channel
, "<Shift><Control>N"))
152 self
.mixer_menu
.append(Gtk
.SeparatorMenuItem())
154 self
.mixer_menu
.append(self
.new_menu_item('_Open...', self
.on_open_cb
, "<Control>O"))
156 self
.mixer_menu
.append(self
.new_menu_item('_Save', self
.on_save_cb
, "<Control>S"))
159 self
.mixer_menu
.append(self
.new_menu_item('Save _As...', self
.on_save_as_cb
,
160 "<Shift><Control>S"))
162 self
.mixer_menu
.append(Gtk
.SeparatorMenuItem())
163 self
.mixer_menu
.append(self
.new_menu_item('_Quit', self
.on_quit_cb
, "<Control>Q"))
165 edit_menu
= Gtk
.Menu()
166 edit_menu_item
.set_submenu(edit_menu
)
168 self
.channel_edit_input_menu_item
= self
.new_menu_item('_Edit Input Channel',
170 edit_menu
.append(self
.channel_edit_input_menu_item
)
171 self
.channel_edit_input_menu
= Gtk
.Menu()
172 self
.channel_edit_input_menu_item
.set_submenu(self
.channel_edit_input_menu
)
174 self
.channel_edit_output_menu_item
= self
.new_menu_item('E_dit Output Channel',
176 edit_menu
.append(self
.channel_edit_output_menu_item
)
177 self
.channel_edit_output_menu
= Gtk
.Menu()
178 self
.channel_edit_output_menu_item
.set_submenu(self
.channel_edit_output_menu
)
180 self
.channel_remove_input_menu_item
= self
.new_menu_item('_Remove Input Channel',
182 edit_menu
.append(self
.channel_remove_input_menu_item
)
183 self
.channel_remove_input_menu
= Gtk
.Menu()
184 self
.channel_remove_input_menu_item
.set_submenu(self
.channel_remove_input_menu
)
186 self
.channel_remove_output_menu_item
= self
.new_menu_item('Re_move Output Channel',
188 edit_menu
.append(self
.channel_remove_output_menu_item
)
189 self
.channel_remove_output_menu
= Gtk
.Menu()
190 self
.channel_remove_output_menu_item
.set_submenu(self
.channel_remove_output_menu
)
192 edit_menu
.append(self
.new_menu_item('_Clear', self
.on_channels_clear
, "<Control>X"))
193 edit_menu
.append(Gtk
.SeparatorMenuItem())
194 edit_menu
.append(self
.new_menu_item('_Preferences', self
.on_preferences_cb
, "<Control>P"))
196 help_menu
= Gtk
.Menu()
197 help_menu_item
.set_submenu(help_menu
)
199 help_menu
.append(self
.new_menu_item('_About', self
.on_about
, "F1"))
201 self
.hbox_top
= Gtk
.HBox()
202 self
.vbox_top
.pack_start(self
.hbox_top
, True, True, 0)
204 self
.scrolled_window
= Gtk
.ScrolledWindow()
205 self
.scrolled_window
.set_policy(Gtk
.PolicyType
.AUTOMATIC
, Gtk
.PolicyType
.AUTOMATIC
)
207 self
.hbox_inputs
= Gtk
.Box()
208 self
.hbox_inputs
.set_spacing(0)
209 self
.hbox_inputs
.set_border_width(0)
210 self
.hbox_top
.set_spacing(0)
211 self
.hbox_top
.set_border_width(0)
212 self
.scrolled_window
.add(self
.hbox_inputs
)
213 self
.hbox_outputs
= Gtk
.Box()
214 self
.hbox_outputs
.set_spacing(0)
215 self
.hbox_outputs
.set_border_width(0)
216 self
.scrolled_output
= Gtk
.ScrolledWindow()
217 self
.scrolled_output
.set_policy(Gtk
.PolicyType
.AUTOMATIC
, Gtk
.PolicyType
.AUTOMATIC
)
218 self
.scrolled_output
.add(self
.hbox_outputs
)
219 self
.paned
= Gtk
.HPaned()
220 self
.paned
.set_wide_handle(True)
221 self
.hbox_top
.pack_start(self
.paned
, True, True, 0)
222 self
.paned
.pack1(self
.scrolled_window
, True, False)
223 self
.paned
.pack2(self
.scrolled_output
, True, False)
225 self
.window
.connect("destroy", Gtk
.main_quit
)
227 self
.window
.connect('delete-event', self
.on_delete_event
)
230 self
.nsm_client
.reactToMessage()
233 def nsm_hide_cb(self
):
236 self
.nsm_client
.announceGuiVisibility(False)
238 def nsm_show_cb(self
):
239 width
, height
= self
.window
.get_size()
240 self
.window
.show_all()
241 self
.paned
.set_position(self
.paned_position
/self
.width
*width
)
244 self
.nsm_client
.announceGuiVisibility(True)
246 def nsm_open_cb(self
, path
, session_name
, client_name
):
247 self
.create_mixer(client_name
, with_nsm
= True)
248 self
.current_filename
= path
+ '.xml'
249 if os
.path
.isfile(self
.current_filename
):
250 f
= open(self
.current_filename
, 'r')
251 self
.load_from_xml(f
, from_nsm
=True)
254 f
= open(self
.current_filename
, 'w')
257 def nsm_save_cb(self
, path
, session_name
, client_name
):
258 self
.current_filename
= path
+ '.xml'
259 f
= open(self
.current_filename
, 'w')
263 def nsm_exit_cb(self
, path
, session_name
, client_name
):
266 def on_midi_behavior_mode_changed(self
, gui_factory
, value
):
267 self
.mixer
.midi_behavior_mode
= value
269 def on_delete_event(self
, widget
, event
):
272 def sighandler(self
, signum
, frame
):
273 log
.debug("Signal %d received.", signum
)
274 if signum
== signal
.SIGUSR1
:
276 elif signum
== signal
.SIGTERM
:
278 elif signum
== signal
.SIGINT
:
281 log
.warning("Unknown signal %d received.", signum
)
284 log
.debug("Cleaning jack_mixer.")
288 for channel
in self
.channels
:
293 def on_open_cb(self
, *args
):
294 dlg
= Gtk
.FileChooserDialog(title
='Open', parent
=self
.window
,
295 action
=Gtk
.FileChooserAction
.OPEN
)
296 dlg
.add_buttons(Gtk
.STOCK_CANCEL
, Gtk
.ResponseType
.CANCEL
,
297 Gtk
.STOCK_OPEN
, Gtk
.ResponseType
.OK
)
298 dlg
.set_default_response(Gtk
.ResponseType
.OK
)
299 if dlg
.run() == Gtk
.ResponseType
.OK
:
300 filename
= dlg
.get_filename()
302 f
= open(filename
, 'r')
303 self
.load_from_xml(f
)
304 except Exception as e
:
305 error_dialog(self
.window
, "Failed loading settings (%s)", e
)
307 self
.current_filename
= filename
312 def on_save_cb(self
, *args
):
313 if not self
.current_filename
:
314 return self
.on_save_as_cb()
315 f
= open(self
.current_filename
, 'w')
319 def on_save_as_cb(self
, *args
):
320 dlg
= Gtk
.FileChooserDialog(title
='Save', parent
=self
.window
,
321 action
=Gtk
.FileChooserAction
.SAVE
)
322 dlg
.add_buttons(Gtk
.STOCK_CANCEL
, Gtk
.ResponseType
.CANCEL
,
323 Gtk
.STOCK_SAVE
, Gtk
.ResponseType
.OK
)
324 dlg
.set_default_response(Gtk
.ResponseType
.OK
)
325 if dlg
.run() == Gtk
.ResponseType
.OK
:
326 self
.current_filename
= dlg
.get_filename()
330 def on_quit_cb(self
, *args
):
333 preferences_dialog
= None
334 def on_preferences_cb(self
, widget
):
335 if not self
.preferences_dialog
:
336 self
.preferences_dialog
= PreferencesDialog(self
)
337 self
.preferences_dialog
.show()
338 self
.preferences_dialog
.present()
340 def on_add_input_channel(self
, widget
):
341 dialog
= NewInputChannelDialog(app
=self
)
342 dialog
.set_transient_for(self
.window
)
347 if ret
== Gtk
.ResponseType
.OK
:
348 result
= dialog
.get_result()
349 channel
= self
.add_channel(**result
)
350 if self
.visible
or self
.nsm_client
== None:
351 self
.window
.show_all()
353 def on_add_output_channel(self
, widget
):
354 dialog
= NewOutputChannelDialog(app
=self
)
355 dialog
.set_transient_for(self
.window
)
360 if ret
== Gtk
.ResponseType
.OK
:
361 result
= dialog
.get_result()
362 channel
= self
.add_output_channel(**result
)
363 if self
.visible
or self
.nsm_client
== None:
364 self
.window
.show_all()
366 def on_edit_input_channel(self
, widget
, channel
):
367 log
.debug('Editing input channel "%s".', channel
.channel_name
)
368 channel
.on_channel_properties()
370 def remove_channel_edit_input_menuitem_by_label(self
, widget
, label
):
371 if (widget
.get_label() == label
):
372 self
.channel_edit_input_menu
.remove(widget
)
374 def on_remove_input_channel(self
, widget
, channel
):
375 log
.debug('Removing input channel "%s".', channel
.channel_name
)
376 self
.channel_remove_input_menu
.remove(widget
)
377 self
.channel_edit_input_menu
.foreach(
378 self
.remove_channel_edit_input_menuitem_by_label
,
379 channel
.channel_name
);
380 if self
.monitored_channel
is channel
:
381 channel
.monitor_button
.set_active(False)
382 for i
in range(len(self
.channels
)):
383 if self
.channels
[i
] is channel
:
386 self
.hbox_inputs
.remove(channel
.get_parent())
388 if not self
.channels
:
389 self
.channel_edit_input_menu_item
.set_sensitive(False)
390 self
.channel_remove_input_menu_item
.set_sensitive(False)
392 def on_edit_output_channel(self
, widget
, channel
):
393 log
.debug('Editing output channel "%s".', channel
.channel_name
)
394 channel
.on_channel_properties()
396 def remove_channel_edit_output_menuitem_by_label(self
, widget
, label
):
397 if (widget
.get_label() == label
):
398 self
.channel_edit_output_menu
.remove(widget
)
400 def on_remove_output_channel(self
, widget
, channel
):
401 log
.debug('Removing output channel "%s".', channel
.channel_name
)
402 self
.channel_remove_output_menu
.remove(widget
)
403 self
.channel_edit_output_menu
.foreach(
404 self
.remove_channel_edit_output_menuitem_by_label
,
405 channel
.channel_name
);
406 if self
.monitored_channel
is channel
:
407 channel
.monitor_button
.set_active(False)
408 for i
in range(len(self
.channels
)):
409 if self
.output_channels
[i
] is channel
:
411 del self
.output_channels
[i
]
412 self
.hbox_outputs
.remove(channel
.get_parent())
414 if not self
.output_channels
:
415 self
.channel_edit_output_menu_item
.set_sensitive(False)
416 self
.channel_remove_output_menu_item
.set_sensitive(False)
418 def rename_channels(self
, container
, parameters
):
419 if (container
.get_label() == parameters
['oldname']):
420 container
.set_label(parameters
['newname'])
422 def on_channel_rename(self
, oldname
, newname
):
423 rename_parameters
= { 'oldname' : oldname
, 'newname' : newname
}
424 self
.channel_edit_input_menu
.foreach(self
.rename_channels
,
426 self
.channel_edit_output_menu
.foreach(self
.rename_channels
,
428 self
.channel_remove_input_menu
.foreach(self
.rename_channels
,
430 self
.channel_remove_output_menu
.foreach(self
.rename_channels
,
432 log
.debug('Renaming channel from "%s" to "%s".', oldname
, newname
)
434 def on_channels_clear(self
, widget
):
435 dlg
= Gtk
.MessageDialog(parent
= self
.window
,
437 message_type
= Gtk
.MessageType
.WARNING
,
438 text
= "Are you sure you want to clear all channels?",
439 buttons
= Gtk
.ButtonsType
.OK_CANCEL
)
440 if not widget
or dlg
.run() == Gtk
.ResponseType
.OK
:
441 for channel
in self
.output_channels
:
443 self
.hbox_outputs
.remove(channel
.get_parent())
444 for channel
in self
.channels
:
446 self
.hbox_inputs
.remove(channel
.get_parent())
448 self
.output_channels
= []
449 self
.channel_edit_input_menu
= Gtk
.Menu()
450 self
.channel_edit_input_menu_item
.set_submenu(self
.channel_edit_input_menu
)
451 self
.channel_edit_input_menu_item
.set_sensitive(False)
452 self
.channel_remove_input_menu
= Gtk
.Menu()
453 self
.channel_remove_input_menu_item
.set_submenu(self
.channel_remove_input_menu
)
454 self
.channel_remove_input_menu_item
.set_sensitive(False)
455 self
.channel_edit_output_menu
= Gtk
.Menu()
456 self
.channel_edit_output_menu_item
.set_submenu(self
.channel_edit_output_menu
)
457 self
.channel_edit_output_menu_item
.set_sensitive(False)
458 self
.channel_remove_output_menu
= Gtk
.Menu()
459 self
.channel_remove_output_menu_item
.set_submenu(self
.channel_remove_output_menu
)
460 self
.channel_remove_output_menu_item
.set_sensitive(False)
463 def add_channel(self
, name
, stereo
, volume_cc
, balance_cc
, mute_cc
, solo_cc
, value
):
465 channel
= InputChannel(self
, name
, stereo
, value
)
466 self
.add_channel_precreated(channel
)
468 error_dialog(self
.window
, "Channel creation failed.")
471 channel
.channel
.volume_midi_cc
= volume_cc
473 channel
.channel
.autoset_volume_midi_cc()
475 channel
.channel
.balance_midi_cc
= balance_cc
477 channel
.channel
.autoset_balance_midi_cc()
479 channel
.channel
.mute_midi_cc
= mute_cc
481 channel
.channel
.autoset_mute_midi_cc()
483 channel
.channel
.solo_midi_cc
= solo_cc
485 channel
.channel
.autoset_solo_midi_cc()
488 def add_channel_precreated(self
, channel
):
491 self
.hbox_inputs
.pack_start(frame
, False, True, 0)
494 channel_edit_menu_item
= Gtk
.MenuItem(label
=channel
.channel_name
)
495 self
.channel_edit_input_menu
.append(channel_edit_menu_item
)
496 channel_edit_menu_item
.connect("activate", self
.on_edit_input_channel
, channel
)
497 self
.channel_edit_input_menu_item
.set_sensitive(True)
499 channel_remove_menu_item
= Gtk
.MenuItem(label
=channel
.channel_name
)
500 self
.channel_remove_input_menu
.append(channel_remove_menu_item
)
501 channel_remove_menu_item
.connect("activate", self
.on_remove_input_channel
, channel
)
502 self
.channel_remove_input_menu_item
.set_sensitive(True)
504 self
.channels
.append(channel
)
506 for outputchannel
in self
.output_channels
:
507 channel
.add_control_group(outputchannel
)
509 # create post fader output channel matching the input channel
510 channel
.post_fader_output_channel
= self
.mixer
.add_output_channel(
511 channel
.channel
.name
+ ' Out', channel
.channel
.is_stereo
, True)
512 channel
.post_fader_output_channel
.volume
= 0
513 channel
.post_fader_output_channel
.set_solo(channel
.channel
, True)
515 def read_meters(self
):
516 for channel
in self
.channels
:
518 for channel
in self
.output_channels
:
522 def midi_events_check(self
):
523 for channel
in self
.channels
+ self
.output_channels
:
524 channel
.midi_events_check()
527 def add_output_channel(self
, name
, stereo
, volume_cc
, balance_cc
, mute_cc
,
528 display_solo_buttons
, color
, value
):
530 channel
= OutputChannel(self
, name
, stereo
, value
)
531 channel
.display_solo_buttons
= display_solo_buttons
532 channel
.color
= color
533 self
.add_output_channel_precreated(channel
)
535 error_dialog(self
.window
, "Channel creation failed")
539 channel
.channel
.volume_midi_cc
= volume_cc
541 channel
.channel
.autoset_volume_midi_cc()
543 channel
.channel
.balance_midi_cc
= balance_cc
545 channel
.channel
.autoset_balance_midi_cc()
547 channel
.channel
.mute_midi_cc
= mute_cc
549 channel
.channel
.autoset_mute_midi_cc()
553 def add_output_channel_precreated(self
, channel
):
556 self
.hbox_outputs
.pack_end(frame
, False, True, 0)
557 self
.hbox_outputs
.reorder_child(frame
, 0)
560 channel_edit_menu_item
= Gtk
.MenuItem(label
=channel
.channel_name
)
561 self
.channel_edit_output_menu
.append(channel_edit_menu_item
)
562 channel_edit_menu_item
.connect("activate", self
.on_edit_output_channel
, channel
)
563 self
.channel_edit_output_menu_item
.set_sensitive(True)
565 channel_remove_menu_item
= Gtk
.MenuItem(label
=channel
.channel_name
)
566 self
.channel_remove_output_menu
.append(channel_remove_menu_item
)
567 channel_remove_menu_item
.connect("activate", self
.on_remove_output_channel
, channel
)
568 self
.channel_remove_output_menu_item
.set_sensitive(True)
570 self
.output_channels
.append(channel
)
572 _monitored_channel
= None
573 def get_monitored_channel(self
):
574 return self
._monitored
_channel
576 def set_monitored_channel(self
, channel
):
577 if self
._monitored
_channel
:
578 if channel
.channel
.name
== self
._monitored
_channel
.channel
.name
:
580 self
._monitored
_channel
= channel
581 if type(channel
) is InputChannel
:
582 # reset all solo/mute settings
583 for in_channel
in self
.channels
:
584 self
.monitor_channel
.set_solo(in_channel
.channel
, False)
585 self
.monitor_channel
.set_muted(in_channel
.channel
, False)
586 self
.monitor_channel
.set_solo(channel
.channel
, True)
587 self
.monitor_channel
.prefader
= True
589 self
.monitor_channel
.prefader
= False
590 self
.update_monitor(channel
)
591 monitored_channel
= property(get_monitored_channel
, set_monitored_channel
)
593 def update_monitor(self
, channel
):
594 if self
.monitored_channel
is not channel
:
596 self
.monitor_channel
.volume
= channel
.channel
.volume
597 self
.monitor_channel
.balance
= channel
.channel
.balance
598 self
.monitor_channel
.out_mute
= channel
.channel
.out_mute
599 if type(self
.monitored_channel
) is OutputChannel
:
600 # sync solo/muted channels
601 for input_channel
in self
.channels
:
602 self
.monitor_channel
.set_solo(input_channel
.channel
,
603 channel
.channel
.is_solo(input_channel
.channel
))
604 self
.monitor_channel
.set_muted(input_channel
.channel
,
605 channel
.channel
.is_muted(input_channel
.channel
))
607 def get_input_channel_by_name(self
, name
):
608 for input_channel
in self
.channels
:
609 if input_channel
.channel
.name
== name
:
613 def on_about(self
, *args
):
614 about
= Gtk
.AboutDialog()
615 about
.set_name('jack_mixer')
616 about
.set_copyright('Copyright © 2006-2020\nNedko Arnaudov, Frédéric Péters, Arnout Engelen, Daniel Sheeler')
617 about
.set_license('''\
618 jack_mixer is free software; you can redistribute it and/or modify it
619 under the terms of the GNU General Public License as published by the
620 Free Software Foundation; either version 2 of the License, or (at your
621 option) any later version.
623 jack_mixer is distributed in the hope that it will be useful, but
624 WITHOUT ANY WARRANTY; without even the implied warranty of
625 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
626 General Public License for more details.
628 You should have received a copy of the GNU General Public License along
629 with jack_mixer; if not, write to the Free Software Foundation, Inc., 51
630 Franklin Street, Fifth Floor, Boston, MA 02110-130159 USA''')
632 'Nedko Arnaudov <nedko@arnaudov.name>',
633 'Christopher Arndt <chris@chrisarndt.de>',
634 'Arnout Engelen <arnouten@bzzt.net>',
635 'John Hedges <john@drystone.co.uk>',
636 'Olivier Humbert <trebmuh@tuxfamily.org>',
637 'Sarah Mischke <sarah@spooky-online.de>',
638 'Frédéric Péters <fpeters@0d.be>',
639 'Daniel Sheeler <dsheeler@pobox.com>',
640 'Athanasios Silis <athanasios.silis@gmail.com>',
642 about
.set_logo_icon_name('jack_mixer')
643 about
.set_website('https://rdio.space/jackmixer/')
648 def save_to_xml(self
, file):
649 log
.debug("Saving to XML...")
650 b
= XmlSerialization()
655 def load_from_xml(self
, file, silence_errors
=False, from_nsm
=False):
656 log
.debug("Loading from XML...")
657 self
.unserialized_channels
= []
658 b
= XmlSerialization()
665 self
.on_channels_clear(None)
667 s
.unserialize(self
, b
)
668 for channel
in self
.unserialized_channels
:
669 if isinstance(channel
, InputChannel
):
670 if self
._init
_solo
_channels
and channel
.channel_name
in self
._init
_solo
_channels
:
672 self
.add_channel_precreated(channel
)
673 self
._init
_solo
_channels
= None
674 for channel
in self
.unserialized_channels
:
675 if isinstance(channel
, OutputChannel
):
676 self
.add_output_channel_precreated(channel
)
677 del self
.unserialized_channels
678 width
, height
= self
.window
.get_size()
679 if self
.visible
or not from_nsm
:
680 self
.window
.show_all()
681 self
.paned
.set_position(self
.paned_position
/self
.width
*width
)
682 self
.window
.resize(self
.width
, self
.height
)
684 def serialize(self
, object_backend
):
685 width
, height
= self
.window
.get_size()
686 object_backend
.add_property('geometry',
687 '%sx%s' % (width
, height
))
688 pos
= self
.paned
.get_position()
689 object_backend
.add_property('paned_position', '%s' % pos
)
691 for input_channel
in self
.channels
:
692 if input_channel
.channel
.solo
:
693 solo_channels
.append(input_channel
)
695 object_backend
.add_property('solo_channels', '|'.join([x
.channel
.name
for x
in solo_channels
]))
696 object_backend
.add_property('visible', '%s' % str(self
.visible
))
698 def unserialize_property(self
, name
, value
):
699 if name
== 'geometry':
700 width
, height
= value
.split('x')
701 self
.width
= int(width
)
702 self
.height
= int(height
)
704 if name
== 'solo_channels':
705 self
._init
_solo
_channels
= value
.split('|')
707 if name
== 'visible':
708 self
.visible
= value
== 'True'
710 if name
== 'paned_position':
711 self
.paned_position
= int(value
)
715 def unserialize_child(self
, name
):
716 if name
== InputChannel
.serialization_name():
717 channel
= InputChannel(self
, "", True)
718 self
.unserialized_channels
.append(channel
)
721 if name
== OutputChannel
.serialization_name():
722 channel
= OutputChannel(self
, "", True)
723 self
.unserialized_channels
.append(channel
)
726 if name
== gui
.Factory
.serialization_name():
727 return self
.gui_factory
729 def serialization_get_childs(self
):
730 '''Get child objects that required and support serialization'''
731 childs
= self
.channels
[:] + self
.output_channels
[:] + [self
.gui_factory
]
734 def serialization_name(self
):
741 if self
.visible
or self
.nsm_client
== None:
742 width
, height
= self
.window
.get_size()
743 self
.window
.show_all()
744 if hasattr(self
, 'paned_position'):
745 self
.paned
.set_position(self
.paned_position
/self
.width
*width
)
747 signal
.signal(signal
.SIGUSR1
, self
.sighandler
)
748 signal
.signal(signal
.SIGTERM
, self
.sighandler
)
749 signal
.signal(signal
.SIGINT
, self
.sighandler
)
750 signal
.signal(signal
.SIGHUP
, signal
.SIG_IGN
)
754 def error_dialog(parent
, msg
, *args
):
755 log
.exception(msg
, *args
)
756 err
= Gtk
.MessageDialog(parent
=parent
, modal
=True, destroy_with_parent
=True,
757 message_type
=Gtk
.MessageType
.ERROR
, buttons
=Gtk
.ButtonsType
.OK
, text
=msg
% args
)
762 parser
= ArgumentParser()
763 parser
.add_argument('-c', '--config', metavar
="FILE", help='load mixer project configuration from FILE')
764 parser
.add_argument('-d', '--debug', action
="store_true", help='enable debug logging messages')
765 parser
.add_argument('client_name', metavar
='NAME', nargs
='?', default
='jack_mixer',
766 help='set JACK client name')
767 args
= parser
.parse_args()
769 logging
.basicConfig(level
=logging
.DEBUG
if args
.debug
else logging
.INFO
,
770 format
="%(levelname)s: %(message)s")
773 mixer
= JackMixer(args
.client_name
)
774 except Exception as e
:
775 error_dialog(None, "Mixer creation failed (%s).", e
)
778 if not mixer
.nsm_client
and args
.config
:
779 f
= open(args
.config
)
780 mixer
.current_filename
= args
.config
783 mixer
.load_from_xml(f
)
784 except Exception as e
:
785 error_dialog(mixer
.window
, "Failed loading settings (%s).", e
)
787 mixer
.window
.set_default_size(60*(1+len(mixer
.channels
)+len(mixer
.output_channels
)), 300)
794 if __name__
== "__main__":