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 .top_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, value = None):
72 Gtk.VBox.__init__(self)
74 self.mixer = app.mixer
75 self.gui_factory = app.gui_factory
76 self._channel_name = name
78 self.initial_value = value
79 self.meter_scale = self.gui_factory.get_default_meter_scale()
80 self.slider_scale = self.gui_factory.get_default_slider_scale()
81 self.slider_adjustment = slider.AdjustmentdBFS(self.slider_scale, 0.0, 0.02)
82 self.balance_adjustment = slider.BalanceAdjustment()
83 self.future_out_mute = None
84 self.future_volume_midi_cc = None
85 self.future_balance_midi_cc = None
86 self.future_mute_midi_cc = None
87 self.future_solo_midi_cc = None
88 self.css_name = "css_name_%d" % Channel.num_instances
89 Channel.num_instances += 1
91 def get_channel_name(self):
92 return self._channel_name
96 post_fader_output_channel = None
97 def set_channel_name(self, name):
98 self.app.on_channel_rename(self._channel_name, name);
99 self._channel_name = name
101 self.label_name.set_text(name)
103 self.channel.name = name
104 if self.post_fader_output_channel:
105 self.post_fader_output_channel.name = "%s Out" % name;
106 channel_name = property(get_channel_name, set_channel_name)
109 #print "Realizing channel \"%s\"" % self.channel_name
110 if self.future_out_mute != None:
111 self.channel.out_mute = self.future_out_mute
113 self.slider_adjustment.connect("volume-changed", self.on_volume_changed)
114 self.balance_adjustment.connect("balance-changed", self.on_balance_changed)
117 self.create_slider_widget()
120 self.meter = meter.StereoMeterWidget(self.meter_scale)
122 self.meter = meter.MonoMeterWidget(self.meter_scale)
123 self.on_vumeter_color_changed(self.gui_factory)
125 self.meter.set_events(Gdk.EventMask.SCROLL_MASK)
127 self.gui_factory.connect("default-meter-scale-changed", self.on_default_meter_scale_changed)
128 self.gui_factory.connect("default-slider-scale-changed", self.on_default_slider_scale_changed)
129 self.gui_factory.connect('vumeter-color-changed', self.on_vumeter_color_changed)
130 self.gui_factory.connect('vumeter-color-scheme-changed', self.on_vumeter_color_changed)
131 self.gui_factory.connect('use-custom-widgets-changed', self.on_custom_widgets_changed)
133 self.abspeak = abspeak.AbspeakWidget()
134 self.abspeak.connect("reset", self.on_abspeak_reset)
135 self.abspeak.connect("volume-adjust", self.on_abspeak_adjust)
137 self.volume_digits = Gtk.Entry()
138 self.volume_digits.set_property('xalign', 0.5)
139 self.volume_digits.connect("key-press-event", self.on_volume_digits_key_pressed)
140 self.volume_digits.connect("focus-out-event", self.on_volume_digits_focus_out)
142 if self.initial_value != None:
143 if self.initial_value == True:
144 self.slider_adjustment.set_value(0)
146 self.slider_adjustment.set_value_db(0)
148 self.connect("key-press-event", self.on_key_pressed)
149 self.connect("scroll-event", self.on_scroll)
152 #print "Unrealizing channel \"%s\"" % self.channel_name
155 def balance_preferred_width(self):
158 def _preferred_height(self):
161 def create_balance_widget(self):
162 if self.gui_factory.use_custom_widgets and phat:
163 self.balance = phat.HFanSlider()
164 self.balance.set_default_value(0)
165 self.balance.set_adjustment(self.balance_adjustment)
167 self.balance = Gtk.Scale()
168 self.balance.get_preferred_width = self.balance_preferred_width
169 self.balance.get_preferred_height = self._preferred_height
170 self.balance.set_orientation(Gtk.Orientation.HORIZONTAL)
171 self.balance.set_adjustment(self.balance_adjustment)
172 self.balance.set_has_origin(False)
173 self.balance.set_draw_value(False)
174 self.balance.button_down = False
175 self.balance.connect('button-press-event', self.on_balance_button_press_event)
176 self.balance.connect('button-release-event', self.on_balance_button_release_event)
177 self.balance.connect("motion-notify-event", self.on_balance_motion_notify_event)
178 self.balance.connect("scroll-event", self.on_balance_scroll_event)
181 self.pack_start(self.balance, False, True, 0)
182 if self.monitor_button:
183 self.reorder_child(self.monitor_button, -1)
186 def on_balance_button_press_event(self, widget, event):
187 if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
188 self.balance.button_down = True
189 self.balance.button_down_x = event.x
190 self.balance.button_down_value = self.balance.get_value()
192 if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
193 self.balance_adjustment.set_balance(0)
197 def on_balance_button_release_event(self, widget, event):
198 self.balance.button_down = False
201 def on_balance_motion_notify_event(self, widget, event):
202 slider_length = widget.get_allocation().width - widget.get_style_context().get_property('min-width', Gtk.StateFlags.NORMAL)
203 if self.balance.button_down:
204 delta_x = (event.x - self.balance.button_down_x) / slider_length
205 x = self.balance.button_down_value + 2 * delta_x
210 self.balance_adjustment.set_balance(x)
213 def on_balance_scroll_event(self, widget, event):
215 delta = bal.get_adjustment().get_step_increment()
216 value = bal.get_value()
217 if event.direction == Gdk.ScrollDirection.UP:
219 elif event.direction == Gdk.ScrollDirection.DOWN:
221 elif event.direction == Gdk.ScrollDirection.SMOOTH:
222 x = value - event.delta_y * delta
231 def create_slider_widget(self):
234 parent = self.slider.get_parent()
235 self.slider.destroy()
236 if self.gui_factory.use_custom_widgets:
237 self.slider = slider.CustomSliderWidget(self.slider_adjustment)
239 self.slider = slider.GtkSlider(self.slider_adjustment)
241 parent.pack_start(self.slider, True, True, 0)
242 parent.reorder_child(self.slider, 0)
245 def on_default_meter_scale_changed(self, gui_factory, scale):
246 #print "Default meter scale change detected."
247 self.meter.set_scale(scale)
249 def on_default_slider_scale_changed(self, gui_factory, scale):
250 #print "Default slider scale change detected."
251 self.slider_scale = scale
252 self.slider_adjustment.set_scale(scale)
254 self.channel.midi_scale = self.slider_scale.scale
256 def on_vumeter_color_changed(self, gui_factory, *args):
257 color = gui_factory.get_vumeter_color()
258 color_scheme = gui_factory.get_vumeter_color_scheme()
259 if color_scheme != 'solid':
260 self.meter.set_color(None)
262 self.meter.set_color(Gdk.color_parse(color))
264 def on_custom_widgets_changed(self, gui_factory, value):
265 self.balance.destroy()
266 self.create_balance_widget()
267 self.create_slider_widget()
269 def on_abspeak_adjust(self, abspeak, adjust):
270 #print "abspeak adjust %f" % adjust
271 self.slider_adjustment.set_value_db(self.slider_adjustment.get_value_db() + adjust)
272 self.channel.abspeak = None
273 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
275 def on_abspeak_reset(self, abspeak):
276 #print "abspeak reset"
277 self.channel.abspeak = None
279 def on_volume_digits_key_pressed(self, widget, event):
280 if (event.keyval == Gdk.KEY_Return or event.keyval == Gdk.KEY_KP_Enter):
281 db_text = self.volume_digits.get_text()
284 #print "Volume digits confirmation \"%f dBFS\"" % db
285 except (ValueError) as e:
286 #print "Volume digits confirmation ignore, reset to current"
287 self.update_volume(False)
289 self.slider_adjustment.set_value_db(db)
291 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
293 def on_volume_digits_focus_out(self, widget, event):
294 #print "volume digits focus out detected"
295 self.update_volume(False)
297 def read_meter(self):
301 meter_left, meter_right = self.channel.meter
302 self.meter.set_values(meter_left, meter_right)
304 self.meter.set_value(self.channel.meter[0])
306 self.abspeak.set_peak(self.channel.abspeak)
308 def on_scroll(self, widget, event):
309 if event.direction == Gdk.ScrollDirection.DOWN:
310 self.slider_adjustment.step_down()
311 elif event.direction == Gdk.ScrollDirection.UP:
312 self.slider_adjustment.step_up()
315 def update_volume(self, update_engine, from_midi = False):
316 db = self.slider_adjustment.get_value_db()
318 db_text = "%.2f" % db
319 self.volume_digits.set_text(db_text)
323 self.channel.volume = db
325 self.channel.set_volume_from_midi(db)
326 self.app.update_monitor(self)
328 def on_volume_changed(self, adjustment):
329 self.update_volume(True)
331 def on_volume_changed_from_midi(self, adjustment):
332 self.update_volume(True, from_midi = True)
334 def on_balance_changed(self, adjustment):
335 balance = self.balance_adjustment.get_value()
336 #print("%s balance: %f" % (self.channel_name, balance))
337 self.channel.balance = balance
338 self.app.update_monitor(self)
340 def on_volume_changed_from_midi(self, adjustment):
341 balance = self.balance_adjustment.get_value()
342 #print("%s balance from midi: %f" % (self.channel_name, balance))
343 self.channel.set_balance_from_midi(balance)
344 self.app.update_monitor(self)
346 def on_key_pressed(self, widget, event):
347 if (event.keyval == Gdk.KEY_Up):
348 #print self.channel_name + " Up"
349 self.slider_adjustment.step_up()
351 elif (event.keyval == Gdk.KEY_Down):
352 #print self.channel_name + " Down"
353 self.slider_adjustment.step_down()
358 def serialize(self, object_backend):
359 object_backend.add_property("volume", "%f" % self.slider_adjustment.get_value_db())
360 object_backend.add_property("balance", "%f" % self.balance_adjustment.get_value())
362 if hasattr(self.channel, 'out_mute'):
363 object_backend.add_property('out_mute', str(self.channel.out_mute))
364 if self.channel.volume_midi_cc != -1:
365 object_backend.add_property('volume_midi_cc', str(self.channel.volume_midi_cc))
366 if self.channel.balance_midi_cc != -1:
367 object_backend.add_property('balance_midi_cc', str(self.channel.balance_midi_cc))
368 if self.channel.mute_midi_cc != -1:
369 object_backend.add_property('mute_midi_cc', str(self.channel.mute_midi_cc))
370 if self.channel.solo_midi_cc != -1:
371 object_backend.add_property('solo_midi_cc', str(self.channel.solo_midi_cc))
374 def unserialize_property(self, name, value):
376 self.slider_adjustment.set_value_db(float(value))
378 if name == "balance":
379 self.balance_adjustment.set_value(float(value))
381 if name == 'out_mute':
382 self.future_out_mute = (value == 'True')
384 if name == 'volume_midi_cc':
385 self.future_volume_midi_cc = int(value)
387 if name == 'balance_midi_cc':
388 self.future_balance_midi_cc = int(value)
390 if name == 'mute_midi_cc':
391 self.future_mute_midi_cc = int(value)
393 if name == 'solo_midi_cc':
394 self.future_solo_midi_cc = int(value)
398 def on_midi_event_received(self, *args):
399 self.slider_adjustment.set_value_db(self.channel.volume, from_midi = True)
400 self.balance_adjustment.set_balance(self.channel.balance, from_midi = True)
402 def on_monitor_button_toggled(self, button):
403 if button.get_active():
404 for channel in self.app.channels + self.app.output_channels:
405 if channel.monitor_button.get_active() and channel.monitor_button is not button:
406 channel.monitor_button.handler_block_by_func(
407 channel.on_monitor_button_toggled)
408 channel.monitor_button.set_active(False)
409 channel.monitor_button.handler_unblock_by_func(
410 channel.on_monitor_button_toggled)
411 self.app.set_monitored_channel(self)
413 def set_monitored(self):
415 self.app.set_monitored_channel(self)
416 self.monitor_button.set_active(True)
418 def set_color(self, color):
420 set_background_color(self.label_name_event_box, self.css_name, self.color.to_string())
422 class InputChannel(Channel):
423 post_fader_output_channel = None
425 def __init__(self, app, name, stereo, value = None):
426 Channel.__init__(self, app, name, stereo, value)
429 self.channel = self.mixer.add_channel(self.channel_name, self.stereo)
431 if self.channel == None:
432 raise Exception("Cannot create a channel")
433 Channel.realize(self)
434 if self.future_volume_midi_cc != None:
435 self.channel.volume_midi_cc = self.future_volume_midi_cc
436 if self.future_balance_midi_cc != None:
437 self.channel.balance_midi_cc = self.future_balance_midi_cc
438 if self.future_mute_midi_cc != None:
439 self.channel.mute_midi_cc = self.future_mute_midi_cc
440 if self.future_solo_midi_cc != None:
441 self.channel.solo_midi_cc = self.future_solo_midi_cc
442 if self.app._init_solo_channels and self.channel_name in self.app._init_solo_channels:
443 self.channel.solo = True
445 self.channel.midi_scale = self.slider_scale.scale
447 self.on_volume_changed(self.slider_adjustment)
448 self.on_balance_changed(self.balance_adjustment)
450 # vbox child at upper part
451 self.vbox = Gtk.VBox()
452 self.pack_start(self.vbox, False, True, 0)
453 self.label_name = Gtk.Label()
454 self.label_name.get_style_context().add_class('top_label')
455 self.label_name.set_text(self.channel_name)
456 self.label_name.set_width_chars(0)
457 self.label_name_event_box = Gtk.EventBox()
458 self.label_name_event_box.connect("button-press-event", self.on_label_mouse)
459 self.label_name_event_box.add(self.label_name)
460 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
461 # self.label_stereo = Gtk.Label()
463 # self.label_stereo.set_text("stereo")
465 # self.label_stereo.set_text("mono")
466 # self.label_stereo.set_size_request(0, -1)
467 # self.vbox.pack_start(self.label_stereo, True)
469 self.hbox_mutesolo = Gtk.Box(False, button_padding)
471 self.vbox.pack_start(hbox, True, True, button_padding)
473 label = Gtk.Label('All')
474 hbox.pack_start(label, False, False, button_padding)
475 hbox.pack_end(self.hbox_mutesolo, False, False, button_padding)
477 self.mute = Gtk.ToggleButton()
478 self.mute.set_label("M")
479 self.mute.set_name("mute")
480 self.mute.set_active(self.channel.out_mute)
481 self.mute.connect("toggled", self.on_mute_toggled)
482 self.hbox_mutesolo.pack_start(self.mute, True, True, button_padding)
484 self.solo = Gtk.ToggleButton()
485 self.solo.set_label("S")
486 self.solo.set_name("solo")
487 self.solo.set_active(self.channel.solo)
488 self.solo.connect("toggled", self.on_solo_toggled)
489 self.hbox_mutesolo.pack_start(self.solo, True, True, button_padding)
491 self.vbox.pack_start(self.hbox_mutesolo, True, True, 0)
494 frame.set_shadow_type(Gtk.ShadowType.IN)
495 frame.add(self.abspeak);
496 self.pack_start(frame, False, True, 0)
498 # hbox child at lower part
499 self.hbox = Gtk.HBox()
500 self.hbox.pack_start(self.slider, True, True, 0)
502 frame.set_shadow_type(Gtk.ShadowType.IN)
503 frame.add(self.meter);
504 self.hbox.pack_start(frame, True, True, 0)
506 frame.set_shadow_type(Gtk.ShadowType.IN)
507 frame.add(self.hbox);
508 self.pack_start(frame, True, True, 0)
510 self.volume_digits.set_width_chars(6)
511 self.pack_start(self.volume_digits, False, False, 0)
513 self.create_balance_widget()
515 self.monitor_button = Gtk.ToggleButton('MON')
516 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
517 self.pack_start(self.monitor_button, False, False, 0)
519 def add_control_group(self, channel):
520 control_group = ControlGroup(channel, self)
521 control_group.show_all()
522 self.vbox.pack_start(control_group, True, True, 0)
525 def remove_control_group(self, channel):
526 ctlgroup = self.get_control_group(channel)
527 self.vbox.remove(ctlgroup)
529 def update_control_group(self, channel):
530 for control_group in self.vbox.get_children():
531 if isinstance(control_group, ControlGroup):
532 if control_group.output_channel is channel:
533 control_group.update()
535 def get_control_group(self, channel):
536 for control_group in self.vbox.get_children():
537 if isinstance(control_group, ControlGroup):
538 if control_group.output_channel is channel:
543 Channel.unrealize(self)
544 if self.post_fader_output_channel:
545 self.post_fader_output_channel.remove()
546 self.post_fader_output_channel = None
547 self.channel.remove()
550 channel_properties_dialog = None
552 def on_channel_properties(self):
553 if not self.channel_properties_dialog:
554 self.channel_properties_dialog = ChannelPropertiesDialog(self, self.app)
555 self.channel_properties_dialog.show()
556 self.channel_properties_dialog.present()
558 def on_label_mouse(self, widget, event):
559 if event.type == Gdk.EventType._2BUTTON_PRESS:
560 if event.button == 1:
561 self.on_channel_properties()
563 def on_mute_toggled(self, button):
564 self.channel.out_mute = self.mute.get_active()
566 def on_solo_toggled(self, button):
567 self.channel.solo = self.solo.get_active()
569 def midi_events_check(self):
570 if hasattr(self, 'channel') and self.channel.midi_in_got_events:
571 self.mute.set_active(self.channel.out_mute)
572 self.solo.set_active(self.channel.solo)
573 Channel.on_midi_event_received(self)
575 def on_solo_button_pressed(self, button, event, *args):
576 if event.button == 3:
577 # right click on the solo button, act on all output channels
578 if button.get_active(): # was soloed
579 button.set_active(False)
580 if hasattr(button, 'touched_channels'):
581 touched_channels = button.touched_channels
582 for chan in touched_channels:
583 ctlgroup = self.get_control_group(chan)
584 ctlgroup.solo.set_active(False)
585 del button.touched_channels
586 else: # was not soloed
587 button.set_active(True)
588 touched_channels = []
589 for chan in self.app.output_channels:
590 ctlgroup = self.get_control_group(chan)
591 if not ctlgroup.solo.get_active():
592 ctlgroup.solo.set_active(True)
593 touched_channels.append(chan)
594 button.touched_channels = touched_channels
599 def serialization_name(cls):
600 return 'input_channel'
602 def serialize(self, object_backend):
603 object_backend.add_property("name", self.channel_name)
605 object_backend.add_property("type", "stereo")
607 object_backend.add_property("type", "mono")
608 Channel.serialize(self, object_backend)
610 def unserialize_property(self, name, value):
612 self.channel_name = str(value)
615 if value == "stereo":
621 return Channel.unserialize_property(self, name, value)
623 class OutputChannel(Channel):
624 _display_solo_buttons = False
626 _init_muted_channels = None
627 _init_solo_channels = None
629 def __init__(self, app, name, stereo, value = None):
630 Channel.__init__(self, app, name, stereo, value)
632 def get_display_solo_buttons(self):
633 return self._display_solo_buttons
635 def set_display_solo_buttons(self, value):
636 self._display_solo_buttons = value
637 # notifying control groups
638 for inputchannel in self.app.channels:
639 inputchannel.update_control_group(self)
641 display_solo_buttons = property(get_display_solo_buttons, set_display_solo_buttons)
644 self.channel = self.mixer.add_output_channel(self.channel_name, self.stereo)
645 if self.channel == None:
646 raise Exception("Cannot create a channel")
647 Channel.realize(self)
648 if self.future_volume_midi_cc != None:
649 self.channel.volume_midi_cc = self.future_volume_midi_cc
650 if self.future_balance_midi_cc != None:
651 self.channel.balance_midi_cc = self.future_balance_midi_cc
652 if self.future_mute_midi_cc != None:
653 self.channel.mute_midi_cc = self.future_mute_midi_cc
654 self.channel.midi_scale = self.slider_scale.scale
656 self.on_volume_changed(self.slider_adjustment)
657 self.on_balance_changed(self.balance_adjustment)
659 # vbox child at upper part
660 self.vbox = Gtk.VBox()
661 self.pack_start(self.vbox, False, True, 0)
662 self.label_name = Gtk.Label()
663 self.label_name.get_style_context().add_class('top_label')
664 self.label_name.set_text(self.channel_name)
665 self.label_name.set_width_chars(0)
666 self.label_name_event_box = Gtk.EventBox()
667 self.label_name_event_box.connect('button-press-event', self.on_label_mouse)
668 self.label_name_event_box.add(self.label_name)
669 if not hasattr(self, 'color'):
670 self.color = random_color()
671 set_background_color(self.label_name_event_box, self.css_name,
672 self.color.to_string())
673 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
674 self.mute = Gtk.ToggleButton()
675 self.mute.set_label("M")
676 self.mute.set_name("mute")
677 self.mute.set_active(self.channel.out_mute)
678 self.mute.connect("toggled", self.on_mute_toggled)
680 hbox.pack_start(self.mute, True, True, button_padding)
681 self.vbox.pack_start(hbox, True, True, button_padding)
684 frame.set_shadow_type(Gtk.ShadowType.IN)
685 frame.add(self.abspeak);
686 self.vbox.pack_start(frame, False, True, 0)
688 # hbox child at lower part
689 self.hbox = Gtk.HBox()
690 self.hbox.pack_start(self.slider, True, True, 0)
692 frame.set_shadow_type(Gtk.ShadowType.IN)
693 frame.add(self.meter);
694 self.hbox.pack_start(frame, True, True, 0)
696 frame.set_shadow_type(Gtk.ShadowType.IN)
697 frame.add(self.hbox);
698 self.pack_start(frame, True, True, 0)
700 self.volume_digits.set_width_chars(6)
701 self.pack_start(self.volume_digits, False, True, 0)
703 self.create_balance_widget()
705 self.monitor_button = Gtk.ToggleButton('MON')
706 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
707 self.pack_start(self.monitor_button, False, False, 0)
709 # add control groups to the input channels, and initialize them
711 for input_channel in self.app.channels:
712 ctlgroup = input_channel.add_control_group(self)
713 if self._init_muted_channels and input_channel.channel.name in self._init_muted_channels:
714 ctlgroup.mute.set_active(True)
715 if self._init_solo_channels and input_channel.channel.name in self._init_solo_channels:
716 ctlgroup.solo.set_active(True)
717 self._init_muted_channels = None
718 self._init_solo_channels = None
720 channel_properties_dialog = None
721 def on_channel_properties(self):
722 if not self.channel_properties_dialog:
723 self.channel_properties_dialog = OutputChannelPropertiesDialog(self, self.app)
724 self.channel_properties_dialog.show()
725 self.channel_properties_dialog.present()
727 def on_label_mouse(self, widget, event):
728 if event.type == Gdk.EventType._2BUTTON_PRESS:
729 if event.button == 1:
730 self.on_channel_properties()
732 def on_mute_toggled(self, button):
733 self.channel.out_mute = self.mute.get_active()
735 def midi_events_check(self):
736 if self.channel != None and self.channel.midi_in_got_events:
737 self.mute.set_active(self.channel.out_mute)
738 Channel.on_midi_event_received(self)
741 # remove control groups from input channels
742 for input_channel in self.app.channels:
743 input_channel.remove_control_group(self)
745 Channel.unrealize(self)
746 self.channel.remove()
750 def serialization_name(cls):
751 return 'output_channel'
753 def serialize(self, object_backend):
754 object_backend.add_property("name", self.channel_name)
756 object_backend.add_property("type", "stereo")
758 object_backend.add_property("type", "mono")
759 if self.display_solo_buttons:
760 object_backend.add_property("solo_buttons", "true")
763 for input_channel in self.app.channels:
764 if self.channel.is_muted(input_channel.channel):
765 muted_channels.append(input_channel)
766 if self.channel.is_solo(input_channel.channel):
767 solo_channels.append(input_channel)
769 object_backend.add_property('muted_channels', '|'.join([x.channel.name for x in muted_channels]))
771 object_backend.add_property('solo_channels', '|'.join([x.channel.name for x in solo_channels]))
772 object_backend.add_property("color", self.color.to_string())
773 Channel.serialize(self, object_backend)
775 def unserialize_property(self, name, value):
777 self.channel_name = str(value)
780 if value == "stereo":
786 if name == "solo_buttons":
788 self.display_solo_buttons = True
790 if name == 'muted_channels':
791 self._init_muted_channels = value.split('|')
793 if name == 'solo_channels':
794 self._init_solo_channels = value.split('|')
801 return Channel.unserialize_property(self, name, value)
803 class ChannelPropertiesDialog(Gtk.Dialog):
806 def __init__(self, parent, app):
807 self.channel = parent
809 self.mixer = self.channel.mixer
810 Gtk.Dialog.__init__(self, 'Channel "%s" Properties' % self.channel.channel_name, app.window)
812 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
813 self.ok_button = self.add_button(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY)
814 self.set_default_response(Gtk.ResponseType.APPLY);
819 self.connect('response', self.on_response_cb)
820 self.connect('delete-event', self.on_response_cb)
822 def create_frame(self, label, child):
825 frame.set_border_width(3)
826 #frame.set_shadow_type(Gtk.ShadowType.NONE)
827 frame.get_label_widget().set_markup('<b>%s</b>' % label)
829 alignment = Gtk.Alignment.new(0, 0, 1, 1)
830 alignment.set_padding(0, 0, 12, 0)
840 self.properties_table = table = Gtk.Table(4, 3, False)
841 vbox.pack_start(self.create_frame('Properties', table), True, True, 0)
842 table.set_row_spacings(5)
843 table.set_col_spacings(5)
845 table.attach(Gtk.Label(label='Name'), 0, 1, 0, 1)
846 self.entry_name = Gtk.Entry()
847 self.entry_name.set_activates_default(True)
848 self.entry_name.connect('changed', self.on_entry_name_changed)
849 table.attach(self.entry_name, 1, 2, 0, 1)
851 table.attach(Gtk.Label(label='Mode'), 0, 1, 1, 2)
852 self.mode_hbox = Gtk.HBox()
853 table.attach(self.mode_hbox, 1, 2, 1, 2)
854 self.mono = Gtk.RadioButton(label='Mono')
855 self.stereo = Gtk.RadioButton(label='Stereo', group=self.mono)
856 self.mode_hbox.pack_start(self.mono, True, True, 0)
857 self.mode_hbox.pack_start(self.stereo, True, True, 0)
859 table = Gtk.Table(2, 3, False)
860 vbox.pack_start(self.create_frame('MIDI Control Channels', table), True, True, 0)
861 table.set_row_spacings(5)
862 table.set_col_spacings(5)
864 cc_tooltip = "{} MIDI Control Change number (0-127, set to -1 to assign next free CC #)"
865 table.attach(Gtk.Label(label='Volume'), 0, 1, 0, 1)
866 self.entry_volume_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
867 self.entry_volume_cc.set_tooltip_text(cc_tooltip.format("Volume"))
868 table.attach(self.entry_volume_cc, 1, 2, 0, 1)
869 self.button_sense_midi_volume = Gtk.Button('Learn')
870 self.button_sense_midi_volume.connect('clicked',
871 self.on_sense_midi_volume_clicked)
872 table.attach(self.button_sense_midi_volume, 2, 3, 0, 1)
874 table.attach(Gtk.Label(label='Balance'), 0, 1, 1, 2)
875 self.entry_balance_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
876 self.entry_balance_cc.set_tooltip_text(cc_tooltip.format("Balance"))
877 table.attach(self.entry_balance_cc, 1, 2, 1, 2)
878 self.button_sense_midi_balance = Gtk.Button('Learn')
879 self.button_sense_midi_balance.connect('clicked',
880 self.on_sense_midi_balance_clicked)
881 table.attach(self.button_sense_midi_balance, 2, 3, 1, 2)
883 table.attach(Gtk.Label(label='Mute'), 0, 1, 2, 3)
884 self.entry_mute_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
885 self.entry_mute_cc.set_tooltip_text(cc_tooltip.format("Mute"))
886 table.attach(self.entry_mute_cc, 1, 2, 2, 3)
887 self.button_sense_midi_mute = Gtk.Button('Learn')
888 self.button_sense_midi_mute.connect('clicked',
889 self.on_sense_midi_mute_clicked)
890 table.attach(self.button_sense_midi_mute, 2, 3, 2, 3)
892 if (isinstance(self, NewChannelDialog) or (self.channel and
893 isinstance(self.channel, InputChannel))):
894 table.attach(Gtk.Label(label='Solo'), 0, 1, 3, 4)
895 self.entry_solo_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
896 self.entry_solo_cc.set_tooltip_text(cc_tooltip.format("Solo"))
897 table.attach(self.entry_solo_cc, 1, 2, 3, 4)
898 self.button_sense_midi_solo = Gtk.Button('Learn')
899 self.button_sense_midi_solo.connect('clicked',
900 self.on_sense_midi_solo_clicked)
901 table.attach(self.button_sense_midi_solo, 2, 3, 3, 4)
906 self.entry_name.set_text(self.channel.channel_name)
907 if self.channel.channel.is_stereo:
908 self.stereo.set_active(True)
910 self.mono.set_active(True)
911 self.mode_hbox.set_sensitive(False)
912 self.entry_volume_cc.set_value(self.channel.channel.volume_midi_cc)
913 self.entry_balance_cc.set_value(self.channel.channel.balance_midi_cc)
914 self.entry_mute_cc.set_value(self.channel.channel.mute_midi_cc)
915 if (self.channel and isinstance(self.channel, InputChannel)):
916 self.entry_solo_cc.set_value(self.channel.channel.solo_midi_cc)
918 def sense_popup_dialog(self, entry):
919 window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
920 window.set_destroy_with_parent(True)
921 window.set_transient_for(self)
922 window.set_decorated(False)
923 window.set_modal(True)
924 window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
925 window.set_border_width(10)
930 vbox.pack_start(Gtk.Label(label='Please move the MIDI control you want to use for this function.'), True, True, 0)
931 timeout_label = Gtk.Label(label='This window will close in 5 seconds')
932 vbox.pack_start(timeout_label, True, True, 0)
933 def close_sense_timeout(window, entry):
935 timeout_label.set_text('This window will close in %d seconds.' % window.timeout)
936 if window.timeout == 0:
938 entry.set_value(self.mixer.last_midi_channel)
942 GObject.timeout_add_seconds(1, close_sense_timeout, window, entry)
944 def on_sense_midi_volume_clicked(self, *args):
945 self.mixer.last_midi_channel = int(self.entry_volume_cc.get_value())
946 self.sense_popup_dialog(self.entry_volume_cc)
948 def on_sense_midi_balance_clicked(self, *args):
949 self.mixer.last_midi_channel = int(self.entry_balance_cc.get_value())
950 self.sense_popup_dialog(self.entry_balance_cc)
952 def on_sense_midi_mute_clicked(self, *args):
953 self.mixer.last_midi_channel = int(self.entry_mute_cc.get_value())
954 self.sense_popup_dialog(self.entry_mute_cc)
956 def on_sense_midi_solo_clicked(self, *args):
957 self.mixer.last_midi_channel = int(self.entry_solo_cc.get_value())
958 self.sense_popup_dialog(self.entry_solo_cc)
960 def on_response_cb(self, dlg, response_id, *args):
961 self.channel.channel_properties_dialog = None
962 name = self.entry_name.get_text()
963 if response_id == Gtk.ResponseType.APPLY:
964 if name != self.channel.channel_name:
965 self.channel.channel_name = name
966 for control in ('volume', 'balance', 'mute', 'solo'):
967 widget = getattr(self, 'entry_{}_cc'.format(control), None)
968 if widget is not None:
969 value = int(widget.get_value())
971 setattr(self.channel.channel, '{}_midi_cc'.format(control), value)
974 def on_entry_name_changed(self, entry):
976 if len(entry.get_text()):
977 if self.channel and self.channel.channel.name == entry.get_text():
979 elif entry.get_text() not in [x.channel.name for x in self.app.channels] + \
980 [x.channel.name for x in self.app.output_channels] + ['MAIN']:
982 self.ok_button.set_sensitive(sensitive)
985 class NewChannelDialog(ChannelPropertiesDialog):
987 ChannelPropertiesDialog.create_ui(self)
988 self.properties_table.attach(Gtk.Label(label='Value'), 0, 1, 2, 3)
990 self.value_hbox = Gtk.HBox()
991 self.properties_table.attach(self.value_hbox, 1, 2, 2, 3)
992 self.minus_inf = Gtk.RadioButton(label='-Inf')
993 self.zero_dB = Gtk.RadioButton(label='0dB', group=self.minus_inf)
994 self.value_hbox.pack_start(self.minus_inf, True, True, 0)
995 self.value_hbox.pack_start(self.zero_dB, True, True, 0)
999 class NewInputChannelDialog(NewChannelDialog):
1000 def __init__(self, app):
1001 Gtk.Dialog.__init__(self, 'New Input Channel', app.window)
1002 self.mixer = app.mixer
1007 self.stereo.set_active(True) # default to stereo
1009 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1010 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1011 self.ok_button.set_sensitive(False)
1012 self.set_default_response(Gtk.ResponseType.OK);
1015 self.entry_volume_cc.set_value(-1)
1016 self.entry_balance_cc.set_value(-1)
1017 self.entry_mute_cc.set_value(-1)
1018 self.entry_solo_cc.set_value(-1)
1020 def get_result(self):
1021 print('minus_inf active?', self.zero_dB.get_active())
1022 return {'name': self.entry_name.get_text(),
1023 'stereo': self.stereo.get_active(),
1024 'volume_cc': int(self.entry_volume_cc.get_value()),
1025 'balance_cc': int(self.entry_balance_cc.get_value()),
1026 'mute_cc': int(self.entry_mute_cc.get_value()),
1027 'solo_cc': int(self.entry_solo_cc.get_value()),
1028 'value': self.minus_inf.get_active()
1031 class OutputChannelPropertiesDialog(ChannelPropertiesDialog):
1032 def create_ui(self):
1034 NewChannelDialog.create_ui(self)
1036 table = self.properties_table
1037 table.attach(Gtk.Label(label='Color'), 0, 1, 4, 5)
1038 self.color_chooser_button = Gtk.ColorButton()
1039 table.attach(self.color_chooser_button, 1, 2, 4, 5)
1042 self.vbox.pack_start(self.create_frame('Input Channels', vbox), True, True, 0)
1044 self.display_solo_buttons = Gtk.CheckButton('Display solo buttons')
1045 vbox.pack_start(self.display_solo_buttons, True, True, 0)
1047 self.vbox.show_all()
1050 ChannelPropertiesDialog.fill_ui(self)
1051 self.display_solo_buttons.set_active(self.channel.display_solo_buttons)
1052 self.color_chooser_button.set_rgba(self.channel.color)
1054 def on_response_cb(self, dlg, response_id, *args):
1055 ChannelPropertiesDialog.on_response_cb(self, dlg, response_id, *args)
1056 if response_id == Gtk.ResponseType.APPLY:
1057 self.channel.display_solo_buttons = self.display_solo_buttons.get_active()
1058 self.channel.set_color(self.color_chooser_button.get_rgba())
1059 for inputchannel in self.app.channels:
1060 inputchannel.update_control_group(self.channel)
1064 class NewOutputChannelDialog(NewChannelDialog, OutputChannelPropertiesDialog):
1065 def __init__(self, app):
1066 Gtk.Dialog.__init__(self, 'New Output Channel', app.window)
1067 self.mixer = app.mixer
1069 OutputChannelPropertiesDialog.create_ui(self)
1072 # TODO: disable mode for output channels as mono output channels may
1073 # not be correctly handled yet.
1074 self.mode_hbox.set_sensitive(False)
1075 self.stereo.set_active(True) # default to stereo
1077 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1078 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1079 self.ok_button.set_sensitive(False)
1080 self.set_default_response(Gtk.ResponseType.OK);
1083 self.entry_volume_cc.set_value(-1)
1084 self.entry_balance_cc.set_value(-1)
1085 self.entry_mute_cc.set_value(-1)
1087 def get_result(self):
1088 return {'name': self.entry_name.get_text(),
1089 'stereo': self.stereo.get_active(),
1090 'volume_cc': int(self.entry_volume_cc.get_value()),
1091 'balance_cc': int(self.entry_balance_cc.get_value()),
1092 'mute_cc': int(self.entry_mute_cc.get_value()),
1093 'display_solo_buttons': self.display_solo_buttons.get_active(),
1094 'color': self.color_chooser_button.get_rgba(),
1095 'value': self.minus_inf.get_active()
1098 class ControlGroup(Gtk.Alignment):
1099 def __init__(self, output_channel, input_channel):
1100 GObject.GObject.__init__(self)
1101 self.set(0.5, 0.5, 1, 1)
1102 self.output_channel = output_channel
1103 self.input_channel = input_channel
1104 self.app = input_channel.app
1106 self.hbox = Gtk.HBox()
1107 self.vbox = Gtk.VBox()
1109 self.buttons_box = Gtk.Box(False, button_padding)
1111 set_background_color(self.vbox, output_channel.css_name,
1112 output_channel.color.to_string())
1114 self.vbox.pack_start(self.hbox, True, True, button_padding)
1115 css = b""" .control_group {
1116 min-width: 0px; padding: 0px;} """
1118 css_provider = Gtk.CssProvider()
1119 css_provider.load_from_data(css)
1120 context = Gtk.StyleContext()
1121 screen = Gdk.Screen.get_default()
1122 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
1124 self.label = Gtk.Label(output_channel.channel.name)
1125 label_context = self.label.get_style_context()
1126 label_context.add_class('control_group')
1127 self.hbox.pack_start(self.label, False, False, button_padding)
1128 self.hbox.pack_end(self.buttons_box, False, False, button_padding)
1129 mute = Gtk.ToggleButton()
1131 mute.set_name("mute")
1132 mute.connect("toggled", self.on_mute_toggled)
1134 solo = Gtk.ToggleButton()
1135 solo.set_name("solo")
1137 solo.connect("toggled", self.on_solo_toggled)
1140 if self.output_channel.display_solo_buttons:
1141 self.buttons_box.pack_end(solo, True, True, button_padding)
1142 self.buttons_box.pack_end(mute, True, True, button_padding)
1145 if self.output_channel.display_solo_buttons:
1146 if not self.solo in self.buttons_box.get_children():
1147 self.buttons_box.pack_end(self.solo, True, True, button_padding)
1148 self.buttons_box.reorder_child(self.mute, -1)
1151 if self.solo in self.hbox.get_children():
1152 self.hbox.remove(self.solo)
1154 self.label.set_text(self.output_channel.channel.name)
1155 set_background_color(self.vbox, self.output_channel.css_name, self.output_channel.color.to_string())
1158 def on_mute_toggled(self, button):
1159 self.output_channel.channel.set_muted(self.input_channel.channel, button.get_active())
1160 self.app.update_monitor(self)
1162 def on_solo_toggled(self, button):
1163 self.output_channel.channel.set_solo(self.input_channel.channel, button.get_active())
1164 self.app.update_monitor(self)