1 # This file is part of jack_mixer
3 # Copyright (C) 2006 Nedko Arnaudov <nedko@arnaudov.name>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; version 2 of the License
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19 from gi.repository import Gtk
20 from gi.repository import Gdk
21 from gi.repository import GObject
25 from serialization import SerializedObject
35 :not(button) > label {min-width: 100px;}
39 css_provider = Gtk.CssProvider()
40 css_provider.load_from_data(css)
41 context = Gtk.StyleContext()
42 screen = Gdk.Screen.get_default()
43 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
45 def set_background_color(widget, name, color_string):
50 """ % (name, color_string)
52 css_provider = Gtk.CssProvider()
53 css_provider.load_from_data(css.encode('utf-8'))
54 context = Gtk.StyleContext()
55 screen = Gdk.Screen.get_default()
56 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
58 widget_context = widget.get_style_context()
59 widget_context.add_class(name)
62 from random import uniform, seed
64 return Gdk.RGBA(uniform(0, 1), uniform(0, 1), uniform(0, 1), 1)
66 class Channel(Gtk.VBox, SerializedObject):
67 '''Widget with slider and meter used as base class for more specific
71 def __init__(self, app, name, stereo):
72 Gtk.VBox.__init__(self)
74 self.mixer = app.mixer
75 self.gui_factory = app.gui_factory
76 self._channel_name = name
78 self.meter_scale = self.gui_factory.get_default_meter_scale()
79 self.slider_scale = self.gui_factory.get_default_slider_scale()
80 self.slider_adjustment = slider.AdjustmentdBFS(self.slider_scale, 0.0, 0.02)
81 self.balance_adjustment = Gtk.Adjustment(0.0, -1.0, 1.0, 0.02)
82 self.future_out_mute = None
83 self.future_volume_midi_cc = None
84 self.future_balance_midi_cc = None
85 self.future_mute_midi_cc = None
86 self.future_solo_midi_cc = None
88 def get_channel_name(self):
89 return self._channel_name
93 post_fader_output_channel = None
94 def set_channel_name(self, name):
95 self.app.on_channel_rename(self._channel_name, name);
96 self._channel_name = name
98 self.label_name.set_text(name)
100 self.channel.name = name
101 if self.post_fader_output_channel:
102 self.post_fader_output_channel.name = "%s Out" % name;
103 channel_name = property(get_channel_name, set_channel_name)
106 #print "Realizing channel \"%s\"" % self.channel_name
107 if self.future_out_mute != None:
108 self.channel.out_mute = self.future_out_mute
110 self.slider_adjustment.connect("volume-changed", self.on_volume_changed)
111 self.balance_adjustment.connect("value-changed", self.on_balance_changed)
114 self.create_slider_widget()
117 self.meter = meter.StereoMeterWidget(self.meter_scale)
119 self.meter = meter.MonoMeterWidget(self.meter_scale)
120 self.on_vumeter_color_changed(self.gui_factory)
122 self.meter.set_events(Gdk.EventMask.SCROLL_MASK)
124 self.gui_factory.connect("default-meter-scale-changed", self.on_default_meter_scale_changed)
125 self.gui_factory.connect("default-slider-scale-changed", self.on_default_slider_scale_changed)
126 self.gui_factory.connect('vumeter-color-changed', self.on_vumeter_color_changed)
127 self.gui_factory.connect('vumeter-color-scheme-changed', self.on_vumeter_color_changed)
128 self.gui_factory.connect('use-custom-widgets-changed', self.on_custom_widgets_changed)
130 self.abspeak = abspeak.AbspeakWidget()
131 self.abspeak.connect("reset", self.on_abspeak_reset)
132 self.abspeak.connect("volume-adjust", self.on_abspeak_adjust)
134 self.volume_digits = Gtk.Entry()
135 self.volume_digits.set_property('xalign', 0.5)
136 self.volume_digits.connect("key-press-event", self.on_volume_digits_key_pressed)
137 self.volume_digits.connect("focus-out-event", self.on_volume_digits_focus_out)
139 self.connect("key-press-event", self.on_key_pressed)
140 self.connect("scroll-event", self.on_scroll)
143 #print "Unrealizing channel \"%s\"" % self.channel_name
146 def balance_preferred_width(self):
149 def _preferred_height(self):
152 def create_balance_widget(self):
153 if self.gui_factory.use_custom_widgets and phat:
154 self.balance = phat.HFanSlider()
155 self.balance.set_default_value(0)
156 self.balance.set_adjustment(self.balance_adjustment)
158 self.balance = Gtk.Scale()
159 self.balance.get_preferred_width = self.balance_preferred_width
160 self.balance.get_preferred_height = self._preferred_height
161 self.balance.set_orientation(Gtk.Orientation.HORIZONTAL)
162 self.balance.set_adjustment(self.balance_adjustment)
163 self.balance.set_has_origin(False)
164 self.balance.set_draw_value(False)
165 self.balance.button_down = False
166 self.balance.connect('button-press-event', self.on_balance_button_press_event)
167 self.balance.connect('button-release-event', self.on_balance_button_release_event)
168 self.balance.connect("motion-notify-event", self.on_balance_motion_notify_event)
169 self.balance.connect("scroll-event", self.on_balance_scroll_event)
172 self.pack_start(self.balance, False, True, 0)
173 if self.monitor_button:
174 self.reorder_child(self.monitor_button, -1)
177 def on_balance_button_press_event(self, widget, event):
178 if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
179 self.balance.button_down = True
180 self.balance.button_down_x = event.x
181 self.balance.button_down_value = self.balance.get_value()
183 if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
184 self.balance.set_value(0)
188 def on_balance_button_release_event(self, widget, event):
189 self.balance.button_down = False
192 def on_balance_motion_notify_event(self, widget, event):
193 slider_length = widget.get_allocation().width - widget.get_style_context().get_property('min-width', Gtk.StateFlags.NORMAL)
194 if self.balance.button_down:
195 delta_x = (event.x - self.balance.button_down_x) / slider_length
196 x = self.balance.button_down_value + 2 * delta_x
201 self.balance.set_value(x)
204 def on_balance_scroll_event(self, widget, event):
206 delta = bal.get_adjustment().get_step_increment()
207 value = bal.get_value()
208 if event.direction == Gdk.ScrollDirection.UP:
210 elif event.direction == Gdk.ScrollDirection.DOWN:
212 elif event.direction == Gdk.ScrollDirection.SMOOTH:
213 x = value - event.delta_y * delta
222 def create_slider_widget(self):
225 parent = self.slider.get_parent()
226 self.slider.destroy()
227 if self.gui_factory.use_custom_widgets:
228 self.slider = slider.CustomSliderWidget(self.slider_adjustment)
230 self.slider = slider.GtkSlider(self.slider_adjustment)
232 parent.pack_start(self.slider, True, True, 0)
233 parent.reorder_child(self.slider, 0)
236 def on_default_meter_scale_changed(self, gui_factory, scale):
237 #print "Default meter scale change detected."
238 self.meter.set_scale(scale)
240 def on_default_slider_scale_changed(self, gui_factory, scale):
241 #print "Default slider scale change detected."
242 self.slider_scale = scale
243 self.slider_adjustment.set_scale(scale)
244 self.channel.midi_scale = self.slider_scale.scale
246 def on_vumeter_color_changed(self, gui_factory, *args):
247 color = gui_factory.get_vumeter_color()
248 color_scheme = gui_factory.get_vumeter_color_scheme()
249 if color_scheme != 'solid':
250 self.meter.set_color(None)
252 self.meter.set_color(Gdk.color_parse(color))
254 def on_custom_widgets_changed(self, gui_factory, value):
255 self.balance.destroy()
256 self.create_balance_widget()
257 self.create_slider_widget()
259 def on_abspeak_adjust(self, abspeak, adjust):
260 #print "abspeak adjust %f" % adjust
261 self.slider_adjustment.set_value_db(self.slider_adjustment.get_value_db() + adjust)
262 self.channel.abspeak = None
263 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
265 def on_abspeak_reset(self, abspeak):
266 #print "abspeak reset"
267 self.channel.abspeak = None
269 def on_volume_digits_key_pressed(self, widget, event):
270 if (event.keyval == Gdk.KEY_Return or event.keyval == Gdk.KEY_KP_Enter):
271 db_text = self.volume_digits.get_text()
274 #print "Volume digits confirmation \"%f dBFS\"" % db
275 except (ValueError) as e:
276 #print "Volume digits confirmation ignore, reset to current"
277 self.update_volume(False)
279 self.slider_adjustment.set_value_db(db)
281 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
283 def on_volume_digits_focus_out(self, widget, event):
284 #print "volume digits focus out detected"
285 self.update_volume(False)
287 def read_meter(self):
291 meter_left, meter_right = self.channel.meter
292 self.meter.set_values(meter_left, meter_right)
294 self.meter.set_value(self.channel.meter[0])
296 self.abspeak.set_peak(self.channel.abspeak)
298 def on_scroll(self, widget, event):
299 if event.direction == Gdk.ScrollDirection.DOWN:
300 self.slider_adjustment.step_down()
301 elif event.direction == Gdk.ScrollDirection.UP:
302 self.slider_adjustment.step_up()
305 def update_volume(self, update_engine, from_midi = False):
306 db = self.slider_adjustment.get_value_db()
308 db_text = "%.2f" % db
309 self.volume_digits.set_text(db_text)
313 self.channel.volume = db
315 self.channel.set_volume_from_midi(db)
316 self.app.update_monitor(self)
318 def on_volume_changed(self, adjustment):
319 self.update_volume(True)
321 def on_volume_changed_from_midi(self, adjustment):
322 self.update_volume(True, from_midi = True)
324 def on_balance_changed(self, adjustment):
325 balance = self.balance_adjustment.get_value()
326 #print "%s balance: %f" % (self.channel_name, balance)
327 self.channel.balance = balance
328 self.app.update_monitor(self)
330 def on_key_pressed(self, widget, event):
331 if (event.keyval == Gdk.KEY_Up):
332 #print self.channel_name + " Up"
333 self.slider_adjustment.step_up()
335 elif (event.keyval == Gdk.KEY_Down):
336 #print self.channel_name + " Down"
337 self.slider_adjustment.step_down()
342 def serialize(self, object_backend):
343 object_backend.add_property("volume", "%f" % self.slider_adjustment.get_value_db())
344 object_backend.add_property("balance", "%f" % self.balance_adjustment.get_value())
346 if hasattr(self.channel, 'out_mute'):
347 object_backend.add_property('out_mute', str(self.channel.out_mute))
348 if self.channel.volume_midi_cc != -1:
349 object_backend.add_property('volume_midi_cc', str(self.channel.volume_midi_cc))
350 if self.channel.balance_midi_cc != -1:
351 object_backend.add_property('balance_midi_cc', str(self.channel.balance_midi_cc))
352 if self.channel.mute_midi_cc != -1:
353 object_backend.add_property('mute_midi_cc', str(self.channel.mute_midi_cc))
354 if self.channel.solo_midi_cc != -1:
355 object_backend.add_property('solo_midi_cc', str(self.channel.solo_midi_cc))
358 def unserialize_property(self, name, value):
360 self.slider_adjustment.set_value_db(float(value))
362 if name == "balance":
363 self.balance_adjustment.set_value(float(value))
365 if name == 'out_mute':
366 self.future_out_mute = (value == 'True')
368 if name == 'volume_midi_cc':
369 self.future_volume_midi_cc = int(value)
371 if name == 'balance_midi_cc':
372 self.future_balance_midi_cc = int(value)
374 if name == 'mute_midi_cc':
375 self.future_mute_midi_cc = int(value)
377 if name == 'solo_midi_cc':
378 self.future_solo_midi_cc = int(value)
382 def on_midi_event_received(self, *args):
383 self.slider_adjustment.set_value_db(self.channel.volume, from_midi = True)
384 self.balance_adjustment.set_value(self.channel.balance)
386 def on_monitor_button_toggled(self, button):
387 if button.get_active():
388 for channel in self.app.channels + self.app.output_channels:
389 if channel.monitor_button.get_active() and channel.monitor_button is not button:
390 channel.monitor_button.handler_block_by_func(
391 channel.on_monitor_button_toggled)
392 channel.monitor_button.set_active(False)
393 channel.monitor_button.handler_unblock_by_func(
394 channel.on_monitor_button_toggled)
395 self.app.set_monitored_channel(self)
397 def set_monitored(self):
399 self.app.set_monitored_channel(self)
400 self.monitor_button.set_active(True)
402 def set_color(self, color):
404 set_background_color(self.label_name_event_box, self.channel.name.replace(" ", "") + 'label', self.color.to_string())
406 class InputChannel(Channel):
407 post_fader_output_channel = None
409 def __init__(self, app, name, stereo):
410 Channel.__init__(self, app, name, stereo)
413 self.channel = self.mixer.add_channel(self.channel_name, self.stereo)
415 if self.channel == None:
416 raise Exception("Cannot create a channel")
417 Channel.realize(self)
418 if self.future_volume_midi_cc != None:
419 self.channel.volume_midi_cc = self.future_volume_midi_cc
420 if self.future_balance_midi_cc != None:
421 self.channel.balance_midi_cc = self.future_balance_midi_cc
422 if self.future_mute_midi_cc != None:
423 self.channel.mute_midi_cc = self.future_mute_midi_cc
424 if self.future_solo_midi_cc != None:
425 self.channel.solo_midi_cc = self.future_solo_midi_cc
426 if self.app._init_solo_channels and self.channel_name in self.app._init_solo_channels:
427 self.channel.solo = True
429 self.channel.midi_scale = self.slider_scale.scale
431 self.on_volume_changed(self.slider_adjustment)
432 self.on_balance_changed(self.balance_adjustment)
434 # vbox child at upper part
435 self.vbox = Gtk.VBox()
436 self.pack_start(self.vbox, False, True, 0)
437 self.label_name = Gtk.Label()
438 self.label_name.set_text(self.channel_name)
439 self.label_name.set_width_chars(0)
440 self.label_name_event_box = Gtk.EventBox()
441 self.label_name_event_box.connect("button-press-event", self.on_label_mouse)
442 self.label_name_event_box.add(self.label_name)
443 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
444 # self.label_stereo = Gtk.Label()
446 # self.label_stereo.set_text("stereo")
448 # self.label_stereo.set_text("mono")
449 # self.label_stereo.set_size_request(0, -1)
450 # self.vbox.pack_start(self.label_stereo, True)
452 self.hbox_mutesolo = Gtk.HBox()
453 vbox_mutesolo = Gtk.VBox()
454 vbox_mutesolo.pack_start(self.hbox_mutesolo, True, True, button_padding)
455 self.vbox.pack_start(vbox_mutesolo, True, True, 0)
457 self.mute = Gtk.ToggleButton()
458 self.mute.set_label("M")
459 self.mute.set_name("mute")
460 self.mute.set_active(self.channel.out_mute)
461 self.mute.connect("toggled", self.on_mute_toggled)
462 self.hbox_mutesolo.pack_start(self.mute, True, True, button_padding)
464 self.solo = Gtk.ToggleButton()
465 self.solo.set_label("S")
466 self.solo.set_name("solo")
467 self.solo.set_active(self.channel.solo)
468 self.solo.connect("toggled", self.on_solo_toggled)
469 self.hbox_mutesolo.pack_start(self.solo, True, True, button_padding)
471 self.vbox.pack_start(self.hbox_mutesolo, True, True, 0)
474 frame.set_shadow_type(Gtk.ShadowType.IN)
475 frame.add(self.abspeak);
476 self.pack_start(frame, False, True, 0)
478 # hbox child at lower part
479 self.hbox = Gtk.HBox()
480 self.hbox.pack_start(self.slider, True, True, 0)
482 frame.set_shadow_type(Gtk.ShadowType.IN)
483 frame.add(self.meter);
484 self.hbox.pack_start(frame, True, True, 0)
486 frame.set_shadow_type(Gtk.ShadowType.IN)
487 frame.add(self.hbox);
488 self.pack_start(frame, True, True, 0)
490 self.volume_digits.set_width_chars(6)
491 self.pack_start(self.volume_digits, False, False, 0)
493 self.create_balance_widget()
495 self.monitor_button = Gtk.ToggleButton('MON')
496 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
497 self.pack_start(self.monitor_button, False, False, 0)
499 def add_control_group(self, channel):
500 control_group = ControlGroup(channel, self)
501 control_group.show_all()
502 self.vbox.pack_start(control_group, True, True, 0)
505 def remove_control_group(self, channel):
506 ctlgroup = self.get_control_group(channel)
507 self.vbox.remove(ctlgroup)
509 def update_control_group(self, channel):
510 for control_group in self.vbox.get_children():
511 if isinstance(control_group, ControlGroup):
512 if control_group.output_channel is channel:
513 control_group.update()
515 def get_control_group(self, channel):
516 for control_group in self.vbox.get_children():
517 if isinstance(control_group, ControlGroup):
518 if control_group.output_channel is channel:
523 Channel.unrealize(self)
524 if self.post_fader_output_channel:
525 self.post_fader_output_channel.remove()
526 self.post_fader_output_channel = None
527 self.channel.remove()
530 channel_properties_dialog = None
532 def on_channel_properties(self):
533 if not self.channel_properties_dialog:
534 self.channel_properties_dialog = ChannelPropertiesDialog(self, self.app)
535 self.channel_properties_dialog.show()
536 self.channel_properties_dialog.present()
538 def on_label_mouse(self, widget, event):
539 if event.type == Gdk.EventType._2BUTTON_PRESS:
540 if event.button == 1:
541 self.on_channel_properties()
543 def on_mute_toggled(self, button):
544 self.channel.out_mute = self.mute.get_active()
546 def on_solo_toggled(self, button):
547 self.channel.solo = self.solo.get_active()
549 def midi_events_check(self):
550 if hasattr(self, 'channel') and self.channel.midi_in_got_events:
551 self.mute.set_active(self.channel.out_mute)
552 self.solo.set_active(self.channel.solo)
553 Channel.on_midi_event_received(self)
555 def on_solo_button_pressed(self, button, event, *args):
556 if event.button == 3:
557 # right click on the solo button, act on all output channels
558 if button.get_active(): # was soloed
559 button.set_active(False)
560 if hasattr(button, 'touched_channels'):
561 touched_channels = button.touched_channels
562 for chan in touched_channels:
563 ctlgroup = self.get_control_group(chan)
564 ctlgroup.solo.set_active(False)
565 del button.touched_channels
566 else: # was not soloed
567 button.set_active(True)
568 touched_channels = []
569 for chan in self.app.output_channels:
570 ctlgroup = self.get_control_group(chan)
571 if not ctlgroup.solo.get_active():
572 ctlgroup.solo.set_active(True)
573 touched_channels.append(chan)
574 button.touched_channels = touched_channels
579 def serialization_name(cls):
580 return 'input_channel'
582 def serialize(self, object_backend):
583 object_backend.add_property("name", self.channel_name)
585 object_backend.add_property("type", "stereo")
587 object_backend.add_property("type", "mono")
588 Channel.serialize(self, object_backend)
590 def unserialize_property(self, name, value):
592 self.channel_name = str(value)
595 if value == "stereo":
601 return Channel.unserialize_property(self, name, value)
603 class OutputChannel(Channel):
604 _display_solo_buttons = False
606 _init_muted_channels = None
607 _init_solo_channels = None
609 def __init__(self, app, name, stereo):
610 Channel.__init__(self, app, name, stereo)
612 def get_display_solo_buttons(self):
613 return self._display_solo_buttons
615 def set_display_solo_buttons(self, value):
616 self._display_solo_buttons = value
617 # notifying control groups
618 for inputchannel in self.app.channels:
619 inputchannel.update_control_group(self)
621 display_solo_buttons = property(get_display_solo_buttons, set_display_solo_buttons)
624 self.channel = self.mixer.add_output_channel(self.channel_name, self.stereo)
625 if self.channel == None:
626 raise Exception("Cannot create a channel")
627 Channel.realize(self)
628 if self.future_volume_midi_cc != None:
629 self.channel.volume_midi_cc = self.future_volume_midi_cc
630 if self.future_balance_midi_cc != None:
631 self.channel.balance_midi_cc = self.future_balance_midi_cc
632 if self.future_mute_midi_cc != None:
633 self.channel.mute_midi_cc = self.future_mute_midi_cc
634 self.channel.midi_scale = self.slider_scale.scale
636 self.on_volume_changed(self.slider_adjustment)
637 self.on_balance_changed(self.balance_adjustment)
639 # vbox child at upper part
640 self.vbox = Gtk.VBox()
641 self.pack_start(self.vbox, False, True, 0)
642 self.label_name = Gtk.Label()
643 self.label_name.set_text(self.channel_name)
644 self.label_name.set_width_chars(0)
645 self.label_name_event_box = Gtk.EventBox()
646 self.label_name_event_box.connect('button-press-event', self.on_label_mouse)
647 self.label_name_event_box.add(self.label_name)
648 if not hasattr(self, 'color'):
649 self.color = random_color()
650 set_background_color(self.label_name_event_box, self.channel.name + 'label',
651 self.color.to_string())
652 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
653 self.mute = Gtk.ToggleButton()
654 self.mute.set_label("M")
655 self.mute.set_name("mute")
656 self.mute.set_active(self.channel.out_mute)
657 self.mute.connect("toggled", self.on_mute_toggled)
659 hbox.pack_start(self.mute, True, True, button_padding)
660 self.vbox.pack_start(hbox, True, True, button_padding)
663 frame.set_shadow_type(Gtk.ShadowType.IN)
664 frame.add(self.abspeak);
665 self.vbox.pack_start(frame, False, True, 0)
667 # hbox child at lower part
668 self.hbox = Gtk.HBox()
669 self.hbox.pack_start(self.slider, True, True, 0)
671 frame.set_shadow_type(Gtk.ShadowType.IN)
672 frame.add(self.meter);
673 self.hbox.pack_start(frame, True, True, 0)
675 frame.set_shadow_type(Gtk.ShadowType.IN)
676 frame.add(self.hbox);
677 self.pack_start(frame, True, True, 0)
679 self.volume_digits.set_width_chars(6)
680 self.pack_start(self.volume_digits, False, True, 0)
682 self.create_balance_widget()
684 self.monitor_button = Gtk.ToggleButton('MON')
685 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
686 self.pack_start(self.monitor_button, False, False, 0)
688 # add control groups to the input channels, and initialize them
690 for input_channel in self.app.channels:
691 ctlgroup = input_channel.add_control_group(self)
692 if self._init_muted_channels and input_channel.channel.name in self._init_muted_channels:
693 ctlgroup.mute.set_active(True)
694 if self._init_solo_channels and input_channel.channel.name in self._init_solo_channels:
695 ctlgroup.solo.set_active(True)
696 self._init_muted_channels = None
697 self._init_solo_channels = None
699 channel_properties_dialog = None
700 def on_channel_properties(self):
701 if not self.channel_properties_dialog:
702 self.channel_properties_dialog = OutputChannelPropertiesDialog(self, self.app)
703 self.channel_properties_dialog.show()
704 self.channel_properties_dialog.present()
706 def on_label_mouse(self, widget, event):
707 if event.type == Gdk.EventType._2BUTTON_PRESS:
708 if event.button == 1:
709 self.on_channel_properties()
711 def on_mute_toggled(self, button):
712 self.channel.out_mute = self.mute.get_active()
714 def midi_events_check(self):
715 if self.channel != None and self.channel.midi_in_got_events:
716 self.mute.set_active(self.channel.out_mute)
717 Channel.on_midi_event_received(self)
720 # remove control groups from input channels
721 for input_channel in self.app.channels:
722 input_channel.remove_control_group(self)
724 Channel.unrealize(self)
725 self.channel.remove()
729 def serialization_name(cls):
730 return 'output_channel'
732 def serialize(self, object_backend):
733 object_backend.add_property("name", self.channel_name)
735 object_backend.add_property("type", "stereo")
737 object_backend.add_property("type", "mono")
738 if self.display_solo_buttons:
739 object_backend.add_property("solo_buttons", "true")
742 for input_channel in self.app.channels:
743 if self.channel.is_muted(input_channel.channel):
744 muted_channels.append(input_channel)
745 if self.channel.is_solo(input_channel.channel):
746 solo_channels.append(input_channel)
748 object_backend.add_property('muted_channels', '|'.join([x.channel.name for x in muted_channels]))
750 object_backend.add_property('solo_channels', '|'.join([x.channel.name for x in solo_channels]))
751 object_backend.add_property("color", self.color.to_string())
752 Channel.serialize(self, object_backend)
754 def unserialize_property(self, name, value):
756 self.channel_name = str(value)
759 if value == "stereo":
765 if name == "solo_buttons":
767 self.display_solo_buttons = True
769 if name == 'muted_channels':
770 self._init_muted_channels = value.split('|')
772 if name == 'solo_channels':
773 self._init_solo_channels = value.split('|')
780 return Channel.unserialize_property(self, name, value)
782 class ChannelPropertiesDialog(Gtk.Dialog):
785 def __init__(self, parent, app):
786 self.channel = parent
788 self.mixer = self.channel.mixer
789 Gtk.Dialog.__init__(self, 'Channel "%s" Properties' % self.channel.channel_name, app.window)
791 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
792 self.ok_button = self.add_button(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY)
793 self.set_default_response(Gtk.ResponseType.APPLY);
798 self.connect('response', self.on_response_cb)
799 self.connect('delete-event', self.on_response_cb)
801 def create_frame(self, label, child):
804 frame.set_border_width(3)
805 #frame.set_shadow_type(Gtk.ShadowType.NONE)
806 frame.get_label_widget().set_markup('<b>%s</b>' % label)
808 alignment = Gtk.Alignment.new(0, 0, 1, 1)
809 alignment.set_padding(0, 0, 12, 0)
819 self.properties_table = table = Gtk.Table(3, 3, False)
820 vbox.pack_start(self.create_frame('Properties', table), True, True, 0)
821 table.set_row_spacings(5)
822 table.set_col_spacings(5)
824 table.attach(Gtk.Label(label='Name'), 0, 1, 0, 1)
825 self.entry_name = Gtk.Entry()
826 self.entry_name.set_activates_default(True)
827 self.entry_name.connect('changed', self.on_entry_name_changed)
828 table.attach(self.entry_name, 1, 2, 0, 1)
830 table.attach(Gtk.Label(label='Mode'), 0, 1, 1, 2)
831 self.mode_hbox = Gtk.HBox()
832 table.attach(self.mode_hbox, 1, 2, 1, 2)
833 self.mono = Gtk.RadioButton(label='Mono')
834 self.stereo = Gtk.RadioButton(label='Stereo', group=self.mono)
835 self.mode_hbox.pack_start(self.mono, True, True, 0)
836 self.mode_hbox.pack_start(self.stereo, True, True, 0)
838 table = Gtk.Table(2, 3, False)
839 vbox.pack_start(self.create_frame('MIDI Control Channels', table), True, True, 0)
840 table.set_row_spacings(5)
841 table.set_col_spacings(5)
843 cc_tooltip = "{} MIDI Control Change number (0-127, set to -1 to assign next free CC #)"
844 table.attach(Gtk.Label(label='Volume'), 0, 1, 0, 1)
845 self.entry_volume_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
846 self.entry_volume_cc.set_tooltip_text(cc_tooltip.format("Volume"))
847 table.attach(self.entry_volume_cc, 1, 2, 0, 1)
848 self.button_sense_midi_volume = Gtk.Button('Learn')
849 self.button_sense_midi_volume.connect('clicked',
850 self.on_sense_midi_volume_clicked)
851 table.attach(self.button_sense_midi_volume, 2, 3, 0, 1)
853 table.attach(Gtk.Label(label='Balance'), 0, 1, 1, 2)
854 self.entry_balance_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
855 self.entry_balance_cc.set_tooltip_text(cc_tooltip.format("Balance"))
856 table.attach(self.entry_balance_cc, 1, 2, 1, 2)
857 self.button_sense_midi_balance = Gtk.Button('Learn')
858 self.button_sense_midi_balance.connect('clicked',
859 self.on_sense_midi_balance_clicked)
860 table.attach(self.button_sense_midi_balance, 2, 3, 1, 2)
862 table.attach(Gtk.Label(label='Mute'), 0, 1, 2, 3)
863 self.entry_mute_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
864 self.entry_mute_cc.set_tooltip_text(cc_tooltip.format("Mute"))
865 table.attach(self.entry_mute_cc, 1, 2, 2, 3)
866 self.button_sense_midi_mute = Gtk.Button('Learn')
867 self.button_sense_midi_mute.connect('clicked',
868 self.on_sense_midi_mute_clicked)
869 table.attach(self.button_sense_midi_mute, 2, 3, 2, 3)
871 if (isinstance(self, NewChannelDialog) or (self.channel and
872 isinstance(self.channel, InputChannel))):
873 table.attach(Gtk.Label(label='Solo'), 0, 1, 3, 4)
874 self.entry_solo_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
875 self.entry_solo_cc.set_tooltip_text(cc_tooltip.format("Solo"))
876 table.attach(self.entry_solo_cc, 1, 2, 3, 4)
877 self.button_sense_midi_solo = Gtk.Button('Learn')
878 self.button_sense_midi_solo.connect('clicked',
879 self.on_sense_midi_solo_clicked)
880 table.attach(self.button_sense_midi_solo, 2, 3, 3, 4)
885 self.entry_name.set_text(self.channel.channel_name)
886 if self.channel.channel.is_stereo:
887 self.stereo.set_active(True)
889 self.mono.set_active(True)
890 self.mode_hbox.set_sensitive(False)
891 self.entry_volume_cc.set_value(self.channel.channel.volume_midi_cc)
892 self.entry_balance_cc.set_value(self.channel.channel.balance_midi_cc)
893 self.entry_mute_cc.set_value(self.channel.channel.mute_midi_cc)
894 if (self.channel and isinstance(self.channel, InputChannel)):
895 self.entry_solo_cc.set_value(self.channel.channel.solo_midi_cc)
897 def sense_popup_dialog(self, entry):
898 window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
899 window.set_destroy_with_parent(True)
900 window.set_transient_for(self)
901 window.set_decorated(False)
902 window.set_modal(True)
903 window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
904 window.set_border_width(10)
909 vbox.pack_start(Gtk.Label(label='Please move the MIDI control you want to use for this function.'), True, True, 0)
910 timeout_label = Gtk.Label(label='This window will close in 5 seconds')
911 vbox.pack_start(timeout_label, True, True, 0)
912 def close_sense_timeout(window, entry):
914 timeout_label.set_text('This window will close in %d seconds.' % window.timeout)
915 if window.timeout == 0:
917 entry.set_value(self.mixer.last_midi_channel)
921 GObject.timeout_add_seconds(1, close_sense_timeout, window, entry)
923 def on_sense_midi_volume_clicked(self, *args):
924 self.mixer.last_midi_channel = int(self.entry_volume_cc.get_value())
925 self.sense_popup_dialog(self.entry_volume_cc)
927 def on_sense_midi_balance_clicked(self, *args):
928 self.mixer.last_midi_channel = int(self.entry_balance_cc.get_value())
929 self.sense_popup_dialog(self.entry_balance_cc)
931 def on_sense_midi_mute_clicked(self, *args):
932 self.mixer.last_midi_channel = int(self.entry_mute_cc.get_value())
933 self.sense_popup_dialog(self.entry_mute_cc)
935 def on_sense_midi_solo_clicked(self, *args):
936 self.mixer.last_midi_channel = int(self.entry_solo_cc.get_value())
937 self.sense_popup_dialog(self.entry_solo_cc)
939 def on_response_cb(self, dlg, response_id, *args):
940 self.channel.channel_properties_dialog = None
941 name = self.entry_name.get_text()
942 if response_id == Gtk.ResponseType.APPLY:
943 if name != self.channel.channel_name:
944 self.channel.channel_name = name
945 for control in ('volume', 'balance', 'mute', 'solo'):
946 widget = getattr(self, 'entry_{}_cc'.format(control), None)
947 if widget is not None:
948 value = int(widget.get_value())
950 setattr(self.channel.channel, '{}_midi_cc'.format(control), value)
953 def on_entry_name_changed(self, entry):
955 if len(entry.get_text()):
956 if self.channel and self.channel.channel.name == entry.get_text():
958 elif entry.get_text() not in [x.channel.name for x in self.app.channels] + \
959 [x.channel.name for x in self.app.output_channels] + ['MAIN']:
961 self.ok_button.set_sensitive(sensitive)
964 class NewChannelDialog(ChannelPropertiesDialog):
965 def __init__(self, app):
966 Gtk.Dialog.__init__(self, 'New Channel', app.window)
967 self.mixer = app.mixer
972 self.stereo.set_active(True) # default to stereo
974 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
975 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
976 self.ok_button.set_sensitive(False)
977 self.set_default_response(Gtk.ResponseType.OK);
980 self.entry_volume_cc.set_value(-1)
981 self.entry_balance_cc.set_value(-1)
982 self.entry_mute_cc.set_value(-1)
983 self.entry_solo_cc.set_value(-1)
985 def get_result(self):
986 return {'name': self.entry_name.get_text(),
987 'stereo': self.stereo.get_active(),
988 'volume_cc': int(self.entry_volume_cc.get_value()),
989 'balance_cc': int(self.entry_balance_cc.get_value()),
990 'mute_cc': int(self.entry_mute_cc.get_value()),
991 'solo_cc': int(self.entry_solo_cc.get_value())
994 class OutputChannelPropertiesDialog(ChannelPropertiesDialog):
996 ChannelPropertiesDialog.create_ui(self)
998 table = self.properties_table
999 table.attach(Gtk.Label(label='Color'), 0, 1, 2, 3)
1000 self.color_chooser_button = Gtk.ColorButton()
1001 table.attach(self.color_chooser_button, 1, 2, 2, 3)
1005 self.vbox.pack_start(self.create_frame('Input Channels', vbox), True, True, 0)
1007 self.display_solo_buttons = Gtk.CheckButton('Display solo buttons')
1008 vbox.pack_start(self.display_solo_buttons, True, True, 0)
1010 self.vbox.show_all()
1013 ChannelPropertiesDialog.fill_ui(self)
1014 self.display_solo_buttons.set_active(self.channel.display_solo_buttons)
1015 self.color_chooser_button.set_rgba(self.channel.color)
1017 def on_response_cb(self, dlg, response_id, *args):
1018 ChannelPropertiesDialog.on_response_cb(self, dlg, response_id, *args)
1019 if response_id == Gtk.ResponseType.APPLY:
1020 self.channel.display_solo_buttons = self.display_solo_buttons.get_active()
1021 self.channel.set_color(self.color_chooser_button.get_rgba())
1022 for inputchannel in self.app.channels:
1023 inputchannel.update_control_group(self.channel)
1027 class NewOutputChannelDialog(OutputChannelPropertiesDialog):
1028 def __init__(self, app):
1029 Gtk.Dialog.__init__(self, 'New Output Channel', app.window)
1030 self.mixer = app.mixer
1035 # TODO: disable mode for output channels as mono output channels may
1036 # not be correctly handled yet.
1037 self.mode_hbox.set_sensitive(False)
1038 self.stereo.set_active(True) # default to stereo
1040 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1041 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1042 self.ok_button.set_sensitive(False)
1043 self.set_default_response(Gtk.ResponseType.OK);
1046 self.entry_volume_cc.set_value(-1)
1047 self.entry_balance_cc.set_value(-1)
1048 self.entry_mute_cc.set_value(-1)
1050 def get_result(self):
1051 return {'name': self.entry_name.get_text(),
1052 'stereo': self.stereo.get_active(),
1053 'volume_cc': int(self.entry_volume_cc.get_value()),
1054 'balance_cc': int(self.entry_balance_cc.get_value()),
1055 'mute_cc': int(self.entry_mute_cc.get_value()),
1056 'display_solo_buttons': self.display_solo_buttons.get_active(),
1057 'color': self.color_chooser_button.get_rgba()
1060 class ControlGroup(Gtk.Alignment):
1061 def __init__(self, output_channel, input_channel):
1062 GObject.GObject.__init__(self)
1063 self.set(0.5, 0.5, 1, 1)
1064 self.output_channel = output_channel
1065 self.input_channel = input_channel
1066 self.app = input_channel.app
1069 self.vbox = Gtk.VBox()
1072 set_background_color(self.vbox, output_channel.channel.name,
1073 output_channel.color.to_string())
1076 self.vbox.pack_start(hbox, True, True, button_padding)
1077 css = b""" .control_group {
1078 min-width: 0px; padding: 0px;} """
1080 css_provider = Gtk.CssProvider()
1081 css_provider.load_from_data(css)
1082 context = Gtk.StyleContext()
1083 screen = Gdk.Screen.get_default()
1084 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
1086 self.label = Gtk.Label(output_channel.channel.name)
1087 label_context = self.label.get_style_context()
1088 label_context.add_class('control_group')
1090 self.hbox.pack_start(self.label, False, False, button_padding)
1091 mute = Gtk.ToggleButton()
1093 mute.set_name("mute")
1094 mute.connect("toggled", self.on_mute_toggled)
1096 solo = Gtk.ToggleButton()
1097 solo.set_name("solo")
1099 solo.connect("toggled", self.on_solo_toggled)
1102 if self.output_channel.display_solo_buttons:
1103 hbox.pack_end(solo, False, False, button_padding)
1104 hbox.pack_end(mute, False, False, button_padding)
1107 if self.output_channel.display_solo_buttons:
1108 if not self.solo in self.hbox.get_children():
1109 self.hbox.pack_end(self.solo, False, False, button_padding)
1110 self.hbox.reorder_child(self.mute, -1)
1113 if self.solo in self.hbox.get_children():
1114 self.hbox.remove(self.solo)
1116 self.label.set_text(self.output_channel.channel.name)
1117 set_background_color(self.vbox, self.output_channel.channel.name.replace(" ", ""), self.output_channel.color.to_string())
1120 def on_mute_toggled(self, button):
1121 self.output_channel.channel.set_muted(self.input_channel.channel, button.get_active())
1122 self.app.update_monitor(self)
1124 def on_solo_toggled(self, button):
1125 self.output_channel.channel.set_solo(self.input_channel.channel, button.get_active())
1126 self.app.update_monitor(self)