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
28 from serialization import SerializedObject
36 log = logging.getLogger(__name__)
39 .top_label {padding: 0px .1em}
42 css_provider = Gtk.CssProvider()
43 css_provider.load_from_data(css)
44 context = Gtk.StyleContext()
45 screen = Gdk.Screen.get_default()
46 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
49 def set_background_color(widget, name, color_string):
54 """ % (name, color_string)
56 css_provider = Gtk.CssProvider()
57 css_provider.load_from_data(css.encode('utf-8'))
58 context = Gtk.StyleContext()
59 screen = Gdk.Screen.get_default()
60 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
62 widget_context = widget.get_style_context()
63 widget_context.add_class(name)
67 from random import uniform, seed
69 return Gdk.RGBA(uniform(0, 1), uniform(0, 1), uniform(0, 1), 1)
72 class Channel(Gtk.VBox, SerializedObject):
73 '''Widget with slider and meter used as base class for more specific
77 def __init__(self, app, name, stereo, value = None):
78 Gtk.VBox.__init__(self)
80 self.mixer = app.mixer
81 self.gui_factory = app.gui_factory
82 self._channel_name = name
84 self.initial_value = value
85 self.meter_scale = self.gui_factory.get_default_meter_scale()
86 self.slider_scale = self.gui_factory.get_default_slider_scale()
87 self.slider_adjustment = slider.AdjustmentdBFS(self.slider_scale, 0.0, 0.02)
88 self.balance_adjustment = slider.BalanceAdjustment()
89 self.future_out_mute = None
90 self.future_volume_midi_cc = None
91 self.future_balance_midi_cc = None
92 self.future_mute_midi_cc = None
93 self.future_solo_midi_cc = None
94 self.css_name = "css_name_%d" % Channel.num_instances
95 Channel.num_instances += 1
97 def get_channel_name(self):
98 return self._channel_name
102 post_fader_output_channel = None
103 def set_channel_name(self, name):
104 self.app.on_channel_rename(self._channel_name, name);
105 self._channel_name = name
107 self.label_name.set_text(name)
109 self.channel.name = name
110 if self.post_fader_output_channel:
111 self.post_fader_output_channel.name = "%s Out" % name;
112 channel_name = property(get_channel_name, set_channel_name)
115 log.debug('Realizing channel "%s".', self.channel_name)
116 if self.future_out_mute != None:
117 self.channel.out_mute = self.future_out_mute
119 self.slider_adjustment.connect("volume-changed", self.on_volume_changed)
120 self.slider_adjustment.connect("volume-changed-from-midi", self.on_volume_changed_from_midi)
121 self.balance_adjustment.connect("balance-changed", self.on_balance_changed)
124 self.create_slider_widget()
127 self.meter = meter.StereoMeterWidget(self.meter_scale)
129 self.meter = meter.MonoMeterWidget(self.meter_scale)
130 self.on_vumeter_color_changed(self.gui_factory)
132 self.meter.set_events(Gdk.EventMask.SCROLL_MASK)
134 self.gui_factory.connect("default-meter-scale-changed", self.on_default_meter_scale_changed)
135 self.gui_factory.connect("default-slider-scale-changed", self.on_default_slider_scale_changed)
136 self.gui_factory.connect('vumeter-color-changed', self.on_vumeter_color_changed)
137 self.gui_factory.connect('vumeter-color-scheme-changed', self.on_vumeter_color_changed)
138 self.gui_factory.connect('use-custom-widgets-changed', self.on_custom_widgets_changed)
140 self.abspeak = abspeak.AbspeakWidget()
141 self.abspeak.connect("reset", self.on_abspeak_reset)
142 self.abspeak.connect("volume-adjust", self.on_abspeak_adjust)
144 self.volume_digits = Gtk.Entry()
145 self.volume_digits.set_property('xalign', 0.5)
146 self.volume_digits.connect("key-press-event", self.on_volume_digits_key_pressed)
147 self.volume_digits.connect("focus-out-event", self.on_volume_digits_focus_out)
149 if self.initial_value != None:
150 if self.initial_value == True:
151 self.slider_adjustment.set_value(0)
153 self.slider_adjustment.set_value_db(0)
155 self.connect("key-press-event", self.on_key_pressed)
156 self.connect("scroll-event", self.on_scroll)
159 log.debug('Unrealizing channel "%s".', self.channel_name)
162 def balance_preferred_width(self):
165 def _preferred_height(self):
168 def create_balance_widget(self):
169 if self.gui_factory.use_custom_widgets and phat:
170 self.balance = phat.HFanSlider()
171 self.balance.set_default_value(0)
172 self.balance.set_adjustment(self.balance_adjustment)
174 self.balance = Gtk.Scale()
175 self.balance.get_preferred_width = self.balance_preferred_width
176 self.balance.get_preferred_height = self._preferred_height
177 self.balance.set_orientation(Gtk.Orientation.HORIZONTAL)
178 self.balance.set_adjustment(self.balance_adjustment)
179 self.balance.set_has_origin(False)
180 self.balance.set_draw_value(False)
181 self.balance.button_down = False
182 self.balance.connect('button-press-event', self.on_balance_button_press_event)
183 self.balance.connect('button-release-event', self.on_balance_button_release_event)
184 self.balance.connect("motion-notify-event", self.on_balance_motion_notify_event)
185 self.balance.connect("scroll-event", self.on_balance_scroll_event)
188 self.pack_start(self.balance, False, True, 0)
189 if self.monitor_button:
190 self.reorder_child(self.monitor_button, -1)
193 def on_balance_button_press_event(self, widget, event):
194 if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
195 self.balance.button_down = True
196 self.balance.button_down_x = event.x
197 self.balance.button_down_value = self.balance.get_value()
199 if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
200 self.balance_adjustment.set_balance(0)
204 def on_balance_button_release_event(self, widget, event):
205 self.balance.button_down = False
208 def on_balance_motion_notify_event(self, widget, event):
209 slider_length = widget.get_allocation().width - widget.get_style_context().get_property('min-width', Gtk.StateFlags.NORMAL)
210 if self.balance.button_down:
211 delta_x = (event.x - self.balance.button_down_x) / slider_length
212 x = self.balance.button_down_value + 2 * delta_x
217 self.balance_adjustment.set_balance(x)
220 def on_balance_scroll_event(self, widget, event):
222 delta = bal.get_adjustment().get_step_increment()
223 value = bal.get_value()
224 if event.direction == Gdk.ScrollDirection.UP:
226 elif event.direction == Gdk.ScrollDirection.DOWN:
228 elif event.direction == Gdk.ScrollDirection.SMOOTH:
229 x = value - event.delta_y * delta
238 def create_slider_widget(self):
241 parent = self.slider.get_parent()
242 self.slider.destroy()
243 if self.gui_factory.use_custom_widgets:
244 self.slider = slider.CustomSliderWidget(self.slider_adjustment)
246 self.slider = slider.GtkSlider(self.slider_adjustment)
248 parent.pack_start(self.slider, True, True, 0)
249 parent.reorder_child(self.slider, 0)
252 def on_default_meter_scale_changed(self, gui_factory, scale):
253 log.debug("Default meter scale change detected.")
254 self.meter.set_scale(scale)
256 def on_default_slider_scale_changed(self, gui_factory, scale):
257 log.debug("Default slider scale change detected.")
258 self.slider_scale = scale
259 self.slider_adjustment.set_scale(scale)
261 self.channel.midi_scale = self.slider_scale.scale
263 def on_vumeter_color_changed(self, gui_factory, *args):
264 color = gui_factory.get_vumeter_color()
265 color_scheme = gui_factory.get_vumeter_color_scheme()
266 if color_scheme != 'solid':
267 self.meter.set_color(None)
269 self.meter.set_color(Gdk.color_parse(color))
271 def on_custom_widgets_changed(self, gui_factory, value):
272 self.balance.destroy()
273 self.create_balance_widget()
274 self.create_slider_widget()
276 def on_abspeak_adjust(self, abspeak, adjust):
277 log.debug("abspeak adjust %f", adjust)
278 self.slider_adjustment.set_value_db(self.slider_adjustment.get_value_db() + adjust)
279 self.channel.abspeak = None
280 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
282 def on_abspeak_reset(self, abspeak):
283 log.debug("abspeak reset")
284 self.channel.abspeak = None
286 def on_volume_digits_key_pressed(self, widget, event):
287 if (event.keyval == Gdk.KEY_Return or event.keyval == Gdk.KEY_KP_Enter):
288 db_text = self.volume_digits.get_text()
291 log.debug('Volume digits confirmation "%f dBFS".', db)
292 except (ValueError) as e:
293 log.debug("Volume digits confirmation ignore, reset to current.")
294 self.update_volume(False)
296 self.slider_adjustment.set_value_db(db)
298 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
300 def on_volume_digits_focus_out(self, widget, event):
301 log.debug("Volume digits focus out detected.")
302 self.update_volume(False)
304 def read_meter(self):
308 peak_left, peak_right, rms_left, rms_right = self.channel.kmeter
309 self.meter.set_values(peak_left, peak_right, rms_left, rms_right)
311 peak, rms = self.channel.kmeter
312 self.meter.set_values(peak, rms)
314 self.abspeak.set_peak(self.channel.abspeak)
316 def on_scroll(self, widget, event):
317 if event.direction == Gdk.ScrollDirection.DOWN:
318 self.slider_adjustment.step_down()
319 elif event.direction == Gdk.ScrollDirection.UP:
320 self.slider_adjustment.step_up()
323 def update_volume(self, update_engine, from_midi = False):
324 db = self.slider_adjustment.get_value_db()
326 db_text = "%.2f" % db
327 self.volume_digits.set_text(db_text)
331 self.channel.volume = db
332 self.app.update_monitor(self)
334 def on_volume_changed(self, adjustment):
335 self.update_volume(True)
337 def on_volume_changed_from_midi(self, adjustment):
338 self.update_volume(True, from_midi = True)
340 def on_balance_changed(self, adjustment):
341 balance = self.balance_adjustment.get_value()
342 log.debug("%s balance: %f", self.channel_name, balance)
343 self.channel.balance = balance
344 self.app.update_monitor(self)
346 def on_key_pressed(self, widget, event):
347 if (event.keyval == Gdk.KEY_Up):
348 log.debug(self.channel_name + " Up")
349 self.slider_adjustment.step_up()
351 elif (event.keyval == Gdk.KEY_Down):
352 log.debug(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 if self.app._monitored_channel.channel.name == self.channel.name:
414 self.monitor_button.handler_block_by_func(self.on_monitor_button_toggled)
415 self.monitor_button.set_active(True)
416 self.monitor_button.handler_unblock_by_func(self.on_monitor_button_toggled)
418 def set_monitored(self):
420 self.app.set_monitored_channel(self)
421 self.monitor_button.set_active(True)
423 def set_color(self, color):
425 set_background_color(self.label_name_event_box, self.css_name, self.color.to_string())
427 class InputChannel(Channel):
428 post_fader_output_channel = None
430 def __init__(self, app, name, stereo, value = None):
431 Channel.__init__(self, app, name, stereo, value)
435 self.channel = self.mixer.add_channel(self.channel_name, self.stereo)
437 if self.channel == None:
438 raise Exception("Cannot create a channel")
439 Channel.realize(self)
440 if self.future_volume_midi_cc != None:
441 self.channel.volume_midi_cc = self.future_volume_midi_cc
442 if self.future_balance_midi_cc != None:
443 self.channel.balance_midi_cc = self.future_balance_midi_cc
444 if self.future_mute_midi_cc != None:
445 self.channel.mute_midi_cc = self.future_mute_midi_cc
446 if self.future_solo_midi_cc != None:
447 self.channel.solo_midi_cc = self.future_solo_midi_cc
448 if self.app._init_solo_channels and self.channel_name in self.app._init_solo_channels:
449 self.channel.solo = True
451 self.channel.midi_scale = self.slider_scale.scale
453 self.on_volume_changed(self.slider_adjustment)
454 self.on_balance_changed(self.balance_adjustment)
456 # vbox child at upper part
457 self.vbox = Gtk.VBox()
458 self.pack_start(self.vbox, False, True, 0)
459 self.label_name = Gtk.Label()
460 self.label_name.get_style_context().add_class('top_label')
461 self.label_name.set_text(self.channel_name)
462 self.label_name.set_width_chars(0)
463 self.label_name_event_box = Gtk.EventBox()
464 self.label_name_event_box.connect("button-press-event", self.on_label_mouse)
465 self.label_name_event_box.add(self.label_name)
467 entries = [Gtk.TargetEntry.new("INPUT_CHANNEL", Gtk.TargetFlags.SAME_APP, 0)]
468 self.label_name_event_box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, entries,
470 self.label_name_event_box.connect("drag-data-get", self.on_drag_data_get)
471 self.drag_dest_set(Gtk.DestDefaults.ALL, entries, Gdk.DragAction.MOVE)
472 self.connect_after("drag-data-received", self.on_drag_data_received)
474 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
475 # self.label_stereo = Gtk.Label()
477 # self.label_stereo.set_text("stereo")
479 # self.label_stereo.set_text("mono")
480 # self.label_stereo.set_size_request(0, -1)
481 # self.vbox.pack_start(self.label_stereo, True)
484 frame.set_shadow_type(Gtk.ShadowType.IN)
485 frame.add(self.abspeak);
486 self.pack_start(frame, False, True, 0)
488 # hbox child at lower part
489 self.hbox = Gtk.HBox()
490 self.hbox.pack_start(self.slider, True, True, 0)
492 frame.set_shadow_type(Gtk.ShadowType.IN)
493 frame.add(self.meter);
494 self.hbox.pack_start(frame, True, True, 0)
496 frame.set_shadow_type(Gtk.ShadowType.IN)
497 frame.add(self.hbox);
498 self.pack_start(frame, True, True, 0)
500 self.volume_digits.set_width_chars(6)
501 self.pack_start(self.volume_digits, False, False, 0)
503 self.create_balance_widget()
505 self.hbox_mutesolo = Gtk.Box(False, 0)
507 self.mute = Gtk.ToggleButton()
508 self.mute.set_label("M")
509 self.mute.set_name("mute")
510 self.mute.set_active(self.channel.out_mute)
511 self.mute.connect("toggled", self.on_mute_toggled)
512 self.hbox_mutesolo.pack_start(self.mute, True, True, 0)
514 self.solo = Gtk.ToggleButton()
515 self.solo.set_label("S")
516 self.solo.set_name("solo")
517 self.solo.set_active(self.channel.solo)
518 self.solo.connect("toggled", self.on_solo_toggled)
519 self.hbox_mutesolo.pack_start(self.solo, True, True, 0)
521 self.pack_start(self.hbox_mutesolo, False, False, 0)
523 self.monitor_button = Gtk.ToggleButton('MON')
524 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
525 self.pack_start(self.monitor_button, False, False, 0)
531 for cg in self.get_control_groups():
536 for cg in self.get_control_groups():
540 def on_drag_data_get(self, widget, drag_context, data, info, time):
541 channel = widget.get_parent().get_parent()
542 data.set(data.get_target(), 8, channel._channel_name.encode('utf-8'))
544 def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
545 source_name = data.get_data().decode('utf-8')
546 if source_name == self._channel_name:
548 self.emit("input-channel-order-changed", source_name, self._channel_name)
550 def add_control_group(self, channel):
551 control_group = ControlGroup(channel, self)
552 control_group.show_all()
553 self.vbox.pack_start(control_group, True, True, 0)
556 def remove_control_group(self, channel):
557 ctlgroup = self.get_control_group(channel)
558 self.vbox.remove(ctlgroup)
560 def update_control_group(self, channel):
561 for control_group in self.vbox.get_children():
562 if isinstance(control_group, ControlGroup):
563 if control_group.output_channel is channel:
564 control_group.update()
566 def get_control_group(self, channel):
567 for control_group in self.get_control_groups:
568 if control_group.output_channel is channel:
572 def get_control_groups(self):
574 for c in self.vbox.get_children():
575 if isinstance(c, ControlGroup):
580 Channel.unrealize(self)
581 if self.post_fader_output_channel:
582 self.post_fader_output_channel.remove()
583 self.post_fader_output_channel = None
584 self.channel.remove()
587 channel_properties_dialog = None
589 def on_channel_properties(self):
590 if not self.channel_properties_dialog:
591 self.channel_properties_dialog = ChannelPropertiesDialog(self, self.app)
592 self.channel_properties_dialog.show()
593 self.channel_properties_dialog.present()
595 def on_label_mouse(self, widget, event):
596 if event.type == Gdk.EventType._2BUTTON_PRESS:
597 if event.button == 1:
598 self.on_channel_properties()
600 elif event.state & Gdk.ModifierType.CONTROL_MASK and event.type == Gdk.EventType.BUTTON_PRESS and event.button == 1:
607 def on_mute_toggled(self, button):
608 self.channel.out_mute = self.mute.get_active()
610 def on_solo_toggled(self, button):
611 self.channel.solo = self.solo.get_active()
613 def midi_events_check(self):
614 if hasattr(self, 'channel') and self.channel.midi_in_got_events:
615 self.mute.set_active(self.channel.out_mute)
616 self.solo.set_active(self.channel.solo)
617 Channel.on_midi_event_received(self)
619 def on_solo_button_pressed(self, button, event, *args):
620 if event.button == 3:
621 # right click on the solo button, act on all output channels
622 if button.get_active(): # was soloed
623 button.set_active(False)
624 if hasattr(button, 'touched_channels'):
625 touched_channels = button.touched_channels
626 for chan in touched_channels:
627 ctlgroup = self.get_control_group(chan)
628 ctlgroup.solo.set_active(False)
629 del button.touched_channels
630 else: # was not soloed
631 button.set_active(True)
632 touched_channels = []
633 for chan in self.app.output_channels:
634 ctlgroup = self.get_control_group(chan)
635 if not ctlgroup.solo.get_active():
636 ctlgroup.solo.set_active(True)
637 touched_channels.append(chan)
638 button.touched_channels = touched_channels
643 def serialization_name(cls):
644 return 'input_channel'
646 def serialize(self, object_backend):
647 object_backend.add_property("name", self.channel_name)
648 object_backend.add_property("wide", "%s" % str(self.wide))
650 object_backend.add_property("type", "stereo")
652 object_backend.add_property("type", "mono")
653 Channel.serialize(self, object_backend)
655 def unserialize_property(self, name, value):
657 self.channel_name = str(value)
660 self.wide = value == "True"
663 if value == "stereo":
669 return Channel.unserialize_property(self, name, value)
671 GObject.signal_new("input-channel-order-changed", InputChannel,
672 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
673 None, [GObject.TYPE_STRING, GObject.TYPE_STRING])
675 class OutputChannel(Channel):
676 _display_solo_buttons = False
678 _init_muted_channels = None
679 _init_solo_channels = None
680 _init_prefader_channels = None
682 def __init__(self, app, name, stereo, value = None):
683 Channel.__init__(self, app, name, stereo, value)
685 def get_display_solo_buttons(self):
686 return self._display_solo_buttons
688 def set_display_solo_buttons(self, value):
689 self._display_solo_buttons = value
690 # notifying control groups
691 for inputchannel in self.app.channels:
692 inputchannel.update_control_group(self)
694 display_solo_buttons = property(get_display_solo_buttons, set_display_solo_buttons)
697 self.channel = self.mixer.add_output_channel(self.channel_name, self.stereo)
698 if self.channel == None:
699 raise Exception("Cannot create a channel")
700 Channel.realize(self)
701 if self.future_volume_midi_cc != None:
702 self.channel.volume_midi_cc = self.future_volume_midi_cc
703 if self.future_balance_midi_cc != None:
704 self.channel.balance_midi_cc = self.future_balance_midi_cc
705 if self.future_mute_midi_cc != None:
706 self.channel.mute_midi_cc = self.future_mute_midi_cc
707 self.channel.midi_scale = self.slider_scale.scale
709 self.on_volume_changed(self.slider_adjustment)
710 self.on_balance_changed(self.balance_adjustment)
712 # vbox child at upper part
713 self.vbox = Gtk.VBox()
714 self.pack_start(self.vbox, False, True, 0)
715 self.label_name = Gtk.Label()
716 self.label_name.get_style_context().add_class('top_label')
717 self.label_name.set_text(self.channel_name)
718 self.label_name.set_width_chars(0)
719 self.label_name_event_box = Gtk.EventBox()
720 self.label_name_event_box.connect('button-press-event', self.on_label_mouse)
721 self.label_name_event_box.add(self.label_name)
723 entries = [Gtk.TargetEntry.new("OUTPUT_CHANNEL", Gtk.TargetFlags.SAME_APP, 0)]
724 self.label_name_event_box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, entries,
726 self.label_name_event_box.connect("drag-data-get", self.on_drag_data_get)
727 self.drag_dest_set(Gtk.DestDefaults.ALL, entries, Gdk.DragAction.MOVE)
728 self.connect_after("drag-data-received", self.on_drag_data_received)
730 if not hasattr(self, 'color'):
731 self.color = random_color()
732 set_background_color(self.label_name_event_box, self.css_name,
733 self.color.to_string())
734 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
736 frame.set_shadow_type(Gtk.ShadowType.IN)
737 frame.add(self.abspeak);
738 self.vbox.pack_start(frame, False, True, 0)
740 # hbox child at lower part
741 self.hbox = Gtk.HBox()
742 self.hbox.pack_start(self.slider, True, True, 0)
744 frame.set_shadow_type(Gtk.ShadowType.IN)
745 frame.add(self.meter);
746 self.hbox.pack_start(frame, True, True, 0)
748 frame.set_shadow_type(Gtk.ShadowType.IN)
749 frame.add(self.hbox);
750 self.pack_start(frame, True, True, 0)
752 self.volume_digits.set_width_chars(6)
753 self.pack_start(self.volume_digits, False, True, 0)
755 self.create_balance_widget()
757 self.mute = Gtk.ToggleButton()
758 self.mute.set_label("M")
759 self.mute.set_name("mute")
760 self.mute.set_active(self.channel.out_mute)
761 self.mute.connect("toggled", self.on_mute_toggled)
764 hbox.pack_start(self.mute, True, True, 0)
765 self.pack_start(hbox, False, False, 0)
767 self.monitor_button = Gtk.ToggleButton('MON')
768 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
769 self.pack_start(self.monitor_button, False, False, 0)
771 # add control groups to the input channels, and initialize them
773 for input_channel in self.app.channels:
774 input_channel.ctlgroup = input_channel.add_control_group(self)
775 if self._init_muted_channels and input_channel.channel.name in self._init_muted_channels:
776 input_channel.ctlgroup.mute.set_active(True)
777 if self._init_solo_channels and input_channel.channel.name in self._init_solo_channels:
778 input_channel.ctlgroup.solo.set_active(True)
779 if self._init_prefader_channels and input_channel.channel.name in self._init_prefader_channels:
780 input_channel.ctlgroup.prefader.set_active(True)
781 if not input_channel.wide:
782 input_channel.ctlgroup.narrow()
784 self._init_muted_channels = None
785 self._init_solo_channels = None
786 self._init_prefader_channels = None
788 channel_properties_dialog = None
790 def on_drag_data_get(self, widget, drag_context, data, info, time):
791 channel = widget.get_parent().get_parent()
792 data.set(data.get_target(), 8, channel._channel_name.encode('utf-8'))
794 def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
795 source_name = data.get_data().decode('utf-8')
796 if source_name == self._channel_name:
798 self.emit("output-channel-order-changed", source_name, self._channel_name)
800 def on_channel_properties(self):
801 if not self.channel_properties_dialog:
802 self.channel_properties_dialog = OutputChannelPropertiesDialog(self, self.app)
803 self.channel_properties_dialog.show()
804 self.channel_properties_dialog.present()
806 def on_label_mouse(self, widget, event):
807 if event.type == Gdk.EventType._2BUTTON_PRESS:
808 if event.button == 1:
809 self.on_channel_properties()
811 def on_mute_toggled(self, button):
812 self.channel.out_mute = self.mute.get_active()
814 def midi_events_check(self):
815 if self.channel != None and self.channel.midi_in_got_events:
816 self.mute.set_active(self.channel.out_mute)
817 Channel.on_midi_event_received(self)
820 # remove control groups from input channels
821 for input_channel in self.app.channels:
822 input_channel.remove_control_group(self)
824 Channel.unrealize(self)
825 self.channel.remove()
829 def serialization_name(cls):
830 return 'output_channel'
832 def serialize(self, object_backend):
833 object_backend.add_property("name", self.channel_name)
835 object_backend.add_property("type", "stereo")
837 object_backend.add_property("type", "mono")
838 if self.display_solo_buttons:
839 object_backend.add_property("solo_buttons", "true")
842 prefader_in_channels = []
843 for input_channel in self.app.channels:
844 if self.channel.is_muted(input_channel.channel):
845 muted_channels.append(input_channel)
846 if self.channel.is_solo(input_channel.channel):
847 solo_channels.append(input_channel)
848 if self.channel.is_in_prefader(input_channel.channel):
849 prefader_in_channels.append(input_channel)
851 object_backend.add_property('muted_channels', '|'.join([x.channel.name for x in muted_channels]))
853 object_backend.add_property('solo_channels', '|'.join([x.channel.name for x in solo_channels]))
854 if prefader_in_channels:
855 object_backend.add_property('prefader_channels', '|'.join([x.channel.name for x in prefader_in_channels]))
856 object_backend.add_property("color", self.color.to_string())
857 Channel.serialize(self, object_backend)
859 def unserialize_property(self, name, value):
861 self.channel_name = str(value)
864 if value == "stereo":
870 if name == "solo_buttons":
872 self.display_solo_buttons = True
874 if name == 'muted_channels':
875 self._init_muted_channels = value.split('|')
877 if name == 'solo_channels':
878 self._init_solo_channels = value.split('|')
880 if name == 'prefader_channels':
881 self._init_prefader_channels = value.split('|')
888 return Channel.unserialize_property(self, name, value)
890 class ChannelPropertiesDialog(Gtk.Dialog):
893 def __init__(self, parent, app):
894 self.channel = parent
896 self.mixer = self.channel.mixer
897 Gtk.Dialog.__init__(self, 'Channel "%s" Properties' % self.channel.channel_name, app.window)
898 self.set_default_size(365, -1)
900 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
901 self.ok_button = self.add_button(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY)
902 self.set_default_response(Gtk.ResponseType.APPLY);
907 self.connect('response', self.on_response_cb)
908 self.connect('delete-event', self.on_response_cb)
910 def create_frame(self, label, child):
913 frame.set_border_width(3)
914 #frame.set_shadow_type(Gtk.ShadowType.NONE)
915 frame.get_label_widget().set_markup('<b>%s</b>' % label)
917 alignment = Gtk.Alignment.new(0, 0, 1, 1)
918 alignment.set_padding(0, 0, 12, 0)
928 self.properties_table = table = Gtk.Table(4, 3, False)
929 vbox.pack_start(self.create_frame('Properties', table), True, True, 0)
930 table.set_row_spacings(5)
931 table.set_col_spacings(5)
933 table.attach(Gtk.Label(label='Name'), 0, 1, 0, 1)
934 self.entry_name = Gtk.Entry()
935 self.entry_name.set_activates_default(True)
936 self.entry_name.connect('changed', self.on_entry_name_changed)
937 table.attach(self.entry_name, 1, 2, 0, 1)
939 table.attach(Gtk.Label(label='Mode'), 0, 1, 1, 2)
940 self.mode_hbox = Gtk.HBox()
941 table.attach(self.mode_hbox, 1, 2, 1, 2)
942 self.mono = Gtk.RadioButton(label='Mono')
943 self.stereo = Gtk.RadioButton(label='Stereo', group=self.mono)
944 self.mode_hbox.pack_start(self.mono, True, True, 0)
945 self.mode_hbox.pack_start(self.stereo, True, True, 0)
947 table = Gtk.Table(2, 3, False)
948 vbox.pack_start(self.create_frame('MIDI Control Changes', table), True, True, 0)
949 table.set_row_spacings(5)
950 table.set_col_spacings(5)
952 cc_tooltip = "{} MIDI Control Change number (0-127, set to -1 to assign next free CC #)"
953 table.attach(Gtk.Label(label='Volume'), 0, 1, 0, 1)
954 self.entry_volume_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
955 self.entry_volume_cc.set_tooltip_text(cc_tooltip.format("Volume"))
956 table.attach(self.entry_volume_cc, 1, 2, 0, 1)
957 self.button_sense_midi_volume = Gtk.Button('Learn')
958 self.button_sense_midi_volume.connect('clicked',
959 self.on_sense_midi_volume_clicked)
960 table.attach(self.button_sense_midi_volume, 2, 3, 0, 1)
962 table.attach(Gtk.Label(label='Balance'), 0, 1, 1, 2)
963 self.entry_balance_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
964 self.entry_balance_cc.set_tooltip_text(cc_tooltip.format("Balance"))
965 table.attach(self.entry_balance_cc, 1, 2, 1, 2)
966 self.button_sense_midi_balance = Gtk.Button('Learn')
967 self.button_sense_midi_balance.connect('clicked',
968 self.on_sense_midi_balance_clicked)
969 table.attach(self.button_sense_midi_balance, 2, 3, 1, 2)
971 table.attach(Gtk.Label(label='Mute'), 0, 1, 2, 3)
972 self.entry_mute_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
973 self.entry_mute_cc.set_tooltip_text(cc_tooltip.format("Mute"))
974 table.attach(self.entry_mute_cc, 1, 2, 2, 3)
975 self.button_sense_midi_mute = Gtk.Button('Learn')
976 self.button_sense_midi_mute.connect('clicked',
977 self.on_sense_midi_mute_clicked)
978 table.attach(self.button_sense_midi_mute, 2, 3, 2, 3)
980 if (isinstance(self, NewChannelDialog) or (self.channel and
981 isinstance(self.channel, InputChannel))):
982 table.attach(Gtk.Label(label='Solo'), 0, 1, 3, 4)
983 self.entry_solo_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
984 self.entry_solo_cc.set_tooltip_text(cc_tooltip.format("Solo"))
985 table.attach(self.entry_solo_cc, 1, 2, 3, 4)
986 self.button_sense_midi_solo = Gtk.Button('Learn')
987 self.button_sense_midi_solo.connect('clicked',
988 self.on_sense_midi_solo_clicked)
989 table.attach(self.button_sense_midi_solo, 2, 3, 3, 4)
994 self.entry_name.set_text(self.channel.channel_name)
995 if self.channel.channel.is_stereo:
996 self.stereo.set_active(True)
998 self.mono.set_active(True)
999 self.mode_hbox.set_sensitive(False)
1000 self.entry_volume_cc.set_value(self.channel.channel.volume_midi_cc)
1001 self.entry_balance_cc.set_value(self.channel.channel.balance_midi_cc)
1002 self.entry_mute_cc.set_value(self.channel.channel.mute_midi_cc)
1003 if (self.channel and isinstance(self.channel, InputChannel)):
1004 self.entry_solo_cc.set_value(self.channel.channel.solo_midi_cc)
1006 def sense_popup_dialog(self, entry):
1007 window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
1008 window.set_destroy_with_parent(True)
1009 window.set_transient_for(self)
1010 window.set_decorated(False)
1011 window.set_modal(True)
1012 window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
1013 window.set_border_width(10)
1018 vbox.pack_start(Gtk.Label(label='Please move the MIDI control you want to use for this function.'), True, True, 0)
1019 timeout_label = Gtk.Label(label='This window will close in 5 seconds')
1020 vbox.pack_start(timeout_label, True, True, 0)
1021 def close_sense_timeout(window, entry):
1023 timeout_label.set_text('This window will close in %d seconds.' % window.timeout)
1024 if window.timeout == 0:
1026 entry.set_value(self.mixer.last_midi_channel)
1030 GObject.timeout_add_seconds(1, close_sense_timeout, window, entry)
1032 def on_sense_midi_volume_clicked(self, *args):
1033 self.mixer.last_midi_channel = int(self.entry_volume_cc.get_value())
1034 self.sense_popup_dialog(self.entry_volume_cc)
1036 def on_sense_midi_balance_clicked(self, *args):
1037 self.mixer.last_midi_channel = int(self.entry_balance_cc.get_value())
1038 self.sense_popup_dialog(self.entry_balance_cc)
1040 def on_sense_midi_mute_clicked(self, *args):
1041 self.mixer.last_midi_channel = int(self.entry_mute_cc.get_value())
1042 self.sense_popup_dialog(self.entry_mute_cc)
1044 def on_sense_midi_solo_clicked(self, *args):
1045 self.mixer.last_midi_channel = int(self.entry_solo_cc.get_value())
1046 self.sense_popup_dialog(self.entry_solo_cc)
1048 def on_response_cb(self, dlg, response_id, *args):
1049 self.channel.channel_properties_dialog = None
1050 name = self.entry_name.get_text()
1051 if response_id == Gtk.ResponseType.APPLY:
1052 if name != self.channel.channel_name:
1053 self.channel.channel_name = name
1054 for control in ('volume', 'balance', 'mute', 'solo'):
1055 widget = getattr(self, 'entry_{}_cc'.format(control), None)
1056 if widget is not None:
1057 value = int(widget.get_value())
1059 setattr(self.channel.channel, '{}_midi_cc'.format(control), value)
1062 def on_entry_name_changed(self, entry):
1064 if len(entry.get_text()):
1065 if self.channel and self.channel.channel.name == entry.get_text():
1067 elif entry.get_text() not in [x.channel.name for x in self.app.channels] + \
1068 [x.channel.name for x in self.app.output_channels] + ['MAIN']:
1070 self.ok_button.set_sensitive(sensitive)
1072 GObject.signal_new("output-channel-order-changed", OutputChannel,
1073 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
1074 None, [GObject.TYPE_STRING, GObject.TYPE_STRING])
1077 class NewChannelDialog(ChannelPropertiesDialog):
1078 def create_ui(self):
1079 ChannelPropertiesDialog.create_ui(self)
1080 self.add_initial_value_radio()
1081 self.vbox.show_all()
1083 def add_initial_value_radio(self):
1084 self.properties_table.attach(Gtk.Label(label='Value'), 0, 1, 2, 3)
1085 self.value_hbox = Gtk.HBox()
1086 self.properties_table.attach(self.value_hbox, 1, 2, 2, 3)
1087 self.minus_inf = Gtk.RadioButton(label='-Inf')
1088 self.zero_dB = Gtk.RadioButton(label='0dB', group=self.minus_inf)
1089 self.value_hbox.pack_start(self.minus_inf, True, True, 0)
1090 self.value_hbox.pack_start(self.zero_dB, True, True, 0)
1093 class NewInputChannelDialog(NewChannelDialog):
1094 def __init__(self, app):
1095 Gtk.Dialog.__init__(self, 'New Input Channel', app.window)
1096 self.set_default_size(365, -1)
1097 self.mixer = app.mixer
1102 self.stereo.set_active(True) # default to stereo
1104 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1105 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1106 self.ok_button.set_sensitive(False)
1107 self.set_default_response(Gtk.ResponseType.OK);
1110 self.entry_volume_cc.set_value(-1)
1111 self.entry_balance_cc.set_value(-1)
1112 self.entry_mute_cc.set_value(-1)
1113 self.entry_solo_cc.set_value(-1)
1115 def get_result(self):
1116 log.debug('minus_inf active?: %s', self.zero_dB.get_active())
1117 return {'name': self.entry_name.get_text(),
1118 'stereo': self.stereo.get_active(),
1119 'volume_cc': int(self.entry_volume_cc.get_value()),
1120 'balance_cc': int(self.entry_balance_cc.get_value()),
1121 'mute_cc': int(self.entry_mute_cc.get_value()),
1122 'solo_cc': int(self.entry_solo_cc.get_value()),
1123 'value': self.minus_inf.get_active()
1127 class OutputChannelPropertiesDialog(ChannelPropertiesDialog):
1128 def create_ui(self):
1129 ChannelPropertiesDialog.create_ui(self)
1131 table = self.properties_table
1132 table.attach(Gtk.Label(label='Color'), 0, 1, 4, 5)
1133 self.color_chooser_button = Gtk.ColorButton()
1134 self.color_chooser_button.set_use_alpha(True)
1135 self.color_chooser_button.set_rgba(Gdk.RGBA(0, 0, 0, 0))
1136 table.attach(self.color_chooser_button, 1, 2, 4, 5)
1139 self.vbox.pack_start(self.create_frame('Input Channels', vbox), True, True, 0)
1141 self.display_solo_buttons = Gtk.CheckButton('Display solo buttons')
1142 vbox.pack_start(self.display_solo_buttons, True, True, 0)
1144 self.vbox.show_all()
1147 ChannelPropertiesDialog.fill_ui(self)
1148 self.display_solo_buttons.set_active(self.channel.display_solo_buttons)
1149 self.color_chooser_button.set_rgba(self.channel.color)
1151 def on_response_cb(self, dlg, response_id, *args):
1152 ChannelPropertiesDialog.on_response_cb(self, dlg, response_id, *args)
1153 if response_id == Gtk.ResponseType.APPLY:
1154 self.channel.display_solo_buttons = self.display_solo_buttons.get_active()
1155 self.channel.set_color(self.color_chooser_button.get_rgba())
1156 for inputchannel in self.app.channels:
1157 inputchannel.update_control_group(self.channel)
1160 class NewOutputChannelDialog(NewChannelDialog, OutputChannelPropertiesDialog):
1161 def __init__(self, app):
1162 Gtk.Dialog.__init__(self, 'New Output Channel', app.window)
1163 self.mixer = app.mixer
1165 OutputChannelPropertiesDialog.create_ui(self)
1166 self.add_initial_value_radio()
1167 self.vbox.show_all()
1169 self.set_default_size(365, -1)
1171 # TODO: disable mode for output channels as mono output channels may
1172 # not be correctly handled yet.
1173 self.mode_hbox.set_sensitive(False)
1174 self.stereo.set_active(True) # default to stereo
1176 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1177 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1178 self.ok_button.set_sensitive(False)
1179 self.set_default_response(Gtk.ResponseType.OK);
1182 self.entry_volume_cc.set_value(-1)
1183 self.entry_balance_cc.set_value(-1)
1184 self.entry_mute_cc.set_value(-1)
1186 def get_result(self):
1187 return {'name': self.entry_name.get_text(),
1188 'stereo': self.stereo.get_active(),
1189 'volume_cc': int(self.entry_volume_cc.get_value()),
1190 'balance_cc': int(self.entry_balance_cc.get_value()),
1191 'mute_cc': int(self.entry_mute_cc.get_value()),
1192 'display_solo_buttons': self.display_solo_buttons.get_active(),
1193 'color': self.color_chooser_button.get_rgba(),
1194 'value': self.minus_inf.get_active()
1198 class ControlGroup(Gtk.Alignment):
1199 def __init__(self, output_channel, input_channel):
1200 GObject.GObject.__init__(self)
1201 self.set(0.5, 0.5, 1, 1)
1202 self.output_channel = output_channel
1203 self.input_channel = input_channel
1204 self.app = input_channel.app
1206 self.hbox = Gtk.HBox()
1207 self.vbox = Gtk.VBox()
1209 self.buttons_box = Gtk.Box(False, button_padding)
1211 set_background_color(self.vbox, output_channel.css_name,
1212 output_channel.color.to_string())
1214 self.vbox.pack_start(self.hbox, True, True, button_padding)
1221 .control_group #label,
1222 .control_group #mute,
1223 .control_group #pre_fader,
1224 .control_group #solo {
1230 css_provider = Gtk.CssProvider()
1231 css_provider.load_from_data(css)
1232 context = Gtk.StyleContext()
1233 screen = Gdk.Screen.get_default()
1234 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
1235 hbox_context = self.hbox.get_style_context()
1236 hbox_context.add_class('control_group')
1238 self.label = Gtk.Label(output_channel.channel.name)
1239 self.label.set_name("label")
1240 self.hbox.pack_start(self.label, False, False, button_padding)
1241 self.hbox.pack_end(self.buttons_box, False, False, button_padding)
1242 mute = Gtk.ToggleButton()
1244 mute.set_name("mute")
1245 mute.set_tooltip_text("Mute output channel send")
1246 mute.connect("toggled", self.on_mute_toggled)
1248 solo = Gtk.ToggleButton()
1249 solo.set_name("solo")
1251 solo.set_tooltip_text("Solo output send")
1252 solo.connect("toggled", self.on_solo_toggled)
1254 pre = Gtk.ToggleButton("P")
1255 pre.set_name("pre_fader")
1256 pre.set_tooltip_text("Pre (on) / Post (off) fader send")
1257 pre.connect("toggled", self.on_prefader_toggled)
1259 self.buttons_box.pack_start(pre, True, True, button_padding)
1260 self.buttons_box.pack_start(mute, True, True, button_padding)
1261 if self.output_channel.display_solo_buttons:
1262 self.buttons_box.pack_start(solo, True, True, button_padding)
1265 if self.output_channel.display_solo_buttons:
1266 if not self.solo in self.buttons_box.get_children():
1267 self.buttons_box.pack_start(self.solo, True, True, button_padding)
1270 if self.solo in self.buttons_box.get_children():
1271 self.buttons_box.remove(self.solo)
1273 self.label.set_text(self.output_channel.channel.name)
1274 set_background_color(self.vbox, self.output_channel.css_name, self.output_channel.color.to_string())
1276 def on_mute_toggled(self, button):
1277 self.output_channel.channel.set_muted(self.input_channel.channel, button.get_active())
1278 self.app.update_monitor(self)
1280 def on_solo_toggled(self, button):
1281 self.output_channel.channel.set_solo(self.input_channel.channel, button.get_active())
1282 self.app.update_monitor(self)
1284 def on_prefader_toggled(self, button):
1285 self.output_channel.channel.set_in_prefader(self.input_channel.channel, button.get_active())
1288 self.hbox.remove(self.label)
1289 self.hbox.set_child_packing(self.buttons_box, True, True, button_padding, Gtk.PackType.END)
1292 self.hbox.pack_start(self.label, False, False, button_padding)
1293 self.hbox.set_child_packing(self.buttons_box, False, False, button_padding, Gtk.PackType.END)