Remove remnants if previous Python bindings with Swig
[jack_mixer.git] / channel.py
blob20f6c300581b90596d2939b2d6c89fda8d12ed2c
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.
18 import logging
20 import gi
21 from gi.repository import Gtk
22 from gi.repository import Gdk
23 from gi.repository import GObject
25 import abspeak
26 import meter
27 import slider
28 from serialization import SerializedObject
30 try:
31 import phat
32 except:
33 phat = None
36 log = logging.getLogger(__name__)
37 button_padding = 1
38 css = b"""
39 .top_label {min-width: 100px;}
40 button {padding: 0px}
41 """
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):
50 css = """
51 .%s {
52 background-color: %s
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)
66 def random_color():
67 from random import uniform, seed
68 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
74 channel widgets'''
75 monitor_button = None
76 num_instances = 0
77 def __init__(self, app, name, stereo, value = None):
78 Gtk.VBox.__init__(self)
79 self.app = app
80 self.mixer = app.mixer
81 self.gui_factory = app.gui_factory
82 self._channel_name = name
83 self.stereo = stereo
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
100 label_name = None
101 channel = None
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
106 if self.label_name:
107 self.label_name.set_text(name)
108 if self.channel:
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)
114 def realize(self):
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)
123 self.slider = None
124 self.create_slider_widget()
126 if self.stereo:
127 self.meter = meter.StereoMeterWidget(self.meter_scale)
128 else:
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)
152 else:
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)
158 def unrealize(self):
159 log.debug('Unrealizing channel "%s".', self.channel_name)
160 pass
162 def balance_preferred_width(self):
163 return (20, 20)
165 def _preferred_height(self):
166 return (0, 100)
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)
173 else:
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)
191 self.balance.show()
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()
198 return True
199 if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
200 self.balance_adjustment.set_balance(0)
201 return True
202 return False
204 def on_balance_button_release_event(self, widget, event):
205 self.balance.button_down = False
206 return 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
213 if x >= 1:
214 x = 1
215 elif x <= -1:
216 x = -1
217 self.balance_adjustment.set_balance(x)
218 return True
220 def on_balance_scroll_event(self, widget, event):
221 bal = self.balance
222 delta = bal.get_adjustment().get_step_increment()
223 value = bal.get_value()
224 if event.direction == Gdk.ScrollDirection.UP:
225 x = value - delta
226 elif event.direction == Gdk.ScrollDirection.DOWN:
227 x = value + delta
228 elif event.direction == Gdk.ScrollDirection.SMOOTH:
229 x = value - event.delta_y * delta
231 if x >= 1:
232 x = 1
233 elif x <= -1:
234 x = -1
235 bal.set_value(x)
236 return True
238 def create_slider_widget(self):
239 parent = None
240 if self.slider:
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)
245 else:
246 self.slider = slider.GtkSlider(self.slider_adjustment)
247 if parent:
248 parent.pack_start(self.slider, True, True, 0)
249 parent.reorder_child(self.slider, 0)
250 self.slider.show()
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)
260 if self.channel:
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)
268 else:
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()
289 try:
290 db = float(db_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)
295 return
296 self.slider_adjustment.set_value_db(db)
297 #self.grab_focus()
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):
305 if not self.channel:
306 return
307 if self.stereo:
308 meter_left, meter_right = self.channel.meter
309 self.meter.set_values(meter_left, meter_right)
310 else:
311 self.meter.set_value(self.channel.meter[0])
313 self.abspeak.set_peak(self.channel.abspeak)
315 def on_scroll(self, widget, event):
316 if event.direction == Gdk.ScrollDirection.DOWN:
317 self.slider_adjustment.step_down()
318 elif event.direction == Gdk.ScrollDirection.UP:
319 self.slider_adjustment.step_up()
320 return True
322 def update_volume(self, update_engine, from_midi = False):
323 db = self.slider_adjustment.get_value_db()
325 db_text = "%.2f" % db
326 self.volume_digits.set_text(db_text)
328 if update_engine:
329 if not from_midi:
330 self.channel.volume = db
331 self.app.update_monitor(self)
333 def on_volume_changed(self, adjustment):
334 self.update_volume(True)
336 def on_volume_changed_from_midi(self, adjustment):
337 self.update_volume(True, from_midi = True)
339 def on_balance_changed(self, adjustment):
340 balance = self.balance_adjustment.get_value()
341 log.debug("%s balance: %f", self.channel_name, balance)
342 self.channel.balance = balance
343 self.app.update_monitor(self)
345 def on_key_pressed(self, widget, event):
346 if (event.keyval == Gdk.KEY_Up):
347 log.debug(self.channel_name + " Up")
348 self.slider_adjustment.step_up()
349 return True
350 elif (event.keyval == Gdk.KEY_Down):
351 log.debug(self.channel_name + " Down")
352 self.slider_adjustment.step_down()
353 return True
355 return False
357 def serialize(self, object_backend):
358 object_backend.add_property("volume", "%f" % self.slider_adjustment.get_value_db())
359 object_backend.add_property("balance", "%f" % self.balance_adjustment.get_value())
361 if hasattr(self.channel, 'out_mute'):
362 object_backend.add_property('out_mute', str(self.channel.out_mute))
363 if self.channel.volume_midi_cc != -1:
364 object_backend.add_property('volume_midi_cc', str(self.channel.volume_midi_cc))
365 if self.channel.balance_midi_cc != -1:
366 object_backend.add_property('balance_midi_cc', str(self.channel.balance_midi_cc))
367 if self.channel.mute_midi_cc != -1:
368 object_backend.add_property('mute_midi_cc', str(self.channel.mute_midi_cc))
369 if self.channel.solo_midi_cc != -1:
370 object_backend.add_property('solo_midi_cc', str(self.channel.solo_midi_cc))
373 def unserialize_property(self, name, value):
374 if name == "volume":
375 self.slider_adjustment.set_value_db(float(value))
376 return True
377 if name == "balance":
378 self.balance_adjustment.set_value(float(value))
379 return True
380 if name == 'out_mute':
381 self.future_out_mute = (value == 'True')
382 return True
383 if name == 'volume_midi_cc':
384 self.future_volume_midi_cc = int(value)
385 return True
386 if name == 'balance_midi_cc':
387 self.future_balance_midi_cc = int(value)
388 return True
389 if name == 'mute_midi_cc':
390 self.future_mute_midi_cc = int(value)
391 return True
392 if name == 'solo_midi_cc':
393 self.future_solo_midi_cc = int(value)
394 return True
395 return False
397 def on_midi_event_received(self, *args):
398 self.slider_adjustment.set_value_db(self.channel.volume, from_midi = True)
399 self.balance_adjustment.set_balance(self.channel.balance, from_midi = True)
401 def on_monitor_button_toggled(self, button):
402 if button.get_active():
403 for channel in self.app.channels + self.app.output_channels:
404 if channel.monitor_button.get_active() and channel.monitor_button is not button:
405 channel.monitor_button.handler_block_by_func(
406 channel.on_monitor_button_toggled)
407 channel.monitor_button.set_active(False)
408 channel.monitor_button.handler_unblock_by_func(
409 channel.on_monitor_button_toggled)
410 self.app.set_monitored_channel(self)
412 def set_monitored(self):
413 if self.channel:
414 self.app.set_monitored_channel(self)
415 self.monitor_button.set_active(True)
417 def set_color(self, color):
418 self.color = color
419 set_background_color(self.label_name_event_box, self.css_name, self.color.to_string())
421 class InputChannel(Channel):
422 post_fader_output_channel = None
424 def __init__(self, app, name, stereo, value = None):
425 Channel.__init__(self, app, name, stereo, value)
427 def realize(self):
428 self.channel = self.mixer.add_channel(self.channel_name, self.stereo)
430 if self.channel == None:
431 raise Exception("Cannot create a channel")
432 Channel.realize(self)
433 if self.future_volume_midi_cc != None:
434 self.channel.volume_midi_cc = self.future_volume_midi_cc
435 if self.future_balance_midi_cc != None:
436 self.channel.balance_midi_cc = self.future_balance_midi_cc
437 if self.future_mute_midi_cc != None:
438 self.channel.mute_midi_cc = self.future_mute_midi_cc
439 if self.future_solo_midi_cc != None:
440 self.channel.solo_midi_cc = self.future_solo_midi_cc
441 if self.app._init_solo_channels and self.channel_name in self.app._init_solo_channels:
442 self.channel.solo = True
444 self.channel.midi_scale = self.slider_scale.scale
446 self.on_volume_changed(self.slider_adjustment)
447 self.on_balance_changed(self.balance_adjustment)
449 # vbox child at upper part
450 self.vbox = Gtk.VBox()
451 self.pack_start(self.vbox, False, True, 0)
452 self.label_name = Gtk.Label()
453 self.label_name.get_style_context().add_class('top_label')
454 self.label_name.set_text(self.channel_name)
455 self.label_name.set_width_chars(0)
456 self.label_name_event_box = Gtk.EventBox()
457 self.label_name_event_box.connect("button-press-event", self.on_label_mouse)
458 self.label_name_event_box.add(self.label_name)
459 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
460 # self.label_stereo = Gtk.Label()
461 # if self.stereo:
462 # self.label_stereo.set_text("stereo")
463 # else:
464 # self.label_stereo.set_text("mono")
465 # self.label_stereo.set_size_request(0, -1)
466 # self.vbox.pack_start(self.label_stereo, True)
468 frame = Gtk.Frame()
469 frame.set_shadow_type(Gtk.ShadowType.IN)
470 frame.add(self.abspeak);
471 self.pack_start(frame, False, True, 0)
473 # hbox child at lower part
474 self.hbox = Gtk.HBox()
475 self.hbox.pack_start(self.slider, True, True, 0)
476 frame = Gtk.Frame()
477 frame.set_shadow_type(Gtk.ShadowType.IN)
478 frame.add(self.meter);
479 self.hbox.pack_start(frame, True, True, 0)
480 frame = Gtk.Frame()
481 frame.set_shadow_type(Gtk.ShadowType.IN)
482 frame.add(self.hbox);
483 self.pack_start(frame, True, True, 0)
485 self.volume_digits.set_width_chars(6)
486 self.pack_start(self.volume_digits, False, False, 0)
488 self.create_balance_widget()
490 self.hbox_mutesolo = Gtk.Box(False, 0)
492 self.mute = Gtk.ToggleButton()
493 self.mute.set_label("M")
494 self.mute.set_name("mute")
495 self.mute.set_active(self.channel.out_mute)
496 self.mute.connect("toggled", self.on_mute_toggled)
497 self.hbox_mutesolo.pack_start(self.mute, True, True, 0)
499 self.solo = Gtk.ToggleButton()
500 self.solo.set_label("S")
501 self.solo.set_name("solo")
502 self.solo.set_active(self.channel.solo)
503 self.solo.connect("toggled", self.on_solo_toggled)
504 self.hbox_mutesolo.pack_start(self.solo, True, True, 0)
506 self.pack_start(self.hbox_mutesolo, False, False, 0)
508 self.monitor_button = Gtk.ToggleButton('MON')
509 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
510 self.pack_start(self.monitor_button, False, False, 0)
512 def add_control_group(self, channel):
513 control_group = ControlGroup(channel, self)
514 control_group.show_all()
515 self.vbox.pack_start(control_group, True, True, 0)
516 return control_group
518 def remove_control_group(self, channel):
519 ctlgroup = self.get_control_group(channel)
520 self.vbox.remove(ctlgroup)
522 def update_control_group(self, channel):
523 for control_group in self.vbox.get_children():
524 if isinstance(control_group, ControlGroup):
525 if control_group.output_channel is channel:
526 control_group.update()
528 def get_control_group(self, channel):
529 for control_group in self.vbox.get_children():
530 if isinstance(control_group, ControlGroup):
531 if control_group.output_channel is channel:
532 return control_group
533 return None
535 def unrealize(self):
536 Channel.unrealize(self)
537 if self.post_fader_output_channel:
538 self.post_fader_output_channel.remove()
539 self.post_fader_output_channel = None
540 self.channel.remove()
541 self.channel = None
543 channel_properties_dialog = None
545 def on_channel_properties(self):
546 if not self.channel_properties_dialog:
547 self.channel_properties_dialog = ChannelPropertiesDialog(self, self.app)
548 self.channel_properties_dialog.show()
549 self.channel_properties_dialog.present()
551 def on_label_mouse(self, widget, event):
552 if event.type == Gdk.EventType._2BUTTON_PRESS:
553 if event.button == 1:
554 self.on_channel_properties()
556 def on_mute_toggled(self, button):
557 self.channel.out_mute = self.mute.get_active()
559 def on_solo_toggled(self, button):
560 self.channel.solo = self.solo.get_active()
562 def midi_events_check(self):
563 if hasattr(self, 'channel') and self.channel.midi_in_got_events:
564 self.mute.set_active(self.channel.out_mute)
565 self.solo.set_active(self.channel.solo)
566 Channel.on_midi_event_received(self)
568 def on_solo_button_pressed(self, button, event, *args):
569 if event.button == 3:
570 # right click on the solo button, act on all output channels
571 if button.get_active(): # was soloed
572 button.set_active(False)
573 if hasattr(button, 'touched_channels'):
574 touched_channels = button.touched_channels
575 for chan in touched_channels:
576 ctlgroup = self.get_control_group(chan)
577 ctlgroup.solo.set_active(False)
578 del button.touched_channels
579 else: # was not soloed
580 button.set_active(True)
581 touched_channels = []
582 for chan in self.app.output_channels:
583 ctlgroup = self.get_control_group(chan)
584 if not ctlgroup.solo.get_active():
585 ctlgroup.solo.set_active(True)
586 touched_channels.append(chan)
587 button.touched_channels = touched_channels
588 return True
589 return False
591 @classmethod
592 def serialization_name(cls):
593 return 'input_channel'
595 def serialize(self, object_backend):
596 object_backend.add_property("name", self.channel_name)
597 if self.stereo:
598 object_backend.add_property("type", "stereo")
599 else:
600 object_backend.add_property("type", "mono")
601 Channel.serialize(self, object_backend)
603 def unserialize_property(self, name, value):
604 if name == "name":
605 self.channel_name = str(value)
606 return True
607 if name == "type":
608 if value == "stereo":
609 self.stereo = True
610 return True
611 if value == "mono":
612 self.stereo = False
613 return True
614 return Channel.unserialize_property(self, name, value)
616 class OutputChannel(Channel):
617 _display_solo_buttons = False
619 _init_muted_channels = None
620 _init_solo_channels = None
621 _init_prefader_channels = None
623 def __init__(self, app, name, stereo, value = None):
624 Channel.__init__(self, app, name, stereo, value)
626 def get_display_solo_buttons(self):
627 return self._display_solo_buttons
629 def set_display_solo_buttons(self, value):
630 self._display_solo_buttons = value
631 # notifying control groups
632 for inputchannel in self.app.channels:
633 inputchannel.update_control_group(self)
635 display_solo_buttons = property(get_display_solo_buttons, set_display_solo_buttons)
637 def realize(self):
638 self.channel = self.mixer.add_output_channel(self.channel_name, self.stereo)
639 if self.channel == None:
640 raise Exception("Cannot create a channel")
641 Channel.realize(self)
642 if self.future_volume_midi_cc != None:
643 self.channel.volume_midi_cc = self.future_volume_midi_cc
644 if self.future_balance_midi_cc != None:
645 self.channel.balance_midi_cc = self.future_balance_midi_cc
646 if self.future_mute_midi_cc != None:
647 self.channel.mute_midi_cc = self.future_mute_midi_cc
648 self.channel.midi_scale = self.slider_scale.scale
650 self.on_volume_changed(self.slider_adjustment)
651 self.on_balance_changed(self.balance_adjustment)
653 # vbox child at upper part
654 self.vbox = Gtk.VBox()
655 self.pack_start(self.vbox, False, True, 0)
656 self.label_name = Gtk.Label()
657 self.label_name.get_style_context().add_class('top_label')
658 self.label_name.set_text(self.channel_name)
659 self.label_name.set_width_chars(0)
660 self.label_name_event_box = Gtk.EventBox()
661 self.label_name_event_box.connect('button-press-event', self.on_label_mouse)
662 self.label_name_event_box.add(self.label_name)
663 if not hasattr(self, 'color'):
664 self.color = random_color()
665 set_background_color(self.label_name_event_box, self.css_name,
666 self.color.to_string())
667 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
668 frame = Gtk.Frame()
669 frame.set_shadow_type(Gtk.ShadowType.IN)
670 frame.add(self.abspeak);
671 self.vbox.pack_start(frame, False, True, 0)
673 # hbox child at lower part
674 self.hbox = Gtk.HBox()
675 self.hbox.pack_start(self.slider, True, True, 0)
676 frame = Gtk.Frame()
677 frame.set_shadow_type(Gtk.ShadowType.IN)
678 frame.add(self.meter);
679 self.hbox.pack_start(frame, True, True, 0)
680 frame = Gtk.Frame()
681 frame.set_shadow_type(Gtk.ShadowType.IN)
682 frame.add(self.hbox);
683 self.pack_start(frame, True, True, 0)
685 self.volume_digits.set_width_chars(6)
686 self.pack_start(self.volume_digits, False, True, 0)
688 self.create_balance_widget()
690 self.mute = Gtk.ToggleButton()
691 self.mute.set_label("M")
692 self.mute.set_name("mute")
693 self.mute.set_active(self.channel.out_mute)
694 self.mute.connect("toggled", self.on_mute_toggled)
696 hbox = Gtk.HBox()
697 hbox.pack_start(self.mute, True, True, 0)
698 self.pack_start(hbox, False, False, 0)
700 self.monitor_button = Gtk.ToggleButton('MON')
701 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
702 self.pack_start(self.monitor_button, False, False, 0)
704 # add control groups to the input channels, and initialize them
705 # appropriately
706 for input_channel in self.app.channels:
707 ctlgroup = input_channel.add_control_group(self)
708 if self._init_muted_channels and input_channel.channel.name in self._init_muted_channels:
709 ctlgroup.mute.set_active(True)
710 if self._init_solo_channels and input_channel.channel.name in self._init_solo_channels:
711 ctlgroup.solo.set_active(True)
712 if self._init_prefader_channels and input_channel.channel.name in self._init_prefader_channels:
713 ctlgroup.prefader.set_active(True)
714 self._init_muted_channels = None
715 self._init_solo_channels = None
716 self._init_prefader_channels = None
718 channel_properties_dialog = None
719 def on_channel_properties(self):
720 if not self.channel_properties_dialog:
721 self.channel_properties_dialog = OutputChannelPropertiesDialog(self, self.app)
722 self.channel_properties_dialog.show()
723 self.channel_properties_dialog.present()
725 def on_label_mouse(self, widget, event):
726 if event.type == Gdk.EventType._2BUTTON_PRESS:
727 if event.button == 1:
728 self.on_channel_properties()
730 def on_mute_toggled(self, button):
731 self.channel.out_mute = self.mute.get_active()
733 def midi_events_check(self):
734 if self.channel != None and self.channel.midi_in_got_events:
735 self.mute.set_active(self.channel.out_mute)
736 Channel.on_midi_event_received(self)
738 def unrealize(self):
739 # remove control groups from input channels
740 for input_channel in self.app.channels:
741 input_channel.remove_control_group(self)
742 # then remove itself
743 Channel.unrealize(self)
744 self.channel.remove()
745 self.channel = None
747 @classmethod
748 def serialization_name(cls):
749 return 'output_channel'
751 def serialize(self, object_backend):
752 object_backend.add_property("name", self.channel_name)
753 if self.stereo:
754 object_backend.add_property("type", "stereo")
755 else:
756 object_backend.add_property("type", "mono")
757 if self.display_solo_buttons:
758 object_backend.add_property("solo_buttons", "true")
759 muted_channels = []
760 solo_channels = []
761 prefader_in_channels = []
762 for input_channel in self.app.channels:
763 if self.channel.is_muted(input_channel.channel):
764 muted_channels.append(input_channel)
765 if self.channel.is_solo(input_channel.channel):
766 solo_channels.append(input_channel)
767 if self.channel.is_in_prefader(input_channel.channel):
768 prefader_in_channels.append(input_channel)
769 if muted_channels:
770 object_backend.add_property('muted_channels', '|'.join([x.channel.name for x in muted_channels]))
771 if solo_channels:
772 object_backend.add_property('solo_channels', '|'.join([x.channel.name for x in solo_channels]))
773 if prefader_in_channels:
774 object_backend.add_property('prefader_channels', '|'.join([x.channel.name for x in prefader_in_channels]))
775 object_backend.add_property("color", self.color.to_string())
776 Channel.serialize(self, object_backend)
778 def unserialize_property(self, name, value):
779 if name == "name":
780 self.channel_name = str(value)
781 return True
782 if name == "type":
783 if value == "stereo":
784 self.stereo = True
785 return True
786 if value == "mono":
787 self.stereo = False
788 return True
789 if name == "solo_buttons":
790 if value == "true":
791 self.display_solo_buttons = True
792 return True
793 if name == 'muted_channels':
794 self._init_muted_channels = value.split('|')
795 return True
796 if name == 'solo_channels':
797 self._init_solo_channels = value.split('|')
798 return True
799 if name == 'prefader_channels':
800 self._init_prefader_channels = value.split('|')
801 return True
802 if name == 'color':
803 c = Gdk.RGBA()
804 c.parse(value)
805 self.color = c
806 return True
807 return Channel.unserialize_property(self, name, value)
809 class ChannelPropertiesDialog(Gtk.Dialog):
810 channel = None
812 def __init__(self, parent, app):
813 self.channel = parent
814 self.app = app
815 self.mixer = self.channel.mixer
816 Gtk.Dialog.__init__(self, 'Channel "%s" Properties' % self.channel.channel_name, app.window)
817 self.set_default_size(365, -1)
819 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
820 self.ok_button = self.add_button(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY)
821 self.set_default_response(Gtk.ResponseType.APPLY);
823 self.create_ui()
824 self.fill_ui()
826 self.connect('response', self.on_response_cb)
827 self.connect('delete-event', self.on_response_cb)
829 def create_frame(self, label, child):
830 frame = Gtk.Frame()
831 frame.set_label('')
832 frame.set_border_width(3)
833 #frame.set_shadow_type(Gtk.ShadowType.NONE)
834 frame.get_label_widget().set_markup('<b>%s</b>' % label)
836 alignment = Gtk.Alignment.new(0, 0, 1, 1)
837 alignment.set_padding(0, 0, 12, 0)
838 frame.add(alignment)
839 alignment.add(child)
841 return frame
843 def create_ui(self):
844 vbox = Gtk.VBox()
845 self.vbox.add(vbox)
847 self.properties_table = table = Gtk.Table(4, 3, False)
848 vbox.pack_start(self.create_frame('Properties', table), True, True, 0)
849 table.set_row_spacings(5)
850 table.set_col_spacings(5)
852 table.attach(Gtk.Label(label='Name'), 0, 1, 0, 1)
853 self.entry_name = Gtk.Entry()
854 self.entry_name.set_activates_default(True)
855 self.entry_name.connect('changed', self.on_entry_name_changed)
856 table.attach(self.entry_name, 1, 2, 0, 1)
858 table.attach(Gtk.Label(label='Mode'), 0, 1, 1, 2)
859 self.mode_hbox = Gtk.HBox()
860 table.attach(self.mode_hbox, 1, 2, 1, 2)
861 self.mono = Gtk.RadioButton(label='Mono')
862 self.stereo = Gtk.RadioButton(label='Stereo', group=self.mono)
863 self.mode_hbox.pack_start(self.mono, True, True, 0)
864 self.mode_hbox.pack_start(self.stereo, True, True, 0)
866 table = Gtk.Table(2, 3, False)
867 vbox.pack_start(self.create_frame('MIDI Control Changes', table), True, True, 0)
868 table.set_row_spacings(5)
869 table.set_col_spacings(5)
871 cc_tooltip = "{} MIDI Control Change number (0-127, set to -1 to assign next free CC #)"
872 table.attach(Gtk.Label(label='Volume'), 0, 1, 0, 1)
873 self.entry_volume_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
874 self.entry_volume_cc.set_tooltip_text(cc_tooltip.format("Volume"))
875 table.attach(self.entry_volume_cc, 1, 2, 0, 1)
876 self.button_sense_midi_volume = Gtk.Button('Learn')
877 self.button_sense_midi_volume.connect('clicked',
878 self.on_sense_midi_volume_clicked)
879 table.attach(self.button_sense_midi_volume, 2, 3, 0, 1)
881 table.attach(Gtk.Label(label='Balance'), 0, 1, 1, 2)
882 self.entry_balance_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
883 self.entry_balance_cc.set_tooltip_text(cc_tooltip.format("Balance"))
884 table.attach(self.entry_balance_cc, 1, 2, 1, 2)
885 self.button_sense_midi_balance = Gtk.Button('Learn')
886 self.button_sense_midi_balance.connect('clicked',
887 self.on_sense_midi_balance_clicked)
888 table.attach(self.button_sense_midi_balance, 2, 3, 1, 2)
890 table.attach(Gtk.Label(label='Mute'), 0, 1, 2, 3)
891 self.entry_mute_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
892 self.entry_mute_cc.set_tooltip_text(cc_tooltip.format("Mute"))
893 table.attach(self.entry_mute_cc, 1, 2, 2, 3)
894 self.button_sense_midi_mute = Gtk.Button('Learn')
895 self.button_sense_midi_mute.connect('clicked',
896 self.on_sense_midi_mute_clicked)
897 table.attach(self.button_sense_midi_mute, 2, 3, 2, 3)
899 if (isinstance(self, NewChannelDialog) or (self.channel and
900 isinstance(self.channel, InputChannel))):
901 table.attach(Gtk.Label(label='Solo'), 0, 1, 3, 4)
902 self.entry_solo_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
903 self.entry_solo_cc.set_tooltip_text(cc_tooltip.format("Solo"))
904 table.attach(self.entry_solo_cc, 1, 2, 3, 4)
905 self.button_sense_midi_solo = Gtk.Button('Learn')
906 self.button_sense_midi_solo.connect('clicked',
907 self.on_sense_midi_solo_clicked)
908 table.attach(self.button_sense_midi_solo, 2, 3, 3, 4)
910 self.vbox.show_all()
912 def fill_ui(self):
913 self.entry_name.set_text(self.channel.channel_name)
914 if self.channel.channel.is_stereo:
915 self.stereo.set_active(True)
916 else:
917 self.mono.set_active(True)
918 self.mode_hbox.set_sensitive(False)
919 self.entry_volume_cc.set_value(self.channel.channel.volume_midi_cc)
920 self.entry_balance_cc.set_value(self.channel.channel.balance_midi_cc)
921 self.entry_mute_cc.set_value(self.channel.channel.mute_midi_cc)
922 if (self.channel and isinstance(self.channel, InputChannel)):
923 self.entry_solo_cc.set_value(self.channel.channel.solo_midi_cc)
925 def sense_popup_dialog(self, entry):
926 window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
927 window.set_destroy_with_parent(True)
928 window.set_transient_for(self)
929 window.set_decorated(False)
930 window.set_modal(True)
931 window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
932 window.set_border_width(10)
934 vbox = Gtk.VBox(10)
935 window.add(vbox)
936 window.timeout = 5
937 vbox.pack_start(Gtk.Label(label='Please move the MIDI control you want to use for this function.'), True, True, 0)
938 timeout_label = Gtk.Label(label='This window will close in 5 seconds')
939 vbox.pack_start(timeout_label, True, True, 0)
940 def close_sense_timeout(window, entry):
941 window.timeout -= 1
942 timeout_label.set_text('This window will close in %d seconds.' % window.timeout)
943 if window.timeout == 0:
944 window.destroy()
945 entry.set_value(self.mixer.last_midi_channel)
946 return False
947 return True
948 window.show_all()
949 GObject.timeout_add_seconds(1, close_sense_timeout, window, entry)
951 def on_sense_midi_volume_clicked(self, *args):
952 self.mixer.last_midi_channel = int(self.entry_volume_cc.get_value())
953 self.sense_popup_dialog(self.entry_volume_cc)
955 def on_sense_midi_balance_clicked(self, *args):
956 self.mixer.last_midi_channel = int(self.entry_balance_cc.get_value())
957 self.sense_popup_dialog(self.entry_balance_cc)
959 def on_sense_midi_mute_clicked(self, *args):
960 self.mixer.last_midi_channel = int(self.entry_mute_cc.get_value())
961 self.sense_popup_dialog(self.entry_mute_cc)
963 def on_sense_midi_solo_clicked(self, *args):
964 self.mixer.last_midi_channel = int(self.entry_solo_cc.get_value())
965 self.sense_popup_dialog(self.entry_solo_cc)
967 def on_response_cb(self, dlg, response_id, *args):
968 self.channel.channel_properties_dialog = None
969 name = self.entry_name.get_text()
970 if response_id == Gtk.ResponseType.APPLY:
971 if name != self.channel.channel_name:
972 self.channel.channel_name = name
973 for control in ('volume', 'balance', 'mute', 'solo'):
974 widget = getattr(self, 'entry_{}_cc'.format(control), None)
975 if widget is not None:
976 value = int(widget.get_value())
977 if value != -1:
978 setattr(self.channel.channel, '{}_midi_cc'.format(control), value)
979 self.destroy()
981 def on_entry_name_changed(self, entry):
982 sensitive = False
983 if len(entry.get_text()):
984 if self.channel and self.channel.channel.name == entry.get_text():
985 sensitive = True
986 elif entry.get_text() not in [x.channel.name for x in self.app.channels] + \
987 [x.channel.name for x in self.app.output_channels] + ['MAIN']:
988 sensitive = True
989 self.ok_button.set_sensitive(sensitive)
992 class NewChannelDialog(ChannelPropertiesDialog):
993 def create_ui(self):
994 ChannelPropertiesDialog.create_ui(self)
995 self.properties_table.attach(Gtk.Label(label='Value'), 0, 1, 2, 3)
997 self.value_hbox = Gtk.HBox()
998 self.properties_table.attach(self.value_hbox, 1, 2, 2, 3)
999 self.minus_inf = Gtk.RadioButton(label='-Inf')
1000 self.zero_dB = Gtk.RadioButton(label='0dB', group=self.minus_inf)
1001 self.value_hbox.pack_start(self.minus_inf, True, True, 0)
1002 self.value_hbox.pack_start(self.zero_dB, True, True, 0)
1004 self.vbox.show_all()
1006 class NewInputChannelDialog(NewChannelDialog):
1007 def __init__(self, app):
1008 Gtk.Dialog.__init__(self, 'New Input Channel', app.window)
1009 self.set_default_size(365, -1)
1010 self.mixer = app.mixer
1011 self.app = app
1012 self.create_ui()
1013 self.fill_ui()
1015 self.stereo.set_active(True) # default to stereo
1017 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1018 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1019 self.ok_button.set_sensitive(False)
1020 self.set_default_response(Gtk.ResponseType.OK);
1022 def fill_ui(self):
1023 self.entry_volume_cc.set_value(-1)
1024 self.entry_balance_cc.set_value(-1)
1025 self.entry_mute_cc.set_value(-1)
1026 self.entry_solo_cc.set_value(-1)
1028 def get_result(self):
1029 log.debug('minus_inf active?: %s', self.zero_dB.get_active())
1030 return {'name': self.entry_name.get_text(),
1031 'stereo': self.stereo.get_active(),
1032 'volume_cc': int(self.entry_volume_cc.get_value()),
1033 'balance_cc': int(self.entry_balance_cc.get_value()),
1034 'mute_cc': int(self.entry_mute_cc.get_value()),
1035 'solo_cc': int(self.entry_solo_cc.get_value()),
1036 'value': self.minus_inf.get_active()
1040 class OutputChannelPropertiesDialog(ChannelPropertiesDialog):
1041 def create_ui(self):
1042 ChannelPropertiesDialog.create_ui(self)
1044 table = self.properties_table
1045 table.attach(Gtk.Label(label='Color'), 0, 1, 4, 5)
1046 self.color_chooser_button = Gtk.ColorButton()
1047 self.color_chooser_button.set_use_alpha(True)
1048 self.color_chooser_button.set_rgba(Gdk.RGBA(0, 0, 0, 0))
1049 table.attach(self.color_chooser_button, 1, 2, 4, 5)
1051 vbox = Gtk.VBox()
1052 self.vbox.pack_start(self.create_frame('Input Channels', vbox), True, True, 0)
1054 self.display_solo_buttons = Gtk.CheckButton('Display solo buttons')
1055 vbox.pack_start(self.display_solo_buttons, True, True, 0)
1057 self.vbox.show_all()
1059 def fill_ui(self):
1060 ChannelPropertiesDialog.fill_ui(self)
1061 self.display_solo_buttons.set_active(self.channel.display_solo_buttons)
1062 self.color_chooser_button.set_rgba(self.channel.color)
1064 def on_response_cb(self, dlg, response_id, *args):
1065 ChannelPropertiesDialog.on_response_cb(self, dlg, response_id, *args)
1066 if response_id == Gtk.ResponseType.APPLY:
1067 self.channel.display_solo_buttons = self.display_solo_buttons.get_active()
1068 self.channel.set_color(self.color_chooser_button.get_rgba())
1069 for inputchannel in self.app.channels:
1070 inputchannel.update_control_group(self.channel)
1073 class NewOutputChannelDialog(NewChannelDialog, OutputChannelPropertiesDialog):
1074 def __init__(self, app):
1075 Gtk.Dialog.__init__(self, 'New Output Channel', app.window)
1076 self.mixer = app.mixer
1077 self.app = app
1078 OutputChannelPropertiesDialog.create_ui(self)
1079 self.fill_ui()
1080 self.set_default_size(365, -1)
1082 # TODO: disable mode for output channels as mono output channels may
1083 # not be correctly handled yet.
1084 self.mode_hbox.set_sensitive(False)
1085 self.stereo.set_active(True) # default to stereo
1087 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1088 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1089 self.ok_button.set_sensitive(False)
1090 self.set_default_response(Gtk.ResponseType.OK);
1092 def fill_ui(self):
1093 self.entry_volume_cc.set_value(-1)
1094 self.entry_balance_cc.set_value(-1)
1095 self.entry_mute_cc.set_value(-1)
1097 def get_result(self):
1098 return {'name': self.entry_name.get_text(),
1099 'stereo': self.stereo.get_active(),
1100 'volume_cc': int(self.entry_volume_cc.get_value()),
1101 'balance_cc': int(self.entry_balance_cc.get_value()),
1102 'mute_cc': int(self.entry_mute_cc.get_value()),
1103 'display_solo_buttons': self.display_solo_buttons.get_active(),
1104 'color': self.color_chooser_button.get_rgba(),
1105 'value': self.minus_inf.get_active()
1109 class ControlGroup(Gtk.Alignment):
1110 def __init__(self, output_channel, input_channel):
1111 GObject.GObject.__init__(self)
1112 self.set(0.5, 0.5, 1, 1)
1113 self.output_channel = output_channel
1114 self.input_channel = input_channel
1115 self.app = input_channel.app
1117 self.hbox = Gtk.HBox()
1118 self.vbox = Gtk.VBox()
1119 self.add(self.vbox)
1120 self.buttons_box = Gtk.Box(False, button_padding)
1122 set_background_color(self.vbox, output_channel.css_name,
1123 output_channel.color.to_string())
1125 self.vbox.pack_start(self.hbox, True, True, button_padding)
1126 css = b"""
1127 .control_group {
1128 min-width: 0px;
1129 padding: 0px;
1132 .control_group #label,
1133 .control_group #mute,
1134 .control_group #pre_fader,
1135 .control_group #solo {
1136 font-size: smaller;
1137 padding: 0px .1em;
1141 css_provider = Gtk.CssProvider()
1142 css_provider.load_from_data(css)
1143 context = Gtk.StyleContext()
1144 screen = Gdk.Screen.get_default()
1145 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
1146 hbox_context = self.hbox.get_style_context()
1147 hbox_context.add_class('control_group')
1149 self.label = Gtk.Label(output_channel.channel.name)
1150 self.label.set_name("label")
1151 self.hbox.pack_start(self.label, False, False, button_padding)
1152 self.hbox.pack_end(self.buttons_box, False, False, button_padding)
1153 mute = Gtk.ToggleButton()
1154 mute.set_label("M")
1155 mute.set_name("mute")
1156 mute.set_tooltip_text("Mute output channel send")
1157 mute.connect("toggled", self.on_mute_toggled)
1158 self.mute = mute
1159 solo = Gtk.ToggleButton()
1160 solo.set_name("solo")
1161 solo.set_label("S")
1162 solo.set_tooltip_text("Solo output send")
1163 solo.connect("toggled", self.on_solo_toggled)
1164 self.solo = solo
1165 pre = Gtk.ToggleButton("P")
1166 pre.set_name("pre_fader")
1167 pre.set_tooltip_text("Pre (on) / Post (off) fader send")
1168 pre.connect("toggled", self.on_prefader_toggled)
1169 self.prefader = pre
1170 self.buttons_box.pack_start(pre, True, True, button_padding)
1171 self.buttons_box.pack_start(mute, True, True, button_padding)
1172 if self.output_channel.display_solo_buttons:
1173 self.buttons_box.pack_start(solo, True, True, button_padding)
1175 def update(self):
1176 if self.output_channel.display_solo_buttons:
1177 if not self.solo in self.buttons_box.get_children():
1178 self.buttons_box.pack_start(self.solo, True, True, button_padding)
1179 self.solo.show()
1180 else:
1181 if self.solo in self.buttons_box.get_children():
1182 self.buttons_box.remove(self.solo)
1184 self.label.set_text(self.output_channel.channel.name)
1185 set_background_color(self.vbox, self.output_channel.css_name, self.output_channel.color.to_string())
1187 def on_mute_toggled(self, button):
1188 self.output_channel.channel.set_muted(self.input_channel.channel, button.get_active())
1189 self.app.update_monitor(self)
1191 def on_solo_toggled(self, button):
1192 self.output_channel.channel.set_solo(self.input_channel.channel, button.get_active())
1193 self.app.update_monitor(self)
1195 def on_prefader_toggled(self, button):
1196 self.output_channel.channel.set_in_prefader(self.input_channel.channel, button.get_active())