Update AUTHORS file and authors list in about dialog (#23)
[jack_mixer.git] / channel.py
blob423be189bb4059c893e5c33b98bb41be2054234b
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 gi
19 from gi.repository import Gtk
20 from gi.repository import Gdk
21 from gi.repository import GObject
22 import slider
23 import meter
24 import abspeak
25 from serialization import SerializedObject
27 try:
28 import phat
29 except:
30 phat = None
32 button_padding = 1
34 css = b"""
35 :not(button) > label {min-width: 100px;}
36 button {padding: 0px}
37 """
39 css_provider = Gtk.CssProvider()
40 css_provider.load_from_data(css)
41 context = Gtk.StyleContext()
42 screen = Gdk.Screen.get_default()
43 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
45 def set_background_color(widget, name, color_string):
46 css = """
47 .%s {
48 background-color: %s
50 """ % (name, color_string)
52 css_provider = Gtk.CssProvider()
53 css_provider.load_from_data(css.encode('utf-8'))
54 context = Gtk.StyleContext()
55 screen = Gdk.Screen.get_default()
56 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
58 widget_context = widget.get_style_context()
59 widget_context.add_class(name)
61 def random_color():
62 from random import uniform, seed
63 seed()
64 return Gdk.RGBA(uniform(0, 1), uniform(0, 1), uniform(0, 1), 1)
66 class Channel(Gtk.VBox, SerializedObject):
67 '''Widget with slider and meter used as base class for more specific
68 channel widgets'''
69 monitor_button = None
70 num_instances = 0
71 def __init__(self, app, name, stereo):
72 Gtk.VBox.__init__(self)
73 self.app = app
74 self.mixer = app.mixer
75 self.gui_factory = app.gui_factory
76 self._channel_name = name
77 self.stereo = stereo
78 self.meter_scale = self.gui_factory.get_default_meter_scale()
79 self.slider_scale = self.gui_factory.get_default_slider_scale()
80 self.slider_adjustment = slider.AdjustmentdBFS(self.slider_scale, 0.0, 0.02)
81 self.balance_adjustment = slider.BalanceAdjustment()
82 self.future_out_mute = None
83 self.future_volume_midi_cc = None
84 self.future_balance_midi_cc = None
85 self.future_mute_midi_cc = None
86 self.future_solo_midi_cc = None
87 self.css_name = "css_name_%d" % Channel.num_instances
88 Channel.num_instances += 1
90 def get_channel_name(self):
91 return self._channel_name
93 label_name = None
94 channel = None
95 post_fader_output_channel = None
96 def set_channel_name(self, name):
97 self.app.on_channel_rename(self._channel_name, name);
98 self._channel_name = name
99 if self.label_name:
100 self.label_name.set_text(name)
101 if self.channel:
102 self.channel.name = name
103 if self.post_fader_output_channel:
104 self.post_fader_output_channel.name = "%s Out" % name;
105 channel_name = property(get_channel_name, set_channel_name)
107 def realize(self):
108 #print "Realizing channel \"%s\"" % self.channel_name
109 if self.future_out_mute != None:
110 self.channel.out_mute = self.future_out_mute
112 self.slider_adjustment.connect("volume-changed", self.on_volume_changed)
113 self.balance_adjustment.connect("balance-changed", self.on_balance_changed)
115 self.slider = None
116 self.create_slider_widget()
118 if self.stereo:
119 self.meter = meter.StereoMeterWidget(self.meter_scale)
120 else:
121 self.meter = meter.MonoMeterWidget(self.meter_scale)
122 self.on_vumeter_color_changed(self.gui_factory)
124 self.meter.set_events(Gdk.EventMask.SCROLL_MASK)
126 self.gui_factory.connect("default-meter-scale-changed", self.on_default_meter_scale_changed)
127 self.gui_factory.connect("default-slider-scale-changed", self.on_default_slider_scale_changed)
128 self.gui_factory.connect('vumeter-color-changed', self.on_vumeter_color_changed)
129 self.gui_factory.connect('vumeter-color-scheme-changed', self.on_vumeter_color_changed)
130 self.gui_factory.connect('use-custom-widgets-changed', self.on_custom_widgets_changed)
132 self.abspeak = abspeak.AbspeakWidget()
133 self.abspeak.connect("reset", self.on_abspeak_reset)
134 self.abspeak.connect("volume-adjust", self.on_abspeak_adjust)
136 self.volume_digits = Gtk.Entry()
137 self.volume_digits.set_property('xalign', 0.5)
138 self.volume_digits.connect("key-press-event", self.on_volume_digits_key_pressed)
139 self.volume_digits.connect("focus-out-event", self.on_volume_digits_focus_out)
141 self.connect("key-press-event", self.on_key_pressed)
142 self.connect("scroll-event", self.on_scroll)
144 def unrealize(self):
145 #print "Unrealizing channel \"%s\"" % self.channel_name
146 pass
148 def balance_preferred_width(self):
149 return (20, 20)
151 def _preferred_height(self):
152 return (0, 100)
154 def create_balance_widget(self):
155 if self.gui_factory.use_custom_widgets and phat:
156 self.balance = phat.HFanSlider()
157 self.balance.set_default_value(0)
158 self.balance.set_adjustment(self.balance_adjustment)
159 else:
160 self.balance = Gtk.Scale()
161 self.balance.get_preferred_width = self.balance_preferred_width
162 self.balance.get_preferred_height = self._preferred_height
163 self.balance.set_orientation(Gtk.Orientation.HORIZONTAL)
164 self.balance.set_adjustment(self.balance_adjustment)
165 self.balance.set_has_origin(False)
166 self.balance.set_draw_value(False)
167 self.balance.button_down = False
168 self.balance.connect('button-press-event', self.on_balance_button_press_event)
169 self.balance.connect('button-release-event', self.on_balance_button_release_event)
170 self.balance.connect("motion-notify-event", self.on_balance_motion_notify_event)
171 self.balance.connect("scroll-event", self.on_balance_scroll_event)
174 self.pack_start(self.balance, False, True, 0)
175 if self.monitor_button:
176 self.reorder_child(self.monitor_button, -1)
177 self.balance.show()
179 def on_balance_button_press_event(self, widget, event):
180 if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
181 self.balance.button_down = True
182 self.balance.button_down_x = event.x
183 self.balance.button_down_value = self.balance.get_value()
184 return True
185 if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
186 self.balance_adjustment.set_balance(0)
187 return True
188 return False
190 def on_balance_button_release_event(self, widget, event):
191 self.balance.button_down = False
192 return False
194 def on_balance_motion_notify_event(self, widget, event):
195 slider_length = widget.get_allocation().width - widget.get_style_context().get_property('min-width', Gtk.StateFlags.NORMAL)
196 if self.balance.button_down:
197 delta_x = (event.x - self.balance.button_down_x) / slider_length
198 x = self.balance.button_down_value + 2 * delta_x
199 if x >= 1:
200 x = 1
201 elif x <= -1:
202 x = -1
203 self.balance_adjustment.set_balance(x)
204 return True
206 def on_balance_scroll_event(self, widget, event):
207 bal = self.balance
208 delta = bal.get_adjustment().get_step_increment()
209 value = bal.get_value()
210 if event.direction == Gdk.ScrollDirection.UP:
211 x = value - delta
212 elif event.direction == Gdk.ScrollDirection.DOWN:
213 x = value + delta
214 elif event.direction == Gdk.ScrollDirection.SMOOTH:
215 x = value - event.delta_y * delta
217 if x >= 1:
218 x = 1
219 elif x <= -1:
220 x = -1
221 bal.set_value(x)
222 return True
224 def create_slider_widget(self):
225 parent = None
226 if self.slider:
227 parent = self.slider.get_parent()
228 self.slider.destroy()
229 if self.gui_factory.use_custom_widgets:
230 self.slider = slider.CustomSliderWidget(self.slider_adjustment)
231 else:
232 self.slider = slider.GtkSlider(self.slider_adjustment)
233 if parent:
234 parent.pack_start(self.slider, True, True, 0)
235 parent.reorder_child(self.slider, 0)
236 self.slider.show()
238 def on_default_meter_scale_changed(self, gui_factory, scale):
239 #print "Default meter scale change detected."
240 self.meter.set_scale(scale)
242 def on_default_slider_scale_changed(self, gui_factory, scale):
243 #print "Default slider scale change detected."
244 self.slider_scale = scale
245 self.slider_adjustment.set_scale(scale)
246 self.channel.midi_scale = self.slider_scale.scale
248 def on_vumeter_color_changed(self, gui_factory, *args):
249 color = gui_factory.get_vumeter_color()
250 color_scheme = gui_factory.get_vumeter_color_scheme()
251 if color_scheme != 'solid':
252 self.meter.set_color(None)
253 else:
254 self.meter.set_color(Gdk.color_parse(color))
256 def on_custom_widgets_changed(self, gui_factory, value):
257 self.balance.destroy()
258 self.create_balance_widget()
259 self.create_slider_widget()
261 def on_abspeak_adjust(self, abspeak, adjust):
262 #print "abspeak adjust %f" % adjust
263 self.slider_adjustment.set_value_db(self.slider_adjustment.get_value_db() + adjust)
264 self.channel.abspeak = None
265 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
267 def on_abspeak_reset(self, abspeak):
268 #print "abspeak reset"
269 self.channel.abspeak = None
271 def on_volume_digits_key_pressed(self, widget, event):
272 if (event.keyval == Gdk.KEY_Return or event.keyval == Gdk.KEY_KP_Enter):
273 db_text = self.volume_digits.get_text()
274 try:
275 db = float(db_text)
276 #print "Volume digits confirmation \"%f dBFS\"" % db
277 except (ValueError) as e:
278 #print "Volume digits confirmation ignore, reset to current"
279 self.update_volume(False)
280 return
281 self.slider_adjustment.set_value_db(db)
282 #self.grab_focus()
283 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
285 def on_volume_digits_focus_out(self, widget, event):
286 #print "volume digits focus out detected"
287 self.update_volume(False)
289 def read_meter(self):
290 if not self.channel:
291 return
292 if self.stereo:
293 meter_left, meter_right = self.channel.meter
294 self.meter.set_values(meter_left, meter_right)
295 else:
296 self.meter.set_value(self.channel.meter[0])
298 self.abspeak.set_peak(self.channel.abspeak)
300 def on_scroll(self, widget, event):
301 if event.direction == Gdk.ScrollDirection.DOWN:
302 self.slider_adjustment.step_down()
303 elif event.direction == Gdk.ScrollDirection.UP:
304 self.slider_adjustment.step_up()
305 return True
307 def update_volume(self, update_engine, from_midi = False):
308 db = self.slider_adjustment.get_value_db()
310 db_text = "%.2f" % db
311 self.volume_digits.set_text(db_text)
313 if update_engine:
314 if not from_midi:
315 self.channel.volume = db
316 else:
317 self.channel.set_volume_from_midi(db)
318 self.app.update_monitor(self)
320 def on_volume_changed(self, adjustment):
321 self.update_volume(True)
323 def on_volume_changed_from_midi(self, adjustment):
324 self.update_volume(True, from_midi = True)
326 def on_balance_changed(self, adjustment):
327 balance = self.balance_adjustment.get_value()
328 #print("%s balance: %f" % (self.channel_name, balance))
329 self.channel.balance = balance
330 self.app.update_monitor(self)
332 def on_volume_changed_from_midi(self, adjustment):
333 balance = self.balance_adjustment.get_value()
334 #print("%s balance from midi: %f" % (self.channel_name, balance))
335 self.channel.set_balance_from_midi(balance)
336 self.app.update_monitor(self)
338 def on_key_pressed(self, widget, event):
339 if (event.keyval == Gdk.KEY_Up):
340 #print self.channel_name + " Up"
341 self.slider_adjustment.step_up()
342 return True
343 elif (event.keyval == Gdk.KEY_Down):
344 #print self.channel_name + " Down"
345 self.slider_adjustment.step_down()
346 return True
348 return False
350 def serialize(self, object_backend):
351 object_backend.add_property("volume", "%f" % self.slider_adjustment.get_value_db())
352 object_backend.add_property("balance", "%f" % self.balance_adjustment.get_value())
354 if hasattr(self.channel, 'out_mute'):
355 object_backend.add_property('out_mute', str(self.channel.out_mute))
356 if self.channel.volume_midi_cc != -1:
357 object_backend.add_property('volume_midi_cc', str(self.channel.volume_midi_cc))
358 if self.channel.balance_midi_cc != -1:
359 object_backend.add_property('balance_midi_cc', str(self.channel.balance_midi_cc))
360 if self.channel.mute_midi_cc != -1:
361 object_backend.add_property('mute_midi_cc', str(self.channel.mute_midi_cc))
362 if self.channel.solo_midi_cc != -1:
363 object_backend.add_property('solo_midi_cc', str(self.channel.solo_midi_cc))
366 def unserialize_property(self, name, value):
367 if name == "volume":
368 self.slider_adjustment.set_value_db(float(value))
369 return True
370 if name == "balance":
371 self.balance_adjustment.set_value(float(value))
372 return True
373 if name == 'out_mute':
374 self.future_out_mute = (value == 'True')
375 return True
376 if name == 'volume_midi_cc':
377 self.future_volume_midi_cc = int(value)
378 return True
379 if name == 'balance_midi_cc':
380 self.future_balance_midi_cc = int(value)
381 return True
382 if name == 'mute_midi_cc':
383 self.future_mute_midi_cc = int(value)
384 return True
385 if name == 'solo_midi_cc':
386 self.future_solo_midi_cc = int(value)
387 return True
388 return False
390 def on_midi_event_received(self, *args):
391 self.slider_adjustment.set_value_db(self.channel.volume, from_midi = True)
392 self.balance_adjustment.set_balance(self.channel.balance, from_midi = True)
394 def on_monitor_button_toggled(self, button):
395 if button.get_active():
396 for channel in self.app.channels + self.app.output_channels:
397 if channel.monitor_button.get_active() and channel.monitor_button is not button:
398 channel.monitor_button.handler_block_by_func(
399 channel.on_monitor_button_toggled)
400 channel.monitor_button.set_active(False)
401 channel.monitor_button.handler_unblock_by_func(
402 channel.on_monitor_button_toggled)
403 self.app.set_monitored_channel(self)
405 def set_monitored(self):
406 if self.channel:
407 self.app.set_monitored_channel(self)
408 self.monitor_button.set_active(True)
410 def set_color(self, color):
411 self.color = color
412 set_background_color(self.label_name_event_box, self.css_name, self.color.to_string())
414 class InputChannel(Channel):
415 post_fader_output_channel = None
417 def __init__(self, app, name, stereo):
418 Channel.__init__(self, app, name, stereo)
420 def realize(self):
421 self.channel = self.mixer.add_channel(self.channel_name, self.stereo)
423 if self.channel == None:
424 raise Exception("Cannot create a channel")
425 Channel.realize(self)
426 if self.future_volume_midi_cc != None:
427 self.channel.volume_midi_cc = self.future_volume_midi_cc
428 if self.future_balance_midi_cc != None:
429 self.channel.balance_midi_cc = self.future_balance_midi_cc
430 if self.future_mute_midi_cc != None:
431 self.channel.mute_midi_cc = self.future_mute_midi_cc
432 if self.future_solo_midi_cc != None:
433 self.channel.solo_midi_cc = self.future_solo_midi_cc
434 if self.app._init_solo_channels and self.channel_name in self.app._init_solo_channels:
435 self.channel.solo = True
437 self.channel.midi_scale = self.slider_scale.scale
439 self.on_volume_changed(self.slider_adjustment)
440 self.on_balance_changed(self.balance_adjustment)
442 # vbox child at upper part
443 self.vbox = Gtk.VBox()
444 self.pack_start(self.vbox, False, True, 0)
445 self.label_name = Gtk.Label()
446 self.label_name.set_text(self.channel_name)
447 self.label_name.set_width_chars(0)
448 self.label_name_event_box = Gtk.EventBox()
449 self.label_name_event_box.connect("button-press-event", self.on_label_mouse)
450 self.label_name_event_box.add(self.label_name)
451 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
452 # self.label_stereo = Gtk.Label()
453 # if self.stereo:
454 # self.label_stereo.set_text("stereo")
455 # else:
456 # self.label_stereo.set_text("mono")
457 # self.label_stereo.set_size_request(0, -1)
458 # self.vbox.pack_start(self.label_stereo, True)
460 self.hbox_mutesolo = Gtk.HBox()
461 vbox_mutesolo = Gtk.VBox()
462 vbox_mutesolo.pack_start(self.hbox_mutesolo, True, True, button_padding)
463 self.vbox.pack_start(vbox_mutesolo, True, True, 0)
465 self.mute = Gtk.ToggleButton()
466 self.mute.set_label("M")
467 self.mute.set_name("mute")
468 self.mute.set_active(self.channel.out_mute)
469 self.mute.connect("toggled", self.on_mute_toggled)
470 self.hbox_mutesolo.pack_start(self.mute, True, True, button_padding)
472 self.solo = Gtk.ToggleButton()
473 self.solo.set_label("S")
474 self.solo.set_name("solo")
475 self.solo.set_active(self.channel.solo)
476 self.solo.connect("toggled", self.on_solo_toggled)
477 self.hbox_mutesolo.pack_start(self.solo, True, True, button_padding)
479 self.vbox.pack_start(self.hbox_mutesolo, True, True, 0)
481 frame = Gtk.Frame()
482 frame.set_shadow_type(Gtk.ShadowType.IN)
483 frame.add(self.abspeak);
484 self.pack_start(frame, False, True, 0)
486 # hbox child at lower part
487 self.hbox = Gtk.HBox()
488 self.hbox.pack_start(self.slider, True, True, 0)
489 frame = Gtk.Frame()
490 frame.set_shadow_type(Gtk.ShadowType.IN)
491 frame.add(self.meter);
492 self.hbox.pack_start(frame, True, True, 0)
493 frame = Gtk.Frame()
494 frame.set_shadow_type(Gtk.ShadowType.IN)
495 frame.add(self.hbox);
496 self.pack_start(frame, True, True, 0)
498 self.volume_digits.set_width_chars(6)
499 self.pack_start(self.volume_digits, False, False, 0)
501 self.create_balance_widget()
503 self.monitor_button = Gtk.ToggleButton('MON')
504 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
505 self.pack_start(self.monitor_button, False, False, 0)
507 def add_control_group(self, channel):
508 control_group = ControlGroup(channel, self)
509 control_group.show_all()
510 self.vbox.pack_start(control_group, True, True, 0)
511 return control_group
513 def remove_control_group(self, channel):
514 ctlgroup = self.get_control_group(channel)
515 self.vbox.remove(ctlgroup)
517 def update_control_group(self, channel):
518 for control_group in self.vbox.get_children():
519 if isinstance(control_group, ControlGroup):
520 if control_group.output_channel is channel:
521 control_group.update()
523 def get_control_group(self, channel):
524 for control_group in self.vbox.get_children():
525 if isinstance(control_group, ControlGroup):
526 if control_group.output_channel is channel:
527 return control_group
528 return None
530 def unrealize(self):
531 Channel.unrealize(self)
532 if self.post_fader_output_channel:
533 self.post_fader_output_channel.remove()
534 self.post_fader_output_channel = None
535 self.channel.remove()
536 self.channel = None
538 channel_properties_dialog = None
540 def on_channel_properties(self):
541 if not self.channel_properties_dialog:
542 self.channel_properties_dialog = ChannelPropertiesDialog(self, self.app)
543 self.channel_properties_dialog.show()
544 self.channel_properties_dialog.present()
546 def on_label_mouse(self, widget, event):
547 if event.type == Gdk.EventType._2BUTTON_PRESS:
548 if event.button == 1:
549 self.on_channel_properties()
551 def on_mute_toggled(self, button):
552 self.channel.out_mute = self.mute.get_active()
554 def on_solo_toggled(self, button):
555 self.channel.solo = self.solo.get_active()
557 def midi_events_check(self):
558 if hasattr(self, 'channel') and self.channel.midi_in_got_events:
559 self.mute.set_active(self.channel.out_mute)
560 self.solo.set_active(self.channel.solo)
561 Channel.on_midi_event_received(self)
563 def on_solo_button_pressed(self, button, event, *args):
564 if event.button == 3:
565 # right click on the solo button, act on all output channels
566 if button.get_active(): # was soloed
567 button.set_active(False)
568 if hasattr(button, 'touched_channels'):
569 touched_channels = button.touched_channels
570 for chan in touched_channels:
571 ctlgroup = self.get_control_group(chan)
572 ctlgroup.solo.set_active(False)
573 del button.touched_channels
574 else: # was not soloed
575 button.set_active(True)
576 touched_channels = []
577 for chan in self.app.output_channels:
578 ctlgroup = self.get_control_group(chan)
579 if not ctlgroup.solo.get_active():
580 ctlgroup.solo.set_active(True)
581 touched_channels.append(chan)
582 button.touched_channels = touched_channels
583 return True
584 return False
586 @classmethod
587 def serialization_name(cls):
588 return 'input_channel'
590 def serialize(self, object_backend):
591 object_backend.add_property("name", self.channel_name)
592 if self.stereo:
593 object_backend.add_property("type", "stereo")
594 else:
595 object_backend.add_property("type", "mono")
596 Channel.serialize(self, object_backend)
598 def unserialize_property(self, name, value):
599 if name == "name":
600 self.channel_name = str(value)
601 return True
602 if name == "type":
603 if value == "stereo":
604 self.stereo = True
605 return True
606 if value == "mono":
607 self.stereo = False
608 return True
609 return Channel.unserialize_property(self, name, value)
611 class OutputChannel(Channel):
612 _display_solo_buttons = False
614 _init_muted_channels = None
615 _init_solo_channels = None
617 def __init__(self, app, name, stereo):
618 Channel.__init__(self, app, name, stereo)
620 def get_display_solo_buttons(self):
621 return self._display_solo_buttons
623 def set_display_solo_buttons(self, value):
624 self._display_solo_buttons = value
625 # notifying control groups
626 for inputchannel in self.app.channels:
627 inputchannel.update_control_group(self)
629 display_solo_buttons = property(get_display_solo_buttons, set_display_solo_buttons)
631 def realize(self):
632 self.channel = self.mixer.add_output_channel(self.channel_name, self.stereo)
633 if self.channel == None:
634 raise Exception("Cannot create a channel")
635 Channel.realize(self)
636 if self.future_volume_midi_cc != None:
637 self.channel.volume_midi_cc = self.future_volume_midi_cc
638 if self.future_balance_midi_cc != None:
639 self.channel.balance_midi_cc = self.future_balance_midi_cc
640 if self.future_mute_midi_cc != None:
641 self.channel.mute_midi_cc = self.future_mute_midi_cc
642 self.channel.midi_scale = self.slider_scale.scale
644 self.on_volume_changed(self.slider_adjustment)
645 self.on_balance_changed(self.balance_adjustment)
647 # vbox child at upper part
648 self.vbox = Gtk.VBox()
649 self.pack_start(self.vbox, False, True, 0)
650 self.label_name = Gtk.Label()
651 self.label_name.set_text(self.channel_name)
652 self.label_name.set_width_chars(0)
653 self.label_name_event_box = Gtk.EventBox()
654 self.label_name_event_box.connect('button-press-event', self.on_label_mouse)
655 self.label_name_event_box.add(self.label_name)
656 if not hasattr(self, 'color'):
657 self.color = random_color()
658 set_background_color(self.label_name_event_box, self.css_name,
659 self.color.to_string())
660 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
661 self.mute = Gtk.ToggleButton()
662 self.mute.set_label("M")
663 self.mute.set_name("mute")
664 self.mute.set_active(self.channel.out_mute)
665 self.mute.connect("toggled", self.on_mute_toggled)
666 hbox = Gtk.HBox()
667 hbox.pack_start(self.mute, True, True, button_padding)
668 self.vbox.pack_start(hbox, True, True, button_padding)
670 frame = Gtk.Frame()
671 frame.set_shadow_type(Gtk.ShadowType.IN)
672 frame.add(self.abspeak);
673 self.vbox.pack_start(frame, False, True, 0)
675 # hbox child at lower part
676 self.hbox = Gtk.HBox()
677 self.hbox.pack_start(self.slider, True, True, 0)
678 frame = Gtk.Frame()
679 frame.set_shadow_type(Gtk.ShadowType.IN)
680 frame.add(self.meter);
681 self.hbox.pack_start(frame, True, True, 0)
682 frame = Gtk.Frame()
683 frame.set_shadow_type(Gtk.ShadowType.IN)
684 frame.add(self.hbox);
685 self.pack_start(frame, True, True, 0)
687 self.volume_digits.set_width_chars(6)
688 self.pack_start(self.volume_digits, False, True, 0)
690 self.create_balance_widget()
692 self.monitor_button = Gtk.ToggleButton('MON')
693 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
694 self.pack_start(self.monitor_button, False, False, 0)
696 # add control groups to the input channels, and initialize them
697 # appropriately
698 for input_channel in self.app.channels:
699 ctlgroup = input_channel.add_control_group(self)
700 if self._init_muted_channels and input_channel.channel.name in self._init_muted_channels:
701 ctlgroup.mute.set_active(True)
702 if self._init_solo_channels and input_channel.channel.name in self._init_solo_channels:
703 ctlgroup.solo.set_active(True)
704 self._init_muted_channels = None
705 self._init_solo_channels = None
707 channel_properties_dialog = None
708 def on_channel_properties(self):
709 if not self.channel_properties_dialog:
710 self.channel_properties_dialog = OutputChannelPropertiesDialog(self, self.app)
711 self.channel_properties_dialog.show()
712 self.channel_properties_dialog.present()
714 def on_label_mouse(self, widget, event):
715 if event.type == Gdk.EventType._2BUTTON_PRESS:
716 if event.button == 1:
717 self.on_channel_properties()
719 def on_mute_toggled(self, button):
720 self.channel.out_mute = self.mute.get_active()
722 def midi_events_check(self):
723 if self.channel != None and self.channel.midi_in_got_events:
724 self.mute.set_active(self.channel.out_mute)
725 Channel.on_midi_event_received(self)
727 def unrealize(self):
728 # remove control groups from input channels
729 for input_channel in self.app.channels:
730 input_channel.remove_control_group(self)
731 # then remove itself
732 Channel.unrealize(self)
733 self.channel.remove()
734 self.channel = None
736 @classmethod
737 def serialization_name(cls):
738 return 'output_channel'
740 def serialize(self, object_backend):
741 object_backend.add_property("name", self.channel_name)
742 if self.stereo:
743 object_backend.add_property("type", "stereo")
744 else:
745 object_backend.add_property("type", "mono")
746 if self.display_solo_buttons:
747 object_backend.add_property("solo_buttons", "true")
748 muted_channels = []
749 solo_channels = []
750 for input_channel in self.app.channels:
751 if self.channel.is_muted(input_channel.channel):
752 muted_channels.append(input_channel)
753 if self.channel.is_solo(input_channel.channel):
754 solo_channels.append(input_channel)
755 if muted_channels:
756 object_backend.add_property('muted_channels', '|'.join([x.channel.name for x in muted_channels]))
757 if solo_channels:
758 object_backend.add_property('solo_channels', '|'.join([x.channel.name for x in solo_channels]))
759 object_backend.add_property("color", self.color.to_string())
760 Channel.serialize(self, object_backend)
762 def unserialize_property(self, name, value):
763 if name == "name":
764 self.channel_name = str(value)
765 return True
766 if name == "type":
767 if value == "stereo":
768 self.stereo = True
769 return True
770 if value == "mono":
771 self.stereo = False
772 return True
773 if name == "solo_buttons":
774 if value == "true":
775 self.display_solo_buttons = True
776 return True
777 if name == 'muted_channels':
778 self._init_muted_channels = value.split('|')
779 return True
780 if name == 'solo_channels':
781 self._init_solo_channels = value.split('|')
782 return True
783 if name == 'color':
784 c = Gdk.RGBA()
785 c.parse(value)
786 self.color = c
787 return True
788 return Channel.unserialize_property(self, name, value)
790 class ChannelPropertiesDialog(Gtk.Dialog):
791 channel = None
793 def __init__(self, parent, app):
794 self.channel = parent
795 self.app = app
796 self.mixer = self.channel.mixer
797 Gtk.Dialog.__init__(self, 'Channel "%s" Properties' % self.channel.channel_name, app.window)
799 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
800 self.ok_button = self.add_button(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY)
801 self.set_default_response(Gtk.ResponseType.APPLY);
803 self.create_ui()
804 self.fill_ui()
806 self.connect('response', self.on_response_cb)
807 self.connect('delete-event', self.on_response_cb)
809 def create_frame(self, label, child):
810 frame = Gtk.Frame()
811 frame.set_label('')
812 frame.set_border_width(3)
813 #frame.set_shadow_type(Gtk.ShadowType.NONE)
814 frame.get_label_widget().set_markup('<b>%s</b>' % label)
816 alignment = Gtk.Alignment.new(0, 0, 1, 1)
817 alignment.set_padding(0, 0, 12, 0)
818 frame.add(alignment)
819 alignment.add(child)
821 return frame
823 def create_ui(self):
824 vbox = Gtk.VBox()
825 self.vbox.add(vbox)
827 self.properties_table = table = Gtk.Table(3, 3, False)
828 vbox.pack_start(self.create_frame('Properties', table), True, True, 0)
829 table.set_row_spacings(5)
830 table.set_col_spacings(5)
832 table.attach(Gtk.Label(label='Name'), 0, 1, 0, 1)
833 self.entry_name = Gtk.Entry()
834 self.entry_name.set_activates_default(True)
835 self.entry_name.connect('changed', self.on_entry_name_changed)
836 table.attach(self.entry_name, 1, 2, 0, 1)
838 table.attach(Gtk.Label(label='Mode'), 0, 1, 1, 2)
839 self.mode_hbox = Gtk.HBox()
840 table.attach(self.mode_hbox, 1, 2, 1, 2)
841 self.mono = Gtk.RadioButton(label='Mono')
842 self.stereo = Gtk.RadioButton(label='Stereo', group=self.mono)
843 self.mode_hbox.pack_start(self.mono, True, True, 0)
844 self.mode_hbox.pack_start(self.stereo, True, True, 0)
846 table = Gtk.Table(2, 3, False)
847 vbox.pack_start(self.create_frame('MIDI Control Channels', table), True, True, 0)
848 table.set_row_spacings(5)
849 table.set_col_spacings(5)
851 cc_tooltip = "{} MIDI Control Change number (0-127, set to -1 to assign next free CC #)"
852 table.attach(Gtk.Label(label='Volume'), 0, 1, 0, 1)
853 self.entry_volume_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
854 self.entry_volume_cc.set_tooltip_text(cc_tooltip.format("Volume"))
855 table.attach(self.entry_volume_cc, 1, 2, 0, 1)
856 self.button_sense_midi_volume = Gtk.Button('Learn')
857 self.button_sense_midi_volume.connect('clicked',
858 self.on_sense_midi_volume_clicked)
859 table.attach(self.button_sense_midi_volume, 2, 3, 0, 1)
861 table.attach(Gtk.Label(label='Balance'), 0, 1, 1, 2)
862 self.entry_balance_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
863 self.entry_balance_cc.set_tooltip_text(cc_tooltip.format("Balance"))
864 table.attach(self.entry_balance_cc, 1, 2, 1, 2)
865 self.button_sense_midi_balance = Gtk.Button('Learn')
866 self.button_sense_midi_balance.connect('clicked',
867 self.on_sense_midi_balance_clicked)
868 table.attach(self.button_sense_midi_balance, 2, 3, 1, 2)
870 table.attach(Gtk.Label(label='Mute'), 0, 1, 2, 3)
871 self.entry_mute_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
872 self.entry_mute_cc.set_tooltip_text(cc_tooltip.format("Mute"))
873 table.attach(self.entry_mute_cc, 1, 2, 2, 3)
874 self.button_sense_midi_mute = Gtk.Button('Learn')
875 self.button_sense_midi_mute.connect('clicked',
876 self.on_sense_midi_mute_clicked)
877 table.attach(self.button_sense_midi_mute, 2, 3, 2, 3)
879 if (isinstance(self, NewChannelDialog) or (self.channel and
880 isinstance(self.channel, InputChannel))):
881 table.attach(Gtk.Label(label='Solo'), 0, 1, 3, 4)
882 self.entry_solo_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
883 self.entry_solo_cc.set_tooltip_text(cc_tooltip.format("Solo"))
884 table.attach(self.entry_solo_cc, 1, 2, 3, 4)
885 self.button_sense_midi_solo = Gtk.Button('Learn')
886 self.button_sense_midi_solo.connect('clicked',
887 self.on_sense_midi_solo_clicked)
888 table.attach(self.button_sense_midi_solo, 2, 3, 3, 4)
890 self.vbox.show_all()
892 def fill_ui(self):
893 self.entry_name.set_text(self.channel.channel_name)
894 if self.channel.channel.is_stereo:
895 self.stereo.set_active(True)
896 else:
897 self.mono.set_active(True)
898 self.mode_hbox.set_sensitive(False)
899 self.entry_volume_cc.set_value(self.channel.channel.volume_midi_cc)
900 self.entry_balance_cc.set_value(self.channel.channel.balance_midi_cc)
901 self.entry_mute_cc.set_value(self.channel.channel.mute_midi_cc)
902 if (self.channel and isinstance(self.channel, InputChannel)):
903 self.entry_solo_cc.set_value(self.channel.channel.solo_midi_cc)
905 def sense_popup_dialog(self, entry):
906 window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
907 window.set_destroy_with_parent(True)
908 window.set_transient_for(self)
909 window.set_decorated(False)
910 window.set_modal(True)
911 window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
912 window.set_border_width(10)
914 vbox = Gtk.VBox(10)
915 window.add(vbox)
916 window.timeout = 5
917 vbox.pack_start(Gtk.Label(label='Please move the MIDI control you want to use for this function.'), True, True, 0)
918 timeout_label = Gtk.Label(label='This window will close in 5 seconds')
919 vbox.pack_start(timeout_label, True, True, 0)
920 def close_sense_timeout(window, entry):
921 window.timeout -= 1
922 timeout_label.set_text('This window will close in %d seconds.' % window.timeout)
923 if window.timeout == 0:
924 window.destroy()
925 entry.set_value(self.mixer.last_midi_channel)
926 return False
927 return True
928 window.show_all()
929 GObject.timeout_add_seconds(1, close_sense_timeout, window, entry)
931 def on_sense_midi_volume_clicked(self, *args):
932 self.mixer.last_midi_channel = int(self.entry_volume_cc.get_value())
933 self.sense_popup_dialog(self.entry_volume_cc)
935 def on_sense_midi_balance_clicked(self, *args):
936 self.mixer.last_midi_channel = int(self.entry_balance_cc.get_value())
937 self.sense_popup_dialog(self.entry_balance_cc)
939 def on_sense_midi_mute_clicked(self, *args):
940 self.mixer.last_midi_channel = int(self.entry_mute_cc.get_value())
941 self.sense_popup_dialog(self.entry_mute_cc)
943 def on_sense_midi_solo_clicked(self, *args):
944 self.mixer.last_midi_channel = int(self.entry_solo_cc.get_value())
945 self.sense_popup_dialog(self.entry_solo_cc)
947 def on_response_cb(self, dlg, response_id, *args):
948 self.channel.channel_properties_dialog = None
949 name = self.entry_name.get_text()
950 if response_id == Gtk.ResponseType.APPLY:
951 if name != self.channel.channel_name:
952 self.channel.channel_name = name
953 for control in ('volume', 'balance', 'mute', 'solo'):
954 widget = getattr(self, 'entry_{}_cc'.format(control), None)
955 if widget is not None:
956 value = int(widget.get_value())
957 if value != -1:
958 setattr(self.channel.channel, '{}_midi_cc'.format(control), value)
959 self.destroy()
961 def on_entry_name_changed(self, entry):
962 sensitive = False
963 if len(entry.get_text()):
964 if self.channel and self.channel.channel.name == entry.get_text():
965 sensitive = True
966 elif entry.get_text() not in [x.channel.name for x in self.app.channels] + \
967 [x.channel.name for x in self.app.output_channels] + ['MAIN']:
968 sensitive = True
969 self.ok_button.set_sensitive(sensitive)
972 class NewChannelDialog(ChannelPropertiesDialog):
973 def __init__(self, app):
974 Gtk.Dialog.__init__(self, 'New Channel', app.window)
975 self.mixer = app.mixer
976 self.app = app
977 self.create_ui()
978 self.fill_ui()
980 self.stereo.set_active(True) # default to stereo
982 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
983 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
984 self.ok_button.set_sensitive(False)
985 self.set_default_response(Gtk.ResponseType.OK);
987 def fill_ui(self):
988 self.entry_volume_cc.set_value(-1)
989 self.entry_balance_cc.set_value(-1)
990 self.entry_mute_cc.set_value(-1)
991 self.entry_solo_cc.set_value(-1)
993 def get_result(self):
994 return {'name': self.entry_name.get_text(),
995 'stereo': self.stereo.get_active(),
996 'volume_cc': int(self.entry_volume_cc.get_value()),
997 'balance_cc': int(self.entry_balance_cc.get_value()),
998 'mute_cc': int(self.entry_mute_cc.get_value()),
999 'solo_cc': int(self.entry_solo_cc.get_value())
1002 class OutputChannelPropertiesDialog(ChannelPropertiesDialog):
1003 def create_ui(self):
1004 ChannelPropertiesDialog.create_ui(self)
1006 table = self.properties_table
1007 table.attach(Gtk.Label(label='Color'), 0, 1, 2, 3)
1008 self.color_chooser_button = Gtk.ColorButton()
1009 table.attach(self.color_chooser_button, 1, 2, 2, 3)
1012 vbox = Gtk.VBox()
1013 self.vbox.pack_start(self.create_frame('Input Channels', vbox), True, True, 0)
1015 self.display_solo_buttons = Gtk.CheckButton('Display solo buttons')
1016 vbox.pack_start(self.display_solo_buttons, True, True, 0)
1018 self.vbox.show_all()
1020 def fill_ui(self):
1021 ChannelPropertiesDialog.fill_ui(self)
1022 self.display_solo_buttons.set_active(self.channel.display_solo_buttons)
1023 self.color_chooser_button.set_rgba(self.channel.color)
1025 def on_response_cb(self, dlg, response_id, *args):
1026 ChannelPropertiesDialog.on_response_cb(self, dlg, response_id, *args)
1027 if response_id == Gtk.ResponseType.APPLY:
1028 self.channel.display_solo_buttons = self.display_solo_buttons.get_active()
1029 self.channel.set_color(self.color_chooser_button.get_rgba())
1030 for inputchannel in self.app.channels:
1031 inputchannel.update_control_group(self.channel)
1035 class NewOutputChannelDialog(OutputChannelPropertiesDialog):
1036 def __init__(self, app):
1037 Gtk.Dialog.__init__(self, 'New Output Channel', app.window)
1038 self.mixer = app.mixer
1039 self.app = app
1040 self.create_ui()
1041 self.fill_ui()
1043 # TODO: disable mode for output channels as mono output channels may
1044 # not be correctly handled yet.
1045 self.mode_hbox.set_sensitive(False)
1046 self.stereo.set_active(True) # default to stereo
1048 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1049 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1050 self.ok_button.set_sensitive(False)
1051 self.set_default_response(Gtk.ResponseType.OK);
1053 def fill_ui(self):
1054 self.entry_volume_cc.set_value(-1)
1055 self.entry_balance_cc.set_value(-1)
1056 self.entry_mute_cc.set_value(-1)
1058 def get_result(self):
1059 return {'name': self.entry_name.get_text(),
1060 'stereo': self.stereo.get_active(),
1061 'volume_cc': int(self.entry_volume_cc.get_value()),
1062 'balance_cc': int(self.entry_balance_cc.get_value()),
1063 'mute_cc': int(self.entry_mute_cc.get_value()),
1064 'display_solo_buttons': self.display_solo_buttons.get_active(),
1065 'color': self.color_chooser_button.get_rgba()
1068 class ControlGroup(Gtk.Alignment):
1069 def __init__(self, output_channel, input_channel):
1070 GObject.GObject.__init__(self)
1071 self.set(0.5, 0.5, 1, 1)
1072 self.output_channel = output_channel
1073 self.input_channel = input_channel
1074 self.app = input_channel.app
1076 hbox = Gtk.HBox()
1077 self.vbox = Gtk.VBox()
1078 self.add(self.vbox)
1080 set_background_color(self.vbox, output_channel.css_name,
1081 output_channel.color.to_string())
1083 self.hbox = hbox
1084 self.vbox.pack_start(hbox, True, True, button_padding)
1085 css = b""" .control_group {
1086 min-width: 0px; padding: 0px;} """
1088 css_provider = Gtk.CssProvider()
1089 css_provider.load_from_data(css)
1090 context = Gtk.StyleContext()
1091 screen = Gdk.Screen.get_default()
1092 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
1094 self.label = Gtk.Label(output_channel.channel.name)
1095 label_context = self.label.get_style_context()
1096 label_context.add_class('control_group')
1098 self.hbox.pack_start(self.label, False, False, button_padding)
1099 mute = Gtk.ToggleButton()
1100 mute.set_label("M")
1101 mute.set_name("mute")
1102 mute.connect("toggled", self.on_mute_toggled)
1103 self.mute = mute
1104 solo = Gtk.ToggleButton()
1105 solo.set_name("solo")
1106 solo.set_label("S")
1107 solo.connect("toggled", self.on_solo_toggled)
1108 self.solo = solo
1110 if self.output_channel.display_solo_buttons:
1111 hbox.pack_end(solo, False, False, button_padding)
1112 hbox.pack_end(mute, False, False, button_padding)
1114 def update(self):
1115 if self.output_channel.display_solo_buttons:
1116 if not self.solo in self.hbox.get_children():
1117 self.hbox.pack_end(self.solo, False, False, button_padding)
1118 self.hbox.reorder_child(self.mute, -1)
1119 self.solo.show()
1120 else:
1121 if self.solo in self.hbox.get_children():
1122 self.hbox.remove(self.solo)
1124 self.label.set_text(self.output_channel.channel.name)
1125 set_background_color(self.vbox, self.output_channel.css_name, self.output_channel.color.to_string())
1128 def on_mute_toggled(self, button):
1129 self.output_channel.channel.set_muted(self.input_channel.channel, button.get_active())
1130 self.app.update_monitor(self)
1132 def on_solo_toggled(self, button):
1133 self.output_channel.channel.set_solo(self.input_channel.channel, button.get_active())
1134 self.app.update_monitor(self)