release: 12
[jack_mixer.git] / channel.py
blobece791e8ce30e497699e07739ed7bf3561fb1108
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 class Channel(Gtk.VBox, SerializedObject):
46 '''Widget with slider and meter used as base class for more specific
47 channel widgets'''
48 monitor_button = None
50 def __init__(self, app, name, stereo):
51 Gtk.VBox.__init__(self)
52 self.app = app
53 self.mixer = app.mixer
54 self.gui_factory = app.gui_factory
55 self._channel_name = name
56 self.stereo = stereo
57 self.meter_scale = self.gui_factory.get_default_meter_scale()
58 self.slider_scale = self.gui_factory.get_default_slider_scale()
59 self.slider_adjustment = slider.AdjustmentdBFS(self.slider_scale, 0.0, 0.02)
60 self.balance_adjustment = Gtk.Adjustment(0.0, -1.0, 1.0, 0.02)
61 self.future_out_mute = None
62 self.future_volume_midi_cc = None
63 self.future_balance_midi_cc = None
64 self.future_mute_midi_cc = None
65 self.future_solo_midi_cc = None
67 def get_channel_name(self):
68 return self._channel_name
70 label_name = None
71 channel = None
72 post_fader_output_channel = None
73 def set_channel_name(self, name):
74 self.app.on_channel_rename(self._channel_name, name);
75 self._channel_name = name
76 if self.label_name:
77 self.label_name.set_text(name)
78 if self.channel:
79 self.channel.name = name
80 if self.post_fader_output_channel:
81 self.post_fader_output_channel.name = "%s Out" % name;
82 channel_name = property(get_channel_name, set_channel_name)
84 def realize(self):
85 #print "Realizing channel \"%s\"" % self.channel_name
86 if self.future_out_mute != None:
87 self.channel.out_mute = self.future_out_mute
89 self.slider_adjustment.connect("volume-changed", self.on_volume_changed)
90 self.balance_adjustment.connect("value-changed", self.on_balance_changed)
92 self.slider = None
93 self.create_slider_widget()
95 if self.stereo:
96 self.meter = meter.StereoMeterWidget(self.meter_scale)
97 else:
98 self.meter = meter.MonoMeterWidget(self.meter_scale)
99 self.on_vumeter_color_changed(self.gui_factory)
101 self.meter.set_events(Gdk.EventMask.SCROLL_MASK)
103 self.gui_factory.connect("default-meter-scale-changed", self.on_default_meter_scale_changed)
104 self.gui_factory.connect("default-slider-scale-changed", self.on_default_slider_scale_changed)
105 self.gui_factory.connect('vumeter-color-changed', self.on_vumeter_color_changed)
106 self.gui_factory.connect('vumeter-color-scheme-changed', self.on_vumeter_color_changed)
107 self.gui_factory.connect('use-custom-widgets-changed', self.on_custom_widgets_changed)
109 self.abspeak = abspeak.AbspeakWidget()
110 self.abspeak.connect("reset", self.on_abspeak_reset)
111 self.abspeak.connect("volume-adjust", self.on_abspeak_adjust)
113 self.volume_digits = Gtk.Entry()
114 self.volume_digits.set_property('xalign', 0.5)
115 self.volume_digits.connect("key-press-event", self.on_volume_digits_key_pressed)
116 self.volume_digits.connect("focus-out-event", self.on_volume_digits_focus_out)
118 self.connect("key-press-event", self.on_key_pressed)
119 self.connect("scroll-event", self.on_scroll)
121 def unrealize(self):
122 #print "Unrealizing channel \"%s\"" % self.channel_name
123 pass
125 def balance_preferred_width(self):
126 return (20, 20)
128 def _preferred_height(self):
129 return (0, 100)
131 def create_balance_widget(self):
132 if self.gui_factory.use_custom_widgets and phat:
133 self.balance = phat.HFanSlider()
134 self.balance.set_default_value(0)
135 self.balance.set_adjustment(self.balance_adjustment)
136 else:
137 self.balance = Gtk.Scale()
138 self.balance.get_preferred_width = self.balance_preferred_width
139 self.balance.get_preferred_height = self._preferred_height
140 self.balance.set_orientation(Gtk.Orientation.HORIZONTAL)
141 self.balance.set_adjustment(self.balance_adjustment)
142 self.balance.set_draw_value(False)
143 self.pack_start(self.balance, False, True, 0)
144 if self.monitor_button:
145 self.reorder_child(self.monitor_button, -1)
146 self.balance.show()
148 def create_slider_widget(self):
149 parent = None
150 if self.slider:
151 parent = self.slider.get_parent()
152 self.slider.destroy()
153 if self.gui_factory.use_custom_widgets:
154 self.slider = slider.CustomSliderWidget(self.slider_adjustment)
155 else:
156 self.slider = slider.GtkSlider(self.slider_adjustment)
157 if parent:
158 parent.pack_start(self.slider, True, True, 0)
159 parent.reorder_child(self.slider, 0)
160 self.slider.show()
162 def on_default_meter_scale_changed(self, gui_factory, scale):
163 #print "Default meter scale change detected."
164 self.meter.set_scale(scale)
166 def on_default_slider_scale_changed(self, gui_factory, scale):
167 #print "Default slider scale change detected."
168 self.slider_scale = scale
169 self.slider_adjustment.set_scale(scale)
170 self.channel.midi_scale = self.slider_scale.scale
172 def on_vumeter_color_changed(self, gui_factory, *args):
173 color = gui_factory.get_vumeter_color()
174 color_scheme = gui_factory.get_vumeter_color_scheme()
175 if color_scheme != 'solid':
176 self.meter.set_color(None)
177 else:
178 self.meter.set_color(Gdk.color_parse(color))
180 def on_custom_widgets_changed(self, gui_factory, value):
181 self.balance.destroy()
182 self.create_balance_widget()
183 self.create_slider_widget()
185 def on_abspeak_adjust(self, abspeak, adjust):
186 #print "abspeak adjust %f" % adjust
187 self.slider_adjustment.set_value_db(self.slider_adjustment.get_value_db() + adjust)
188 self.channel.abspeak = None
189 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
191 def on_abspeak_reset(self, abspeak):
192 #print "abspeak reset"
193 self.channel.abspeak = None
195 def on_volume_digits_key_pressed(self, widget, event):
196 if (event.keyval == Gdk.KEY_Return or event.keyval == Gdk.KEY_KP_Enter):
197 db_text = self.volume_digits.get_text()
198 try:
199 db = float(db_text)
200 #print "Volume digits confirmation \"%f dBFS\"" % db
201 except (ValueError) as e:
202 #print "Volume digits confirmation ignore, reset to current"
203 self.update_volume(False)
204 return
205 self.slider_adjustment.set_value_db(db)
206 #self.grab_focus()
207 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
209 def on_volume_digits_focus_out(self, widget, event):
210 #print "volume digits focus out detected"
211 self.update_volume(False)
213 def read_meter(self):
214 if not self.channel:
215 return
216 if self.stereo:
217 meter_left, meter_right = self.channel.meter
218 self.meter.set_values(meter_left, meter_right)
219 else:
220 self.meter.set_value(self.channel.meter[0])
222 self.abspeak.set_peak(self.channel.abspeak)
224 def on_scroll(self, widget, event):
225 if event.direction == Gdk.ScrollDirection.DOWN:
226 self.slider_adjustment.step_down()
227 elif event.direction == Gdk.ScrollDirection.UP:
228 self.slider_adjustment.step_up()
229 return True
231 def update_volume(self, update_engine):
232 db = self.slider_adjustment.get_value_db()
234 db_text = "%.2f" % db
235 self.volume_digits.set_text(db_text)
237 if update_engine:
238 self.channel.volume = db
239 self.app.update_monitor(self)
241 def on_volume_changed(self, adjustment):
242 self.update_volume(True)
244 def on_balance_changed(self, adjustment):
245 balance = self.balance_adjustment.get_value()
246 #print "%s balance: %f" % (self.channel_name, balance)
247 self.channel.balance = balance
248 self.app.update_monitor(self)
250 def on_key_pressed(self, widget, event):
251 if (event.keyval == Gdk.KEY_Up):
252 #print self.channel_name + " Up"
253 self.slider_adjustment.step_up()
254 return True
255 elif (event.keyval == Gdk.KEY_Down):
256 #print self.channel_name + " Down"
257 self.slider_adjustment.step_down()
258 return True
260 return False
262 def serialize(self, object_backend):
263 object_backend.add_property("volume", "%f" % self.slider_adjustment.get_value_db())
264 object_backend.add_property("balance", "%f" % self.balance_adjustment.get_value())
266 if hasattr(self.channel, 'out_mute'):
267 object_backend.add_property('out_mute', str(self.channel.out_mute))
268 if self.channel.volume_midi_cc != -1:
269 object_backend.add_property('volume_midi_cc', str(self.channel.volume_midi_cc))
270 if self.channel.balance_midi_cc != -1:
271 object_backend.add_property('balance_midi_cc', str(self.channel.balance_midi_cc))
272 if self.channel.mute_midi_cc != -1:
273 object_backend.add_property('mute_midi_cc', str(self.channel.mute_midi_cc))
274 if self.channel.solo_midi_cc != -1:
275 object_backend.add_property('solo_midi_cc', str(self.channel.solo_midi_cc))
278 def unserialize_property(self, name, value):
279 if name == "volume":
280 self.slider_adjustment.set_value_db(float(value))
281 return True
282 if name == "balance":
283 self.balance_adjustment.set_value(float(value))
284 return True
285 if name == 'out_mute':
286 self.future_out_mute = (value == 'True')
287 return True
288 if name == 'volume_midi_cc':
289 self.future_volume_midi_cc = int(value)
290 return True
291 if name == 'balance_midi_cc':
292 self.future_balance_midi_cc = int(value)
293 return True
294 if name == 'mute_midi_cc':
295 self.future_mute_midi_cc = int(value)
296 return True
297 if name == 'solo_midi_cc':
298 self.future_solo_midi_cc = int(value)
299 return True
300 return False
302 def on_midi_event_received(self, *args):
303 self.slider_adjustment.set_value_db(self.channel.volume)
304 self.balance_adjustment.set_value(self.channel.balance)
306 def on_monitor_button_toggled(self, button):
307 if button.get_active():
308 for channel in self.app.channels + self.app.output_channels:
309 if channel.monitor_button.get_active() and channel.monitor_button is not button:
310 channel.monitor_button.handler_block_by_func(
311 channel.on_monitor_button_toggled)
312 channel.monitor_button.set_active(False)
313 channel.monitor_button.handler_unblock_by_func(
314 channel.on_monitor_button_toggled)
315 self.app.set_monitored_channel(self)
317 def set_monitored(self):
318 if self.channel:
319 self.app.set_monitored_channel(self)
320 self.monitor_button.set_active(True)
322 class InputChannel(Channel):
323 post_fader_output_channel = None
325 def __init__(self, app, name, stereo):
326 Channel.__init__(self, app, name, stereo)
328 def realize(self):
329 self.channel = self.mixer.add_channel(self.channel_name, self.stereo)
331 if self.channel == None:
332 raise Exception("Cannot create a channel")
333 Channel.realize(self)
334 if self.future_volume_midi_cc != None:
335 self.channel.volume_midi_cc = self.future_volume_midi_cc
336 if self.future_balance_midi_cc != None:
337 self.channel.balance_midi_cc = self.future_balance_midi_cc
338 if self.future_mute_midi_cc != None:
339 self.channel.mute_midi_cc = self.future_mute_midi_cc
340 if self.future_solo_midi_cc != None:
341 self.channel.solo_midi_cc = self.future_solo_midi_cc
342 if self.app._init_solo_channels and self.channel_name in self.app._init_solo_channels:
343 self.channel.solo = True
345 self.channel.midi_scale = self.slider_scale.scale
347 self.on_volume_changed(self.slider_adjustment)
348 self.on_balance_changed(self.balance_adjustment)
350 # vbox child at upper part
351 self.vbox = Gtk.VBox()
352 self.pack_start(self.vbox, False, True, 0)
353 self.label_name = Gtk.Label()
354 self.label_name.set_text(self.channel_name)
355 self.label_name.set_width_chars(0)
356 self.label_name_event_box = Gtk.EventBox()
357 self.label_name_event_box.connect("button-press-event", self.on_label_mouse)
358 self.label_name_event_box.add(self.label_name)
359 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
360 # self.label_stereo = Gtk.Label()
361 # if self.stereo:
362 # self.label_stereo.set_text("stereo")
363 # else:
364 # self.label_stereo.set_text("mono")
365 # self.label_stereo.set_size_request(0, -1)
366 # self.vbox.pack_start(self.label_stereo, True)
368 self.hbox_mutesolo = Gtk.HBox()
369 vbox_mutesolo = Gtk.VBox()
370 vbox_mutesolo.pack_start(self.hbox_mutesolo, True, True, button_padding)
371 self.vbox.pack_start(vbox_mutesolo, True, True, 0)
373 self.mute = Gtk.ToggleButton()
374 self.mute.set_label("M")
375 self.mute.set_name("mute")
376 self.mute.set_active(self.channel.out_mute)
377 self.mute.connect("toggled", self.on_mute_toggled)
378 self.hbox_mutesolo.pack_start(self.mute, True, True, button_padding)
380 self.solo = Gtk.ToggleButton()
381 self.solo.set_label("S")
382 self.solo.set_name("solo")
383 self.solo.set_active(self.channel.solo)
384 self.solo.connect("toggled", self.on_solo_toggled)
385 self.hbox_mutesolo.pack_start(self.solo, True, True, button_padding)
387 self.vbox.pack_start(self.hbox_mutesolo, True, True, 0)
389 frame = Gtk.Frame()
390 frame.set_shadow_type(Gtk.ShadowType.IN)
391 frame.add(self.abspeak);
392 self.pack_start(frame, False, True, 0)
394 # hbox child at lower part
395 self.hbox = Gtk.HBox()
396 self.hbox.pack_start(self.slider, True, True, 0)
397 frame = Gtk.Frame()
398 frame.set_shadow_type(Gtk.ShadowType.IN)
399 frame.add(self.meter);
400 self.hbox.pack_start(frame, True, True, 0)
401 frame = Gtk.Frame()
402 frame.set_shadow_type(Gtk.ShadowType.IN)
403 frame.add(self.hbox);
404 self.pack_start(frame, True, True, 0)
406 self.volume_digits.set_width_chars(6)
407 self.pack_start(self.volume_digits, False, False, 0)
409 self.create_balance_widget()
411 self.monitor_button = Gtk.ToggleButton('MON')
412 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
413 self.pack_start(self.monitor_button, False, False, 0)
415 def add_control_group(self, channel):
416 control_group = ControlGroup(channel, self)
417 control_group.show_all()
418 self.vbox.pack_start(control_group, True, True, 0)
419 return control_group
421 def remove_control_group(self, channel):
422 ctlgroup = self.get_control_group(channel)
423 self.vbox.remove(ctlgroup)
425 def update_control_group(self, channel):
426 for control_group in self.vbox.get_children():
427 if isinstance(control_group, ControlGroup):
428 if control_group.output_channel is channel:
429 control_group.update()
431 def get_control_group(self, channel):
432 for control_group in self.vbox.get_children():
433 if isinstance(control_group, ControlGroup):
434 if control_group.output_channel is channel:
435 return control_group
436 return None
438 def unrealize(self):
439 Channel.unrealize(self)
440 if self.post_fader_output_channel:
441 self.post_fader_output_channel.remove()
442 self.post_fader_output_channel = None
443 self.channel.remove()
444 self.channel = None
446 channel_properties_dialog = None
447 def on_channel_properties(self):
448 if not self.channel_properties_dialog:
449 self.channel_properties_dialog = ChannelPropertiesDialog(self, self.app)
450 self.channel_properties_dialog.show()
451 self.channel_properties_dialog.present()
453 def on_label_mouse(self, widget, event):
454 if event.type == Gdk.EventType._2BUTTON_PRESS:
455 if event.button == 1:
456 self.on_channel_properties()
458 def on_mute_toggled(self, button):
459 self.channel.out_mute = self.mute.get_active()
461 def on_solo_toggled(self, button):
462 self.channel.solo = self.solo.get_active()
464 def midi_events_check(self):
465 if hasattr(self, 'channel') and self.channel.midi_in_got_events:
466 self.mute.set_active(self.channel.out_mute)
467 self.solo.set_active(self.channel.solo)
468 Channel.on_midi_event_received(self)
470 def on_solo_button_pressed(self, button, event, *args):
471 if event.button == 3:
472 # right click on the solo button, act on all output channels
473 if button.get_active(): # was soloed
474 button.set_active(False)
475 if hasattr(button, 'touched_channels'):
476 touched_channels = button.touched_channels
477 for chan in touched_channels:
478 ctlgroup = self.get_control_group(chan)
479 ctlgroup.solo.set_active(False)
480 del button.touched_channels
481 else: # was not soloed
482 button.set_active(True)
483 touched_channels = []
484 for chan in self.app.output_channels:
485 ctlgroup = self.get_control_group(chan)
486 if not ctlgroup.solo.get_active():
487 ctlgroup.solo.set_active(True)
488 touched_channels.append(chan)
489 button.touched_channels = touched_channels
490 return True
491 return False
493 @classmethod
494 def serialization_name(cls):
495 return 'input_channel'
497 def serialize(self, object_backend):
498 object_backend.add_property("name", self.channel_name)
499 if self.stereo:
500 object_backend.add_property("type", "stereo")
501 else:
502 object_backend.add_property("type", "mono")
503 Channel.serialize(self, object_backend)
505 def unserialize_property(self, name, value):
506 if name == "name":
507 self.channel_name = str(value)
508 return True
509 if name == "type":
510 if value == "stereo":
511 self.stereo = True
512 return True
513 if value == "mono":
514 self.stereo = False
515 return True
516 return Channel.unserialize_property(self, name, value)
519 available_colours = [
520 ('#648fcb', '#204c98', '#426cb8'),
521 ('#984a9a', '#542656', '#744676'),
522 ('#7f9abb', '#3f5677', '#5f7697'),
523 ('#bf8f9f', '#7b4d5b', '#9b6f7b'),
524 ('#ba6d89', '#762945', '#964965'),
525 ('#4c9196', '#0c5156', '#2c7176'),
526 ('#56a2c0', '#166280', '#3682a0'),
529 class OutputChannel(Channel):
530 colours = available_colours[:]
531 _display_solo_buttons = False
533 _init_muted_channels = None
534 _init_solo_channels = None
536 def __init__(self, app, name, stereo):
537 Channel.__init__(self, app, name, stereo)
539 def get_display_solo_buttons(self):
540 return self._display_solo_buttons
542 def set_display_solo_buttons(self, value):
543 self._display_solo_buttons = value
544 # notifying control groups
545 for inputchannel in self.app.channels:
546 inputchannel.update_control_group(self)
548 display_solo_buttons = property(get_display_solo_buttons, set_display_solo_buttons)
550 def realize(self):
551 self.channel = self.mixer.add_output_channel(self.channel_name, self.stereo)
552 if self.channel == None:
553 raise Exception("Cannot create a channel")
554 Channel.realize(self)
555 if self.future_volume_midi_cc != None:
556 self.channel.volume_midi_cc = self.future_volume_midi_cc
557 if self.future_balance_midi_cc != None:
558 self.channel.balance_midi_cc = self.future_balance_midi_cc
559 if self.future_mute_midi_cc != None:
560 self.channel.mute_midi_cc = self.future_mute_midi_cc
561 self.channel.midi_scale = self.slider_scale.scale
563 self.on_volume_changed(self.slider_adjustment)
564 self.on_balance_changed(self.balance_adjustment)
566 # vbox child at upper part
567 self.vbox = Gtk.VBox()
568 self.pack_start(self.vbox, False, True, 0)
569 self.label_name = Gtk.Label()
570 self.label_name.set_text(self.channel_name)
571 self.label_name.set_width_chars(0)
572 self.label_name_event_box = Gtk.EventBox()
573 self.label_name_event_box.connect('button-press-event', self.on_label_mouse)
574 self.label_name_event_box.add(self.label_name)
575 if not self.colours:
576 OutputChannel.colours = available_colours[:]
577 for color in self.colours:
578 self.color_tuple = [Gdk.color_parse(color[x]) for x in range(3)]
579 self.colours.remove(color)
580 break
581 self.label_name_event_box.modify_bg(Gtk.StateType.NORMAL, self.color_tuple[1])
582 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
583 self.mute = Gtk.ToggleButton()
584 self.mute.set_label("M")
585 self.mute.set_name("mute")
586 self.mute.set_active(self.channel.out_mute)
587 self.mute.connect("toggled", self.on_mute_toggled)
588 hbox = Gtk.HBox()
589 hbox.pack_start(self.mute, True, True, button_padding)
590 self.vbox.pack_start(hbox, True, True, button_padding)
592 frame = Gtk.Frame()
593 frame.set_shadow_type(Gtk.ShadowType.IN)
594 frame.add(self.abspeak);
595 self.vbox.pack_start(frame, False, True, 0)
597 # hbox child at lower part
598 self.hbox = Gtk.HBox()
599 self.hbox.pack_start(self.slider, True, True, 0)
600 frame = Gtk.Frame()
601 frame.set_shadow_type(Gtk.ShadowType.IN)
602 frame.add(self.meter);
603 self.hbox.pack_start(frame, True, True, 0)
604 frame = Gtk.Frame()
605 frame.set_shadow_type(Gtk.ShadowType.IN)
606 frame.add(self.hbox);
607 self.pack_start(frame, True, True, 0)
609 self.volume_digits.set_width_chars(6)
610 self.pack_start(self.volume_digits, False, True, 0)
612 self.create_balance_widget()
614 self.monitor_button = Gtk.ToggleButton('MON')
615 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
616 self.pack_start(self.monitor_button, False, False, 0)
618 # add control groups to the input channels, and initialize them
619 # appropriately
620 for input_channel in self.app.channels:
621 ctlgroup = input_channel.add_control_group(self)
622 if self._init_muted_channels and input_channel.channel.name in self._init_muted_channels:
623 ctlgroup.mute.set_active(True)
624 if self._init_solo_channels and input_channel.channel.name in self._init_solo_channels:
625 ctlgroup.solo.set_active(True)
626 self._init_muted_channels = None
627 self._init_solo_channels = None
629 channel_properties_dialog = None
630 def on_channel_properties(self):
631 if not self.channel_properties_dialog:
632 self.channel_properties_dialog = OutputChannelPropertiesDialog(self, self.app)
633 self.channel_properties_dialog.show()
634 self.channel_properties_dialog.present()
636 def on_label_mouse(self, widget, event):
637 if event.type == Gdk.EventType._2BUTTON_PRESS:
638 if event.button == 1:
639 self.on_channel_properties()
641 def on_mute_toggled(self, button):
642 self.channel.out_mute = self.mute.get_active()
644 def midi_events_check(self):
645 if self.channel != None and self.channel.midi_in_got_events:
646 self.mute.set_active(self.channel.out_mute)
647 Channel.on_midi_event_received(self)
649 def unrealize(self):
650 # remove control groups from input channels
651 for input_channel in self.app.channels:
652 input_channel.remove_control_group(self)
653 # then remove itself
654 Channel.unrealize(self)
655 self.channel.remove()
656 self.channel = None
658 @classmethod
659 def serialization_name(cls):
660 return 'output_channel'
662 def serialize(self, object_backend):
663 object_backend.add_property("name", self.channel_name)
664 if self.stereo:
665 object_backend.add_property("type", "stereo")
666 else:
667 object_backend.add_property("type", "mono")
668 if self.display_solo_buttons:
669 object_backend.add_property("solo_buttons", "true")
670 muted_channels = []
671 solo_channels = []
672 for input_channel in self.app.channels:
673 if self.channel.is_muted(input_channel.channel):
674 muted_channels.append(input_channel)
675 if self.channel.is_solo(input_channel.channel):
676 solo_channels.append(input_channel)
677 if muted_channels:
678 object_backend.add_property('muted_channels', '|'.join([x.channel.name for x in muted_channels]))
679 if solo_channels:
680 object_backend.add_property('solo_channels', '|'.join([x.channel.name for x in solo_channels]))
681 Channel.serialize(self, object_backend)
683 def unserialize_property(self, name, value):
684 if name == "name":
685 self.channel_name = str(value)
686 return True
687 if name == "type":
688 if value == "stereo":
689 self.stereo = True
690 return True
691 if value == "mono":
692 self.stereo = False
693 return True
694 if name == "solo_buttons":
695 if value == "true":
696 self.display_solo_buttons = True
697 return True
698 if name == 'muted_channels':
699 self._init_muted_channels = value.split('|')
700 return True
701 if name == 'solo_channels':
702 self._init_solo_channels = value.split('|')
703 return True
704 return Channel.unserialize_property(self, name, value)
706 class ChannelPropertiesDialog(Gtk.Dialog):
707 channel = None
709 def __init__(self, parent, app):
710 self.channel = parent
711 self.app = app
712 self.mixer = self.channel.mixer
713 Gtk.Dialog.__init__(self, 'Channel "%s" Properties' % self.channel.channel_name, app.window)
715 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
716 self.ok_button = self.add_button(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY)
717 self.set_default_response(Gtk.ResponseType.APPLY);
719 self.create_ui()
720 self.fill_ui()
722 self.connect('response', self.on_response_cb)
723 self.connect('delete-event', self.on_response_cb)
725 def create_frame(self, label, child):
726 frame = Gtk.Frame()
727 frame.set_label('')
728 frame.set_border_width(3)
729 #frame.set_shadow_type(Gtk.ShadowType.NONE)
730 frame.get_label_widget().set_markup('<b>%s</b>' % label)
732 alignment = Gtk.Alignment.new(0, 0, 1, 1)
733 alignment.set_padding(0, 0, 12, 0)
734 frame.add(alignment)
735 alignment.add(child)
737 return frame
739 def create_ui(self):
740 vbox = Gtk.VBox()
741 self.vbox.add(vbox)
743 table = Gtk.Table(2, 3, False)
744 vbox.pack_start(self.create_frame('Properties', table), True, True, 0)
745 table.set_row_spacings(5)
746 table.set_col_spacings(5)
748 table.attach(Gtk.Label(label='Name'), 0, 1, 0, 1)
749 self.entry_name = Gtk.Entry()
750 self.entry_name.set_activates_default(True)
751 self.entry_name.connect('changed', self.on_entry_name_changed)
752 table.attach(self.entry_name, 1, 2, 0, 1)
754 table.attach(Gtk.Label(label='Mode'), 0, 1, 1, 2)
755 self.mode_hbox = Gtk.HBox()
756 table.attach(self.mode_hbox, 1, 2, 1, 2)
757 self.mono = Gtk.RadioButton(label='Mono')
758 self.stereo = Gtk.RadioButton(label='Stereo', group=self.mono)
759 self.mode_hbox.pack_start(self.mono, True, True, 0)
760 self.mode_hbox.pack_start(self.stereo, True, True, 0)
762 table = Gtk.Table(2, 3, False)
763 vbox.pack_start(self.create_frame('MIDI Control Channels', table), True, True, 0)
764 table.set_row_spacings(5)
765 table.set_col_spacings(5)
767 table.attach(Gtk.Label(label='Volume'), 0, 1, 0, 1)
768 self.entry_volume_cc = Gtk.Entry()
769 self.entry_volume_cc.set_activates_default(True)
770 self.entry_volume_cc.set_editable(False)
771 self.entry_volume_cc.set_width_chars(3)
772 table.attach(self.entry_volume_cc, 1, 2, 0, 1)
773 self.button_sense_midi_volume = Gtk.Button('Autoset')
774 self.button_sense_midi_volume.connect('clicked',
775 self.on_sense_midi_volume_clicked)
776 table.attach(self.button_sense_midi_volume, 2, 3, 0, 1)
778 table.attach(Gtk.Label(label='Balance'), 0, 1, 1, 2)
779 self.entry_balance_cc = Gtk.Entry()
780 self.entry_balance_cc.set_activates_default(True)
781 self.entry_balance_cc.set_width_chars(3)
782 self.entry_balance_cc.set_editable(False)
783 table.attach(self.entry_balance_cc, 1, 2, 1, 2)
784 self.button_sense_midi_balance = Gtk.Button('Autoset')
785 self.button_sense_midi_balance.connect('clicked',
786 self.on_sense_midi_balance_clicked)
787 table.attach(self.button_sense_midi_balance, 2, 3, 1, 2)
789 table.attach(Gtk.Label(label='Mute'), 0, 1, 2, 3)
790 self.entry_mute_cc = Gtk.Entry()
791 self.entry_mute_cc.set_activates_default(True)
792 self.entry_mute_cc.set_editable(False)
793 self.entry_mute_cc.set_width_chars(3)
794 table.attach(self.entry_mute_cc, 1, 2, 2, 3)
795 self.button_sense_midi_mute = Gtk.Button('Autoset')
796 self.button_sense_midi_mute.connect('clicked',
797 self.on_sense_midi_mute_clicked)
798 table.attach(self.button_sense_midi_mute, 2, 3, 2, 3)
800 if (isinstance(self, NewChannelDialog) or (self.channel and
801 isinstance(self.channel, InputChannel))):
802 table.attach(Gtk.Label(label='Solo'), 0, 1, 3, 4)
803 self.entry_solo_cc = Gtk.Entry()
804 self.entry_solo_cc.set_activates_default(True)
805 self.entry_solo_cc.set_editable(False)
806 self.entry_solo_cc.set_width_chars(3)
807 table.attach(self.entry_solo_cc, 1, 2, 3, 4)
808 self.button_sense_midi_solo = Gtk.Button('Autoset')
809 self.button_sense_midi_solo.connect('clicked',
810 self.on_sense_midi_solo_clicked)
811 table.attach(self.button_sense_midi_solo, 2, 3, 3, 4)
813 self.vbox.show_all()
815 def fill_ui(self):
816 self.entry_name.set_text(self.channel.channel_name)
817 if self.channel.channel.is_stereo:
818 self.stereo.set_active(True)
819 else:
820 self.mono.set_active(True)
821 self.mode_hbox.set_sensitive(False)
822 self.entry_volume_cc.set_text('%s' % self.channel.channel.volume_midi_cc)
823 self.entry_balance_cc.set_text('%s' % self.channel.channel.balance_midi_cc)
824 self.entry_mute_cc.set_text('%s' % self.channel.channel.mute_midi_cc)
825 if (self.channel and isinstance(self.channel, InputChannel)):
826 self.entry_solo_cc.set_text('%s' % self.channel.channel.solo_midi_cc)
828 def sense_popup_dialog(self, entry):
829 window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
830 window.set_destroy_with_parent(True)
831 window.set_transient_for(self)
832 window.set_decorated(False)
833 window.set_modal(True)
834 window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
835 window.set_border_width(10)
837 vbox = Gtk.VBox(10)
838 window.add(vbox)
839 window.timeout = 5
840 vbox.pack_start(Gtk.Label(label='Please move the MIDI control you want to use for this function.'), True, True, 0)
841 timeout_label = Gtk.Label(label='This window will close in 5 seconds')
842 vbox.pack_start(timeout_label, True, True, 0)
843 def close_sense_timeout(window, entry):
844 window.timeout -= 1
845 timeout_label.set_text('This window will close in %d seconds.' % window.timeout)
846 if window.timeout == 0:
847 window.destroy()
848 entry.set_text('%s' % self.mixer.last_midi_channel)
849 return False
850 return True
851 window.show_all()
852 GObject.timeout_add_seconds(1, close_sense_timeout, window, entry)
854 def on_sense_midi_volume_clicked(self, *args):
855 self.mixer.last_midi_channel = int(self.entry_volume_cc.get_text())
856 self.sense_popup_dialog(self.entry_volume_cc)
858 def on_sense_midi_balance_clicked(self, *args):
859 self.mixer.last_midi_channel = int(self.entry_balance_cc.get_text())
860 self.sense_popup_dialog(self.entry_balance_cc)
862 def on_sense_midi_mute_clicked(self, *args):
863 self.mixer.last_midi_channel = int(self.entry_mute_cc.get_text())
864 self.sense_popup_dialog(self.entry_mute_cc)
866 def on_sense_midi_solo_clicked(self, *args):
867 self.mixer.last_midi_channel = int(self.entry_solo_cc.get_text())
868 self.sense_popup_dialog(self.entry_solo_cc)
870 def on_response_cb(self, dlg, response_id, *args):
871 self.channel.channel_properties_dialog = None
872 name = self.entry_name.get_text()
873 if response_id == Gtk.ResponseType.APPLY:
874 self.channel.channel_name = name
875 try:
876 if self.entry_volume_cc.get_text() != '-1':
877 self.channel.channel.volume_midi_cc = int(self.entry_volume_cc.get_text())
878 except ValueError:
879 pass
880 try:
881 if self.entry_balance_cc.get_text() != '-1':
882 self.channel.channel.balance_midi_cc = int(self.entry_balance_cc.get_text())
883 except ValueError:
884 pass
885 try:
886 if self.entry_mute_cc.get_text() != '-1':
887 self.channel.channel.mute_midi_cc = int(self.entry_mute_cc.get_text())
888 except ValueError:
889 pass
890 try:
891 if hasattr(self, 'entry_solo_cc') and self.entry_solo_cc.get_text() != '-1':
892 self.channel.channel.solo_midi_cc = int(self.entry_solo_cc.get_text())
893 except ValueError:
894 pass
895 self.destroy()
897 def on_entry_name_changed(self, entry):
898 sensitive = False
899 if len(entry.get_text()):
900 if self.channel and self.channel.channel.name == entry.get_text():
901 sensitive = True
902 elif entry.get_text() not in [x.channel.name for x in self.app.channels] + \
903 [x.channel.name for x in self.app.output_channels] + ['MAIN']:
904 sensitive = True
905 self.ok_button.set_sensitive(sensitive)
908 class NewChannelDialog(ChannelPropertiesDialog):
909 def __init__(self, app):
910 Gtk.Dialog.__init__(self, 'New Channel', app.window)
911 self.mixer = app.mixer
912 self.app = app
913 self.create_ui()
914 self.fill_ui()
916 self.stereo.set_active(True) # default to stereo
918 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
919 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
920 self.ok_button.set_sensitive(False)
921 self.set_default_response(Gtk.ResponseType.OK);
923 def fill_ui(self):
924 self.entry_volume_cc.set_text('-1')
925 self.entry_balance_cc.set_text('-1')
926 self.entry_mute_cc.set_text('-1')
927 self.entry_solo_cc.set_text('-1')
929 def get_result(self):
930 return {'name': self.entry_name.get_text(),
931 'stereo': self.stereo.get_active(),
932 'volume_cc': self.entry_volume_cc.get_text(),
933 'balance_cc': self.entry_balance_cc.get_text(),
934 'mute_cc': self.entry_mute_cc.get_text(),
935 'solo_cc': self.entry_solo_cc.get_text()
938 class OutputChannelPropertiesDialog(ChannelPropertiesDialog):
939 def create_ui(self):
940 ChannelPropertiesDialog.create_ui(self)
942 vbox = Gtk.VBox()
943 self.vbox.pack_start(self.create_frame('Input Channels', vbox), True, True, 0)
945 self.display_solo_buttons = Gtk.CheckButton('Display solo buttons')
946 vbox.pack_start(self.display_solo_buttons, True, True, 0)
948 self.vbox.show_all()
950 def fill_ui(self):
951 ChannelPropertiesDialog.fill_ui(self)
952 self.display_solo_buttons.set_active(self.channel.display_solo_buttons)
954 def on_response_cb(self, dlg, response_id, *args):
955 if response_id == Gtk.ResponseType.APPLY:
956 self.channel.display_solo_buttons = self.display_solo_buttons.get_active()
957 ChannelPropertiesDialog.on_response_cb(self, dlg, response_id, *args)
960 class NewOutputChannelDialog(OutputChannelPropertiesDialog):
961 def __init__(self, app):
962 Gtk.Dialog.__init__(self, 'New Output Channel', app.window)
963 self.mixer = app.mixer
964 self.app = app
965 self.create_ui()
966 self.fill_ui()
968 # TODO: disable mode for output channels as mono output channels may
969 # not be correctly handled yet.
970 self.mode_hbox.set_sensitive(False)
971 self.stereo.set_active(True) # default to stereo
973 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
974 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
975 self.ok_button.set_sensitive(False)
976 self.set_default_response(Gtk.ResponseType.OK);
978 def fill_ui(self):
979 self.entry_volume_cc.set_text('-1')
980 self.entry_balance_cc.set_text('-1')
981 self.entry_mute_cc.set_text('-1')
983 def get_result(self):
984 return {'name': self.entry_name.get_text(),
985 'stereo': self.stereo.get_active(),
986 'volume_cc': self.entry_volume_cc.get_text(),
987 'balance_cc': self.entry_balance_cc.get_text(),
988 'mute_cc': self.entry_mute_cc.get_text(),
989 'display_solo_buttons': self.display_solo_buttons.get_active(),
992 class ControlGroup(Gtk.Alignment):
993 def __init__(self, output_channel, input_channel):
994 GObject.GObject.__init__(self)
995 self.set(0.5, 0.5, 1, 1)
996 self.output_channel = output_channel
997 self.input_channel = input_channel
998 self.app = input_channel.app
1000 hbox = Gtk.HBox()
1001 vbox = Gtk.VBox()
1002 self.hbox = hbox
1003 vbox.pack_start(hbox, True, True, button_padding)
1004 self.add(vbox)
1006 vbox.modify_bg(Gtk.StateType.NORMAL, output_channel.color_tuple[1])
1007 mute_name = "%s_mute" % output_channel.channel.name
1008 mute = Gtk.ToggleButton()
1009 mute.set_label("M")
1010 mute.set_name("mute")
1011 mute.connect("toggled", self.on_mute_toggled)
1012 self.mute = mute
1013 hbox.pack_start(mute, True, True, button_padding)
1014 solo = Gtk.ToggleButton()
1015 solo.set_name("solo")
1016 solo.set_label("S")
1017 solo.connect("toggled", self.on_solo_toggled)
1018 self.solo = solo
1020 if self.output_channel.display_solo_buttons:
1021 hbox.pack_start(solo, True, True, button_padding)
1023 def update(self):
1024 if self.output_channel.display_solo_buttons:
1025 if not self.solo in self.hbox.get_children():
1026 self.hbox.pack_start(self.solo, True, True, 0)
1027 self.solo.show()
1028 else:
1029 if self.solo in self.hbox.get_children():
1030 self.hbox.remove(self.solo)
1032 def on_mute_toggled(self, button):
1033 self.output_channel.channel.set_muted(self.input_channel.channel, button.get_active())
1034 self.app.update_monitor(self)
1036 def on_solo_toggled(self, button):
1037 self.output_channel.channel.set_solo(self.input_channel.channel, button.get_active())
1038 self.app.update_monitor(self)