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.
21 from gi.repository import Gtk
22 from gi.repository import Gdk
23 from gi.repository import GObject
24 from gi.repository import Pango
29 from serialization import SerializedObject
37 log = logging.getLogger(__name__)
57 css_provider = Gtk.CssProvider()
58 css_provider.load_from_data(css)
59 context = Gtk.StyleContext()
60 screen = Gdk.Screen.get_default()
61 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
64 def set_background_color(widget, name, color_string):
69 """ % (name, color_string)
71 css_provider = Gtk.CssProvider()
72 css_provider.load_from_data(css.encode('utf-8'))
73 context = Gtk.StyleContext()
74 screen = Gdk.Screen.get_default()
75 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
77 widget_context = widget.get_style_context()
78 widget_context.add_class(name)
82 from random import uniform, seed
84 return Gdk.RGBA(uniform(0, 1), uniform(0, 1), uniform(0, 1), 1)
87 class Channel(Gtk.VBox, SerializedObject):
88 '''Widget with slider and meter used as base class for more specific
92 def __init__(self, app, name, stereo, value = None):
93 Gtk.VBox.__init__(self)
95 self.mixer = app.mixer
97 self.gui_factory = app.gui_factory
98 self._channel_name = name
100 self.initial_value = value
101 self.meter_scale = self.gui_factory.get_default_meter_scale()
102 self.slider_scale = self.gui_factory.get_default_slider_scale()
103 self.slider_adjustment = slider.AdjustmentdBFS(self.slider_scale, 0.0, 0.02)
104 self.balance_adjustment = slider.BalanceAdjustment()
105 self.post_fader_output_channel = None
106 self.future_out_mute = None
107 self.future_volume_midi_cc = None
108 self.future_balance_midi_cc = None
109 self.future_mute_midi_cc = None
110 self.future_solo_midi_cc = None
111 self.css_name = "css_name_%d" % Channel.num_instances
112 self.label_name = None
114 self.label_chars_wide = 12
115 self.label_chars_narrow = 8
116 Channel.num_instances += 1
118 def get_channel_name(self):
119 return self._channel_name
121 def set_channel_name(self, name):
122 self.app.on_channel_rename(self._channel_name, name);
123 self._channel_name = name
125 self.label_name.set_text(name)
126 if len(name) > (self.label_chars_wide if self.wide else self.label_chars_narrow):
127 self.label_name.set_tooltip_text(name)
129 self.channel.name = name
130 if self.post_fader_output_channel:
131 self.post_fader_output_channel.name = "%s Out" % name;
132 channel_name = property(get_channel_name, set_channel_name)
135 log.debug('Realizing channel "%s".', self.channel_name)
136 if self.future_out_mute != None:
137 self.channel.out_mute = self.future_out_mute
140 # Channel strip label
141 self.vbox = Gtk.VBox()
142 self.pack_start(self.vbox, False, True, 0)
143 self.label_name = Gtk.Label()
144 self.label_name.get_style_context().add_class('top_label')
145 self.label_name.set_text(self.channel_name)
146 self.label_name.set_max_width_chars(self.label_chars_wide if self.wide else
147 self.label_chars_narrow)
148 self.label_name.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
149 self.label_name_event_box = Gtk.EventBox()
150 self.label_name_event_box.connect('button-press-event', self.on_label_mouse)
151 self.label_name_event_box.add(self.label_name)
155 self.create_slider_widget()
158 self.volume_digits = Gtk.Entry()
159 self.volume_digits.set_property('xalign', 0.5)
160 self.volume_digits.connect("key-press-event", self.on_volume_digits_key_pressed)
161 self.volume_digits.connect("focus-out-event", self.on_volume_digits_focus_out)
164 self.abspeak = abspeak.AbspeakWidget()
165 self.abspeak.connect("reset", self.on_abspeak_reset)
166 self.abspeak.connect("volume-adjust", self.on_abspeak_adjust)
170 self.meter = meter.StereoMeterWidget(self.meter_scale)
172 self.meter = meter.MonoMeterWidget(self.meter_scale)
174 self.meter.set_events(Gdk.EventMask.SCROLL_MASK)
175 self.on_vumeter_color_changed(self.gui_factory)
177 if self.initial_value != None:
178 if self.initial_value == True:
179 self.slider_adjustment.set_value(0)
181 self.slider_adjustment.set_value_db(0)
183 self.slider_adjustment.connect("volume-changed", self.on_volume_changed)
184 self.slider_adjustment.connect("volume-changed-from-midi", self.on_volume_changed_from_midi)
185 self.balance_adjustment.connect("balance-changed", self.on_balance_changed)
187 self.gui_factory.connect("default-meter-scale-changed", self.on_default_meter_scale_changed)
188 self.gui_factory.connect("default-slider-scale-changed", self.on_default_slider_scale_changed)
189 self.gui_factory.connect('vumeter-color-changed', self.on_vumeter_color_changed)
190 self.gui_factory.connect('vumeter-color-scheme-changed', self.on_vumeter_color_changed)
191 self.gui_factory.connect('use-custom-widgets-changed', self.on_custom_widgets_changed)
193 self.connect("key-press-event", self.on_key_pressed)
194 self.connect("scroll-event", self.on_scroll)
197 log.debug('Unrealizing channel "%s".', self.channel_name)
202 ctx = self.label_name.get_style_context()
203 ctx.remove_class('wide')
204 ctx.add_class('narrow')
205 label = self.label_name.get_label()
206 self.label_name.set_max_width_chars(self.label_chars_narrow)
208 if len(label) > self.label_chars_narrow:
209 self.label_name.set_tooltip_text(label)
213 self.label_name.set_tooltip_text(None)
214 ctx = self.label_name.get_style_context()
215 ctx.remove_class('narrow')
216 ctx.add_class('wide')
217 label = self.label_name.get_label()
218 self.label_name.set_max_width_chars(self.label_chars_wide)
220 if len(label) > self.label_chars_wide:
221 self.label_name.set_tooltip_text(label)
223 def balance_preferred_width(self):
226 def _preferred_height(self):
229 def create_balance_widget(self):
230 if self.gui_factory.use_custom_widgets and phat:
231 self.balance = phat.HFanSlider()
232 self.balance.set_default_value(0)
233 self.balance.set_adjustment(self.balance_adjustment)
235 self.balance = Gtk.Scale()
236 self.balance.get_preferred_width = self.balance_preferred_width
237 self.balance.get_preferred_height = self._preferred_height
238 self.balance.set_orientation(Gtk.Orientation.HORIZONTAL)
239 self.balance.set_adjustment(self.balance_adjustment)
240 self.balance.set_has_origin(False)
241 self.balance.set_draw_value(False)
242 self.balance.button_down = False
243 self.balance.connect('button-press-event', self.on_balance_button_press_event)
244 self.balance.connect('button-release-event', self.on_balance_button_release_event)
245 self.balance.connect("motion-notify-event", self.on_balance_motion_notify_event)
246 self.balance.connect("scroll-event", self.on_balance_scroll_event)
249 self.pack_start(self.balance, False, True, 0)
250 if self.monitor_button:
251 self.reorder_child(self.monitor_button, -1)
254 def on_label_mouse(self, widget, event):
255 if event.type == Gdk.EventType._2BUTTON_PRESS:
256 if event.button == 1:
257 self.on_channel_properties()
259 elif (event.state & Gdk.ModifierType.CONTROL_MASK and
260 event.type == Gdk.EventType.BUTTON_PRESS and
268 def on_balance_button_press_event(self, widget, event):
269 if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
270 self.balance.button_down = True
271 self.balance.button_down_x = event.x
272 self.balance.button_down_value = self.balance.get_value()
274 if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
275 self.balance_adjustment.set_balance(0)
279 def on_balance_button_release_event(self, widget, event):
280 self.balance.button_down = False
283 def on_balance_motion_notify_event(self, widget, event):
284 slider_length = widget.get_allocation().width - widget.get_style_context().get_property('min-width', Gtk.StateFlags.NORMAL)
285 if self.balance.button_down:
286 delta_x = (event.x - self.balance.button_down_x) / slider_length
287 x = self.balance.button_down_value + 2 * delta_x
292 self.balance_adjustment.set_balance(x)
295 def on_balance_scroll_event(self, widget, event):
297 delta = bal.get_adjustment().get_step_increment()
298 value = bal.get_value()
299 if event.direction == Gdk.ScrollDirection.UP:
301 elif event.direction == Gdk.ScrollDirection.DOWN:
303 elif event.direction == Gdk.ScrollDirection.SMOOTH:
304 x = value - event.delta_y * delta
313 def create_slider_widget(self):
316 parent = self.slider.get_parent()
317 self.slider.destroy()
318 if self.gui_factory.use_custom_widgets:
319 self.slider = slider.CustomSliderWidget(self.slider_adjustment)
321 self.slider = slider.GtkSlider(self.slider_adjustment)
323 parent.pack_start(self.slider, True, True, 0)
324 parent.reorder_child(self.slider, 0)
327 def on_default_meter_scale_changed(self, gui_factory, scale):
328 log.debug("Default meter scale change detected.")
329 self.meter.set_scale(scale)
331 def on_default_slider_scale_changed(self, gui_factory, scale):
332 log.debug("Default slider scale change detected.")
333 self.slider_scale = scale
334 self.slider_adjustment.set_scale(scale)
336 self.channel.midi_scale = self.slider_scale.scale
338 def on_vumeter_color_changed(self, gui_factory, *args):
339 color = gui_factory.get_vumeter_color()
340 color_scheme = gui_factory.get_vumeter_color_scheme()
341 if color_scheme != 'solid':
342 self.meter.set_color(None)
344 self.meter.set_color(Gdk.color_parse(color))
346 def on_custom_widgets_changed(self, gui_factory, value):
347 self.balance.destroy()
348 self.create_balance_widget()
349 self.create_slider_widget()
351 def on_abspeak_adjust(self, abspeak, adjust):
352 log.debug("abspeak adjust %f", adjust)
353 self.slider_adjustment.set_value_db(self.slider_adjustment.get_value_db() + adjust)
354 self.channel.abspeak = None
355 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
357 def on_abspeak_reset(self, abspeak):
358 log.debug("abspeak reset")
359 self.channel.abspeak = None
361 def on_volume_digits_key_pressed(self, widget, event):
362 if (event.keyval == Gdk.KEY_Return or event.keyval == Gdk.KEY_KP_Enter):
363 db_text = self.volume_digits.get_text()
366 log.debug('Volume digits confirmation "%f dBFS".', db)
367 except (ValueError) as e:
368 log.debug("Volume digits confirmation ignore, reset to current.")
369 self.update_volume(False)
371 self.slider_adjustment.set_value_db(db)
373 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
375 def on_volume_digits_focus_out(self, widget, event):
376 log.debug("Volume digits focus out detected.")
377 self.update_volume(False)
379 def read_meter(self):
383 peak_left, peak_right, rms_left, rms_right = self.channel.kmeter
384 self.meter.set_values(peak_left, peak_right, rms_left, rms_right)
386 peak, rms = self.channel.kmeter
387 self.meter.set_values(peak, rms)
389 self.abspeak.set_peak(self.channel.abspeak)
391 def on_scroll(self, widget, event):
392 if event.direction == Gdk.ScrollDirection.DOWN:
393 self.slider_adjustment.step_down()
394 elif event.direction == Gdk.ScrollDirection.UP:
395 self.slider_adjustment.step_up()
398 def update_volume(self, update_engine, from_midi = False):
399 db = self.slider_adjustment.get_value_db()
401 db_text = "%.2f" % db
402 self.volume_digits.set_text(db_text)
406 self.channel.volume = db
407 self.app.update_monitor(self)
409 def on_volume_changed(self, adjustment):
410 self.update_volume(True)
412 def on_volume_changed_from_midi(self, adjustment):
413 self.update_volume(True, from_midi = True)
415 def on_balance_changed(self, adjustment):
416 balance = self.balance_adjustment.get_value()
417 log.debug("%s balance: %f", self.channel_name, balance)
418 self.channel.balance = balance
419 self.app.update_monitor(self)
421 def on_key_pressed(self, widget, event):
422 if (event.keyval == Gdk.KEY_Up):
423 log.debug(self.channel_name + " Up")
424 self.slider_adjustment.step_up()
426 elif (event.keyval == Gdk.KEY_Down):
427 log.debug(self.channel_name + " Down")
428 self.slider_adjustment.step_down()
433 def serialize(self, object_backend):
434 object_backend.add_property("volume", "%f" % self.slider_adjustment.get_value_db())
435 object_backend.add_property("balance", "%f" % self.balance_adjustment.get_value())
436 object_backend.add_property("wide", "%s" % str(self.wide))
438 if hasattr(self.channel, 'out_mute'):
439 object_backend.add_property('out_mute', str(self.channel.out_mute))
440 if self.channel.volume_midi_cc != -1:
441 object_backend.add_property('volume_midi_cc', str(self.channel.volume_midi_cc))
442 if self.channel.balance_midi_cc != -1:
443 object_backend.add_property('balance_midi_cc', str(self.channel.balance_midi_cc))
444 if self.channel.mute_midi_cc != -1:
445 object_backend.add_property('mute_midi_cc', str(self.channel.mute_midi_cc))
446 if self.channel.solo_midi_cc != -1:
447 object_backend.add_property('solo_midi_cc', str(self.channel.solo_midi_cc))
450 def unserialize_property(self, name, value):
452 self.slider_adjustment.set_value_db(float(value))
454 if name == "balance":
455 self.balance_adjustment.set_value(float(value))
457 if name == 'out_mute':
458 self.future_out_mute = (value == 'True')
460 if name == 'volume_midi_cc':
461 self.future_volume_midi_cc = int(value)
463 if name == 'balance_midi_cc':
464 self.future_balance_midi_cc = int(value)
466 if name == 'mute_midi_cc':
467 self.future_mute_midi_cc = int(value)
469 if name == 'solo_midi_cc':
470 self.future_solo_midi_cc = int(value)
473 self.wide = value == "True"
477 def on_midi_event_received(self, *args):
478 self.slider_adjustment.set_value_db(self.channel.volume, from_midi = True)
479 self.balance_adjustment.set_balance(self.channel.balance, from_midi = True)
481 def on_monitor_button_toggled(self, button):
482 if button.get_active():
483 for channel in self.app.channels + self.app.output_channels:
484 if channel.monitor_button.get_active() and channel.monitor_button is not button:
485 channel.monitor_button.handler_block_by_func(
486 channel.on_monitor_button_toggled)
487 channel.monitor_button.set_active(False)
488 channel.monitor_button.handler_unblock_by_func(
489 channel.on_monitor_button_toggled)
490 self.app.set_monitored_channel(self)
492 if self.app._monitored_channel.channel.name == self.channel.name:
493 self.monitor_button.handler_block_by_func(self.on_monitor_button_toggled)
494 self.monitor_button.set_active(True)
495 self.monitor_button.handler_unblock_by_func(self.on_monitor_button_toggled)
497 def set_monitored(self):
499 self.app.set_monitored_channel(self)
500 self.monitor_button.set_active(True)
502 def set_color(self, color):
504 set_background_color(self.label_name_event_box, self.css_name, self.color.to_string())
506 class InputChannel(Channel):
507 post_fader_output_channel = None
510 self.channel = self.mixer.add_channel(self.channel_name, self.stereo)
512 if self.channel == None:
513 raise Exception("Cannot create a channel")
517 if self.future_volume_midi_cc != None:
518 self.channel.volume_midi_cc = self.future_volume_midi_cc
519 if self.future_balance_midi_cc != None:
520 self.channel.balance_midi_cc = self.future_balance_midi_cc
521 if self.future_mute_midi_cc != None:
522 self.channel.mute_midi_cc = self.future_mute_midi_cc
523 if self.future_solo_midi_cc != None:
524 self.channel.solo_midi_cc = self.future_solo_midi_cc
525 if self.app._init_solo_channels and self.channel_name in self.app._init_solo_channels:
526 self.channel.solo = True
528 self.channel.midi_scale = self.slider_scale.scale
530 self.on_volume_changed(self.slider_adjustment)
531 self.on_balance_changed(self.balance_adjustment)
533 entries = [Gtk.TargetEntry.new("INPUT_CHANNEL", Gtk.TargetFlags.SAME_APP, 0)]
534 self.label_name_event_box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, entries,
536 self.label_name_event_box.connect("drag-data-get", self.on_drag_data_get)
537 self.drag_dest_set(Gtk.DestDefaults.ALL, entries, Gdk.DragAction.MOVE)
538 self.connect_after("drag-data-received", self.on_drag_data_received)
540 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
541 # self.label_stereo = Gtk.Label()
543 # self.label_stereo.set_text("stereo")
545 # self.label_stereo.set_text("mono")
546 # self.label_stereo.set_size_request(0, -1)
547 # self.vbox.pack_start(self.label_stereo, True)
550 frame.set_shadow_type(Gtk.ShadowType.IN)
551 frame.add(self.abspeak);
552 self.pack_start(frame, False, True, 0)
554 # hbox child at lower part
555 self.hbox = Gtk.HBox()
556 self.hbox.pack_start(self.slider, True, True, 0)
558 frame.set_shadow_type(Gtk.ShadowType.IN)
559 frame.add(self.meter);
560 self.hbox.pack_start(frame, True, True, 0)
562 frame.set_shadow_type(Gtk.ShadowType.IN)
563 frame.add(self.hbox);
564 self.pack_start(frame, True, True, 0)
566 self.volume_digits.set_width_chars(6)
567 self.pack_start(self.volume_digits, False, False, 0)
569 self.create_balance_widget()
571 self.hbox_mutesolo = Gtk.Box(False, 0)
573 self.mute = Gtk.ToggleButton()
574 self.mute.set_label("M")
575 self.mute.set_name("mute")
576 self.mute.set_active(self.channel.out_mute)
577 self.mute.connect("toggled", self.on_mute_toggled)
578 self.hbox_mutesolo.pack_start(self.mute, True, True, 0)
580 self.solo = Gtk.ToggleButton()
581 self.solo.set_label("S")
582 self.solo.set_name("solo")
583 self.solo.set_active(self.channel.solo)
584 self.solo.connect("toggled", self.on_solo_toggled)
585 self.hbox_mutesolo.pack_start(self.solo, True, True, 0)
587 self.pack_start(self.hbox_mutesolo, False, False, 0)
589 self.monitor_button = Gtk.ToggleButton('MON')
590 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
591 self.pack_start(self.monitor_button, False, False, 0)
598 for cg in self.get_control_groups():
603 for cg in self.get_control_groups():
606 def on_drag_data_get(self, widget, drag_context, data, info, time):
607 channel = widget.get_parent().get_parent()
608 data.set(data.get_target(), 8, channel._channel_name.encode('utf-8'))
610 def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
611 source_name = data.get_data().decode('utf-8')
612 if source_name == self._channel_name:
614 self.emit("input-channel-order-changed", source_name, self._channel_name)
616 def add_control_group(self, channel):
617 control_group = ControlGroup(channel, self)
618 control_group.show_all()
619 self.vbox.pack_start(control_group, True, True, 0)
622 def remove_control_group(self, channel):
623 ctlgroup = self.get_control_group(channel)
624 self.vbox.remove(ctlgroup)
626 def update_control_group(self, channel):
627 for control_group in self.vbox.get_children():
628 if isinstance(control_group, ControlGroup):
629 if control_group.output_channel is channel:
630 control_group.update()
632 def get_control_group(self, channel):
633 for control_group in self.get_control_groups:
634 if control_group.output_channel is channel:
638 def get_control_groups(self):
640 for c in self.vbox.get_children():
641 if isinstance(c, ControlGroup):
647 if self.post_fader_output_channel:
648 self.post_fader_output_channel.remove()
649 self.post_fader_output_channel = None
650 self.channel.remove()
653 channel_properties_dialog = None
655 def on_channel_properties(self):
656 if not self.channel_properties_dialog:
657 self.channel_properties_dialog = ChannelPropertiesDialog(self, self.app)
658 self.channel_properties_dialog.show()
659 self.channel_properties_dialog.present()
661 def on_mute_toggled(self, button):
662 self.channel.out_mute = self.mute.get_active()
664 def on_solo_toggled(self, button):
665 self.channel.solo = self.solo.get_active()
667 def midi_events_check(self):
668 if hasattr(self, 'channel') and self.channel.midi_in_got_events:
669 self.mute.set_active(self.channel.out_mute)
670 self.solo.set_active(self.channel.solo)
671 super().on_midi_event_received()
673 def on_solo_button_pressed(self, button, event, *args):
674 if event.button == 3:
675 # right click on the solo button, act on all output channels
676 if button.get_active(): # was soloed
677 button.set_active(False)
678 if hasattr(button, 'touched_channels'):
679 touched_channels = button.touched_channels
680 for chan in touched_channels:
681 ctlgroup = self.get_control_group(chan)
682 ctlgroup.solo.set_active(False)
683 del button.touched_channels
684 else: # was not soloed
685 button.set_active(True)
686 touched_channels = []
687 for chan in self.app.output_channels:
688 ctlgroup = self.get_control_group(chan)
689 if not ctlgroup.solo.get_active():
690 ctlgroup.solo.set_active(True)
691 touched_channels.append(chan)
692 button.touched_channels = touched_channels
697 def serialization_name(cls):
698 return 'input_channel'
700 def serialize(self, object_backend):
701 object_backend.add_property("name", self.channel_name)
703 object_backend.add_property("type", "stereo")
705 object_backend.add_property("type", "mono")
706 super().serialize(object_backend)
708 def unserialize_property(self, name, value):
710 self.channel_name = str(value)
713 if value == "stereo":
719 return super().unserialize_property(name, value)
721 GObject.signal_new("input-channel-order-changed", InputChannel,
722 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
723 None, [GObject.TYPE_STRING, GObject.TYPE_STRING])
725 class OutputChannel(Channel):
726 _display_solo_buttons = False
728 _init_muted_channels = None
729 _init_solo_channels = None
730 _init_prefader_channels = None
732 channel_properties_dialog = None
734 def get_display_solo_buttons(self):
735 return self._display_solo_buttons
737 def set_display_solo_buttons(self, value):
738 self._display_solo_buttons = value
739 # notifying control groups
740 for inputchannel in self.app.channels:
741 inputchannel.update_control_group(self)
743 display_solo_buttons = property(get_display_solo_buttons, set_display_solo_buttons)
746 self.channel = self.mixer.add_output_channel(self.channel_name, self.stereo)
748 if self.channel == None:
749 raise Exception("Cannot create a channel")
753 if self.future_volume_midi_cc != None:
754 self.channel.volume_midi_cc = self.future_volume_midi_cc
755 if self.future_balance_midi_cc != None:
756 self.channel.balance_midi_cc = self.future_balance_midi_cc
757 if self.future_mute_midi_cc != None:
758 self.channel.mute_midi_cc = self.future_mute_midi_cc
759 self.channel.midi_scale = self.slider_scale.scale
761 self.on_volume_changed(self.slider_adjustment)
762 self.on_balance_changed(self.balance_adjustment)
764 entries = [Gtk.TargetEntry.new("OUTPUT_CHANNEL", Gtk.TargetFlags.SAME_APP, 0)]
765 self.label_name_event_box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, entries,
767 self.label_name_event_box.connect("drag-data-get", self.on_drag_data_get)
768 self.drag_dest_set(Gtk.DestDefaults.ALL, entries, Gdk.DragAction.MOVE)
769 self.connect_after("drag-data-received", self.on_drag_data_received)
771 if not hasattr(self, 'color'):
772 self.color = random_color()
773 set_background_color(self.label_name_event_box, self.css_name,
774 self.color.to_string())
775 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
777 frame.set_shadow_type(Gtk.ShadowType.IN)
778 frame.add(self.abspeak);
779 self.vbox.pack_start(frame, False, True, 0)
781 # hbox child at lower part
782 self.hbox = Gtk.HBox()
783 self.hbox.pack_start(self.slider, True, True, 0)
785 frame.set_shadow_type(Gtk.ShadowType.IN)
786 frame.add(self.meter);
787 self.hbox.pack_start(frame, True, True, 0)
789 frame.set_shadow_type(Gtk.ShadowType.IN)
790 frame.add(self.hbox);
791 self.pack_start(frame, True, True, 0)
793 self.volume_digits.set_width_chars(6)
794 self.pack_start(self.volume_digits, False, True, 0)
796 self.create_balance_widget()
798 self.mute = Gtk.ToggleButton()
799 self.mute.set_label("M")
800 self.mute.set_name("mute")
801 self.mute.set_active(self.channel.out_mute)
802 self.mute.connect("toggled", self.on_mute_toggled)
805 hbox.pack_start(self.mute, True, True, 0)
806 self.pack_start(hbox, False, False, 0)
808 self.monitor_button = Gtk.ToggleButton('MON')
809 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
810 self.pack_start(self.monitor_button, False, False, 0)
812 # add control groups to the input channels, and initialize them
814 for input_channel in self.app.channels:
815 ctlgroup = input_channel.add_control_group(self)
816 if self._init_muted_channels and input_channel.channel.name in self._init_muted_channels:
817 ctlgroup.mute.set_active(True)
818 if self._init_solo_channels and input_channel.channel.name in self._init_solo_channels:
819 ctlgroup.solo.set_active(True)
820 if self._init_prefader_channels and input_channel.channel.name in self._init_prefader_channels:
821 ctlgroup.prefader.set_active(True)
822 if not input_channel.wide:
825 self._init_muted_channels = None
826 self._init_solo_channels = None
827 self._init_prefader_channels = None
832 def on_drag_data_get(self, widget, drag_context, data, info, time):
833 channel = widget.get_parent().get_parent()
834 data.set(data.get_target(), 8, channel._channel_name.encode('utf-8'))
836 def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
837 source_name = data.get_data().decode('utf-8')
838 if source_name == self._channel_name:
840 self.emit("output-channel-order-changed", source_name, self._channel_name)
842 def on_channel_properties(self):
843 if not self.channel_properties_dialog:
844 self.channel_properties_dialog = OutputChannelPropertiesDialog(self, self.app)
845 self.channel_properties_dialog.show()
846 self.channel_properties_dialog.present()
848 def on_mute_toggled(self, button):
849 self.channel.out_mute = self.mute.get_active()
851 def midi_events_check(self):
852 if self.channel != None and self.channel.midi_in_got_events:
853 self.mute.set_active(self.channel.out_mute)
854 super().on_midi_event_received()
857 # remove control groups from input channels
858 for input_channel in self.app.channels:
859 input_channel.remove_control_group(self)
862 self.channel.remove()
866 def serialization_name(cls):
867 return 'output_channel'
869 def serialize(self, object_backend):
870 object_backend.add_property("name", self.channel_name)
872 object_backend.add_property("type", "stereo")
874 object_backend.add_property("type", "mono")
875 if self.display_solo_buttons:
876 object_backend.add_property("solo_buttons", "true")
879 prefader_in_channels = []
880 for input_channel in self.app.channels:
881 if self.channel.is_muted(input_channel.channel):
882 muted_channels.append(input_channel)
883 if self.channel.is_solo(input_channel.channel):
884 solo_channels.append(input_channel)
885 if self.channel.is_in_prefader(input_channel.channel):
886 prefader_in_channels.append(input_channel)
888 object_backend.add_property('muted_channels', '|'.join([x.channel.name for x in muted_channels]))
890 object_backend.add_property('solo_channels', '|'.join([x.channel.name for x in solo_channels]))
891 if prefader_in_channels:
892 object_backend.add_property('prefader_channels', '|'.join([x.channel.name for x in prefader_in_channels]))
893 object_backend.add_property("color", self.color.to_string())
894 super().serialize(object_backend)
896 def unserialize_property(self, name, value):
898 self.channel_name = str(value)
901 if value == "stereo":
907 if name == "solo_buttons":
909 self.display_solo_buttons = True
911 if name == 'muted_channels':
912 self._init_muted_channels = value.split('|')
914 if name == 'solo_channels':
915 self._init_solo_channels = value.split('|')
917 if name == 'prefader_channels':
918 self._init_prefader_channels = value.split('|')
925 return super().unserialize_property(name, value)
927 class ChannelPropertiesDialog(Gtk.Dialog):
930 def __init__(self, parent, app):
931 self.channel = parent
933 self.mixer = self.channel.mixer
934 Gtk.Dialog.__init__(self, 'Channel "%s" Properties' % self.channel.channel_name, app.window)
935 self.set_default_size(365, -1)
937 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
938 self.ok_button = self.add_button(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY)
939 self.set_default_response(Gtk.ResponseType.APPLY);
944 self.connect('response', self.on_response_cb)
945 self.connect('delete-event', self.on_response_cb)
947 def create_frame(self, label, child):
950 frame.set_border_width(3)
951 #frame.set_shadow_type(Gtk.ShadowType.NONE)
952 frame.get_label_widget().set_markup('<b>%s</b>' % label)
954 alignment = Gtk.Alignment.new(0, 0, 1, 1)
955 alignment.set_padding(0, 0, 12, 0)
965 self.properties_table = table = Gtk.Table(4, 3, False)
966 vbox.pack_start(self.create_frame('Properties', table), True, True, 0)
967 table.set_row_spacings(5)
968 table.set_col_spacings(5)
970 table.attach(Gtk.Label(label='Name'), 0, 1, 0, 1)
971 self.entry_name = Gtk.Entry()
972 self.entry_name.set_activates_default(True)
973 self.entry_name.connect('changed', self.on_entry_name_changed)
974 table.attach(self.entry_name, 1, 2, 0, 1)
976 table.attach(Gtk.Label(label='Mode'), 0, 1, 1, 2)
977 self.mode_hbox = Gtk.HBox()
978 table.attach(self.mode_hbox, 1, 2, 1, 2)
979 self.mono = Gtk.RadioButton(label='Mono')
980 self.stereo = Gtk.RadioButton(label='Stereo', group=self.mono)
981 self.mode_hbox.pack_start(self.mono, True, True, 0)
982 self.mode_hbox.pack_start(self.stereo, True, True, 0)
984 table = Gtk.Table(2, 3, False)
985 vbox.pack_start(self.create_frame('MIDI Control Changes', table), True, True, 0)
986 table.set_row_spacings(5)
987 table.set_col_spacings(5)
989 cc_tooltip = "{} MIDI Control Change number (0-127, set to -1 to assign next free CC #)"
990 table.attach(Gtk.Label(label='Volume'), 0, 1, 0, 1)
991 self.entry_volume_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
992 self.entry_volume_cc.set_tooltip_text(cc_tooltip.format("Volume"))
993 table.attach(self.entry_volume_cc, 1, 2, 0, 1)
994 self.button_sense_midi_volume = Gtk.Button('Learn')
995 self.button_sense_midi_volume.connect('clicked',
996 self.on_sense_midi_volume_clicked)
997 table.attach(self.button_sense_midi_volume, 2, 3, 0, 1)
999 table.attach(Gtk.Label(label='Balance'), 0, 1, 1, 2)
1000 self.entry_balance_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
1001 self.entry_balance_cc.set_tooltip_text(cc_tooltip.format("Balance"))
1002 table.attach(self.entry_balance_cc, 1, 2, 1, 2)
1003 self.button_sense_midi_balance = Gtk.Button('Learn')
1004 self.button_sense_midi_balance.connect('clicked',
1005 self.on_sense_midi_balance_clicked)
1006 table.attach(self.button_sense_midi_balance, 2, 3, 1, 2)
1008 table.attach(Gtk.Label(label='Mute'), 0, 1, 2, 3)
1009 self.entry_mute_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
1010 self.entry_mute_cc.set_tooltip_text(cc_tooltip.format("Mute"))
1011 table.attach(self.entry_mute_cc, 1, 2, 2, 3)
1012 self.button_sense_midi_mute = Gtk.Button('Learn')
1013 self.button_sense_midi_mute.connect('clicked',
1014 self.on_sense_midi_mute_clicked)
1015 table.attach(self.button_sense_midi_mute, 2, 3, 2, 3)
1017 if (isinstance(self, NewChannelDialog) or (self.channel and
1018 isinstance(self.channel, InputChannel))):
1019 table.attach(Gtk.Label(label='Solo'), 0, 1, 3, 4)
1020 self.entry_solo_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
1021 self.entry_solo_cc.set_tooltip_text(cc_tooltip.format("Solo"))
1022 table.attach(self.entry_solo_cc, 1, 2, 3, 4)
1023 self.button_sense_midi_solo = Gtk.Button('Learn')
1024 self.button_sense_midi_solo.connect('clicked',
1025 self.on_sense_midi_solo_clicked)
1026 table.attach(self.button_sense_midi_solo, 2, 3, 3, 4)
1028 self.vbox.show_all()
1031 self.entry_name.set_text(self.channel.channel_name)
1032 if self.channel.channel.is_stereo:
1033 self.stereo.set_active(True)
1035 self.mono.set_active(True)
1036 self.mode_hbox.set_sensitive(False)
1037 self.entry_volume_cc.set_value(self.channel.channel.volume_midi_cc)
1038 self.entry_balance_cc.set_value(self.channel.channel.balance_midi_cc)
1039 self.entry_mute_cc.set_value(self.channel.channel.mute_midi_cc)
1040 if (self.channel and isinstance(self.channel, InputChannel)):
1041 self.entry_solo_cc.set_value(self.channel.channel.solo_midi_cc)
1043 def sense_popup_dialog(self, entry):
1044 window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
1045 window.set_destroy_with_parent(True)
1046 window.set_transient_for(self)
1047 window.set_decorated(False)
1048 window.set_modal(True)
1049 window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
1050 window.set_border_width(10)
1055 vbox.pack_start(Gtk.Label(label='Please move the MIDI control you want to use for this function.'), True, True, 0)
1056 timeout_label = Gtk.Label(label='This window will close in 5 seconds')
1057 vbox.pack_start(timeout_label, True, True, 0)
1058 def close_sense_timeout(window, entry):
1060 timeout_label.set_text('This window will close in %d seconds.' % window.timeout)
1061 if window.timeout == 0:
1063 entry.set_value(self.mixer.last_midi_channel)
1067 GObject.timeout_add_seconds(1, close_sense_timeout, window, entry)
1069 def on_sense_midi_volume_clicked(self, *args):
1070 self.mixer.last_midi_channel = int(self.entry_volume_cc.get_value())
1071 self.sense_popup_dialog(self.entry_volume_cc)
1073 def on_sense_midi_balance_clicked(self, *args):
1074 self.mixer.last_midi_channel = int(self.entry_balance_cc.get_value())
1075 self.sense_popup_dialog(self.entry_balance_cc)
1077 def on_sense_midi_mute_clicked(self, *args):
1078 self.mixer.last_midi_channel = int(self.entry_mute_cc.get_value())
1079 self.sense_popup_dialog(self.entry_mute_cc)
1081 def on_sense_midi_solo_clicked(self, *args):
1082 self.mixer.last_midi_channel = int(self.entry_solo_cc.get_value())
1083 self.sense_popup_dialog(self.entry_solo_cc)
1085 def on_response_cb(self, dlg, response_id, *args):
1086 self.channel.channel_properties_dialog = None
1087 name = self.entry_name.get_text()
1088 if response_id == Gtk.ResponseType.APPLY:
1089 if name != self.channel.channel_name:
1090 self.channel.channel_name = name
1091 for control in ('volume', 'balance', 'mute', 'solo'):
1092 widget = getattr(self, 'entry_{}_cc'.format(control), None)
1093 if widget is not None:
1094 value = int(widget.get_value())
1096 setattr(self.channel.channel, '{}_midi_cc'.format(control), value)
1099 def on_entry_name_changed(self, entry):
1101 if len(entry.get_text()):
1102 if self.channel and self.channel.channel.name == entry.get_text():
1104 elif entry.get_text() not in [x.channel.name for x in self.app.channels] + \
1105 [x.channel.name for x in self.app.output_channels] + ['MAIN']:
1107 self.ok_button.set_sensitive(sensitive)
1109 GObject.signal_new("output-channel-order-changed", OutputChannel,
1110 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
1111 None, [GObject.TYPE_STRING, GObject.TYPE_STRING])
1114 class NewChannelDialog(ChannelPropertiesDialog):
1115 def create_ui(self):
1116 ChannelPropertiesDialog.create_ui(self)
1117 self.add_initial_value_radio()
1118 self.vbox.show_all()
1120 def add_initial_value_radio(self):
1121 self.properties_table.attach(Gtk.Label(label='Value'), 0, 1, 2, 3)
1122 self.value_hbox = Gtk.HBox()
1123 self.properties_table.attach(self.value_hbox, 1, 2, 2, 3)
1124 self.minus_inf = Gtk.RadioButton(label='-Inf')
1125 self.zero_dB = Gtk.RadioButton(label='0dB', group=self.minus_inf)
1126 self.value_hbox.pack_start(self.minus_inf, True, True, 0)
1127 self.value_hbox.pack_start(self.zero_dB, True, True, 0)
1130 class NewInputChannelDialog(NewChannelDialog):
1131 def __init__(self, app):
1132 Gtk.Dialog.__init__(self, 'New Input Channel', app.window)
1133 self.set_default_size(365, -1)
1134 self.mixer = app.mixer
1139 self.stereo.set_active(True) # default to stereo
1141 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1142 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1143 self.ok_button.set_sensitive(False)
1144 self.set_default_response(Gtk.ResponseType.OK);
1147 self.entry_volume_cc.set_value(-1)
1148 self.entry_balance_cc.set_value(-1)
1149 self.entry_mute_cc.set_value(-1)
1150 self.entry_solo_cc.set_value(-1)
1152 def get_result(self):
1153 log.debug('minus_inf active?: %s', self.zero_dB.get_active())
1154 return {'name': self.entry_name.get_text(),
1155 'stereo': self.stereo.get_active(),
1156 'volume_cc': int(self.entry_volume_cc.get_value()),
1157 'balance_cc': int(self.entry_balance_cc.get_value()),
1158 'mute_cc': int(self.entry_mute_cc.get_value()),
1159 'solo_cc': int(self.entry_solo_cc.get_value()),
1160 'value': self.minus_inf.get_active()
1164 class OutputChannelPropertiesDialog(ChannelPropertiesDialog):
1165 def create_ui(self):
1166 ChannelPropertiesDialog.create_ui(self)
1168 table = self.properties_table
1169 table.attach(Gtk.Label(label='Color'), 0, 1, 4, 5)
1170 self.color_chooser_button = Gtk.ColorButton()
1171 self.color_chooser_button.set_use_alpha(True)
1172 self.color_chooser_button.set_rgba(Gdk.RGBA(0, 0, 0, 0))
1173 table.attach(self.color_chooser_button, 1, 2, 4, 5)
1176 self.vbox.pack_start(self.create_frame('Input Channels', vbox), True, True, 0)
1178 self.display_solo_buttons = Gtk.CheckButton('Display solo buttons')
1179 vbox.pack_start(self.display_solo_buttons, True, True, 0)
1181 self.vbox.show_all()
1184 ChannelPropertiesDialog.fill_ui(self)
1185 self.display_solo_buttons.set_active(self.channel.display_solo_buttons)
1186 self.color_chooser_button.set_rgba(self.channel.color)
1188 def on_response_cb(self, dlg, response_id, *args):
1189 ChannelPropertiesDialog.on_response_cb(self, dlg, response_id, *args)
1190 if response_id == Gtk.ResponseType.APPLY:
1191 self.channel.display_solo_buttons = self.display_solo_buttons.get_active()
1192 self.channel.set_color(self.color_chooser_button.get_rgba())
1193 for inputchannel in self.app.channels:
1194 inputchannel.update_control_group(self.channel)
1197 class NewOutputChannelDialog(NewChannelDialog, OutputChannelPropertiesDialog):
1198 def __init__(self, app):
1199 Gtk.Dialog.__init__(self, 'New Output Channel', app.window)
1200 self.mixer = app.mixer
1202 OutputChannelPropertiesDialog.create_ui(self)
1203 self.add_initial_value_radio()
1204 self.vbox.show_all()
1206 self.set_default_size(365, -1)
1208 # TODO: disable mode for output channels as mono output channels may
1209 # not be correctly handled yet.
1210 self.mode_hbox.set_sensitive(False)
1211 self.stereo.set_active(True) # default to stereo
1213 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1214 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1215 self.ok_button.set_sensitive(False)
1216 self.set_default_response(Gtk.ResponseType.OK);
1219 self.entry_volume_cc.set_value(-1)
1220 self.entry_balance_cc.set_value(-1)
1221 self.entry_mute_cc.set_value(-1)
1223 def get_result(self):
1224 return {'name': self.entry_name.get_text(),
1225 'stereo': self.stereo.get_active(),
1226 'volume_cc': int(self.entry_volume_cc.get_value()),
1227 'balance_cc': int(self.entry_balance_cc.get_value()),
1228 'mute_cc': int(self.entry_mute_cc.get_value()),
1229 'display_solo_buttons': self.display_solo_buttons.get_active(),
1230 'color': self.color_chooser_button.get_rgba(),
1231 'value': self.minus_inf.get_active()
1235 class ControlGroup(Gtk.Alignment):
1236 def __init__(self, output_channel, input_channel):
1237 GObject.GObject.__init__(self)
1238 self.set(0.5, 0.5, 1, 1)
1239 self.output_channel = output_channel
1240 self.input_channel = input_channel
1241 self.app = input_channel.app
1243 self.hbox = Gtk.HBox()
1244 self.vbox = Gtk.VBox()
1246 self.buttons_box = Gtk.Box(False, button_padding)
1248 set_background_color(self.vbox, output_channel.css_name,
1249 output_channel.color.to_string())
1251 self.vbox.pack_start(self.hbox, True, True, button_padding)
1258 .control_group #label,
1259 .control_group #mute,
1260 .control_group #pre_fader,
1261 .control_group #solo {
1267 css_provider = Gtk.CssProvider()
1268 css_provider.load_from_data(css)
1269 context = Gtk.StyleContext()
1270 screen = Gdk.Screen.get_default()
1271 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
1272 hbox_context = self.hbox.get_style_context()
1273 hbox_context.add_class('control_group')
1275 name = output_channel.channel.name
1276 self.label = Gtk.Label(name)
1277 self.label.set_name("label")
1278 self.label.set_max_width_chars(self.input_channel.label_chars_narrow)
1279 self.label.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
1280 if len(name) > self.input_channel.label_chars_narrow:
1281 self.label.set_tooltip_text(name)
1282 self.hbox.pack_start(self.label, False, False, button_padding)
1283 self.hbox.pack_end(self.buttons_box, False, False, button_padding)
1284 mute = Gtk.ToggleButton()
1286 mute.set_name("mute")
1287 mute.set_tooltip_text("Mute output channel send")
1288 mute.connect("toggled", self.on_mute_toggled)
1290 solo = Gtk.ToggleButton()
1291 solo.set_name("solo")
1293 solo.set_tooltip_text("Solo output send")
1294 solo.connect("toggled", self.on_solo_toggled)
1296 pre = Gtk.ToggleButton("P")
1297 pre.set_name("pre_fader")
1298 pre.set_tooltip_text("Pre (on) / Post (off) fader send")
1299 pre.connect("toggled", self.on_prefader_toggled)
1301 self.buttons_box.pack_start(pre, True, True, button_padding)
1302 self.buttons_box.pack_start(mute, True, True, button_padding)
1303 if self.output_channel.display_solo_buttons:
1304 self.buttons_box.pack_start(solo, True, True, button_padding)
1307 if self.output_channel.display_solo_buttons:
1308 if not self.solo in self.buttons_box.get_children():
1309 self.buttons_box.pack_start(self.solo, True, True, button_padding)
1312 if self.solo in self.buttons_box.get_children():
1313 self.buttons_box.remove(self.solo)
1315 name = self.output_channel.channel.name
1316 self.label.set_text(name)
1317 if len(name) > self.input_channel.label_chars_narrow:
1318 self.label.set_tooltip_text(name)
1320 set_background_color(self.vbox, self.output_channel.css_name, self.output_channel.color.to_string())
1322 def on_mute_toggled(self, button):
1323 self.output_channel.channel.set_muted(self.input_channel.channel, button.get_active())
1324 self.app.update_monitor(self)
1326 def on_solo_toggled(self, button):
1327 self.output_channel.channel.set_solo(self.input_channel.channel, button.get_active())
1328 self.app.update_monitor(self)
1330 def on_prefader_toggled(self, button):
1331 self.output_channel.channel.set_in_prefader(self.input_channel.channel, button.get_active())
1334 self.hbox.remove(self.label)
1335 self.hbox.set_child_packing(self.buttons_box, True, True, button_padding, Gtk.PackType.END)
1338 self.hbox.pack_start(self.label, False, False, button_padding)
1339 self.hbox.set_child_packing(self.buttons_box, False, False, button_padding, Gtk.PackType.END)