]> git.0d.be Git - jack_mixer.git/blob - channel.py
Remove unnecessary c interface funcs. Fix volume text failing to update
[jack_mixer.git] / channel.py
1 # This file is part of jack_mixer
2 #
3 # Copyright (C) 2006 Nedko Arnaudov <nedko@arnaudov.name>
4 #
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
8 #
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.
13 #
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.
17
18 import logging
19
20 import gi
21 from gi.repository import Gtk
22 from gi.repository import Gdk
23 from gi.repository import GObject
24
25 import abspeak
26 import meter
27 import slider
28 from serialization import SerializedObject
29
30 try:
31     import phat
32 except:
33     phat = None
34
35
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)
47
48
49 def set_background_color(widget, name, color_string):
50     css = """
51     .%s {
52         background-color: %s
53     }
54 """ % (name, color_string)
55
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)
61
62     widget_context = widget.get_style_context()
63     widget_context.add_class(name)
64
65
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)
70
71
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
96
97     def get_channel_name(self):
98         return self._channel_name
99
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)
113
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
118
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)
122
123         self.slider = None
124         self.create_slider_widget()
125
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)
131
132         self.meter.set_events(Gdk.EventMask.SCROLL_MASK)
133
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)
139
140         self.abspeak = abspeak.AbspeakWidget()
141         self.abspeak.connect("reset", self.on_abspeak_reset)
142         self.abspeak.connect("volume-adjust", self.on_abspeak_adjust)
143
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)
148
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)
154
155         self.connect("key-press-event", self.on_key_pressed)
156         self.connect("scroll-event", self.on_scroll)
157
158     def unrealize(self):
159         log.debug('Unrealizing channel "%s".', self.channel_name)
160         pass
161
162     def balance_preferred_width(self):
163         return (20, 20)
164
165     def _preferred_height(self):
166         return (0, 100)
167
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)
186
187
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()
192
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
203
204     def on_balance_button_release_event(self, widget, event):
205         self.balance.button_down = False
206         return False
207
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
219
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
230
231         if x >= 1:
232             x = 1
233         elif x <= -1:
234             x = -1
235         bal.set_value(x)
236         return True
237
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()
251
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)
255
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
262
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))
270
271     def on_custom_widgets_changed(self, gui_factory, value):
272         self.balance.destroy()
273         self.create_balance_widget()
274         self.create_slider_widget()
275
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)
281
282     def on_abspeak_reset(self, abspeak):
283         log.debug("abspeak reset")
284         self.channel.abspeak = None
285
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)
299
300     def on_volume_digits_focus_out(self, widget, event):
301         log.debug("Volume digits focus out detected.")
302         self.update_volume(False)
303
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])
312
313         self.abspeak.set_peak(self.channel.abspeak)
314
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
321
322     def update_volume(self, update_engine, from_midi = False):
323         db = self.slider_adjustment.get_value_db()
324
325         db_text = "%.2f" % db
326         self.volume_digits.set_text(db_text)
327
328         if update_engine:
329             if not from_midi:
330                 self.channel.volume = db
331             self.app.update_monitor(self)
332
333     def on_volume_changed(self, adjustment):
334         self.update_volume(True)
335
336     def on_volume_changed_from_midi(self, adjustment):
337         self.update_volume(True, from_midi = True)
338
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)
344
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
354
355         return False
356
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())
360
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))
371
372
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
396
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)
400
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)
411
412     def set_monitored(self):
413         if self.channel:
414             self.app.set_monitored_channel(self)
415         self.monitor_button.set_active(True)
416
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())
420
421 class InputChannel(Channel):
422     post_fader_output_channel = None
423
424     def __init__(self, app, name, stereo, value = None):
425         Channel.__init__(self, app, name, stereo, value)
426
427     def realize(self):
428         self.channel = self.mixer.add_channel(self.channel_name, self.stereo)
429
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
443
444         self.channel.midi_scale = self.slider_scale.scale
445
446         self.on_volume_changed(self.slider_adjustment)
447         self.on_balance_changed(self.balance_adjustment)
448
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)
467
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)
472
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)
484
485         self.volume_digits.set_width_chars(6)
486         self.pack_start(self.volume_digits, False, False, 0)
487
488         self.create_balance_widget()
489
490         self.hbox_mutesolo = Gtk.Box(False, 0)
491
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)
498
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)
505
506         self.pack_start(self.hbox_mutesolo, False, False, 0)
507
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)
511
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
517
518     def remove_control_group(self, channel):
519         ctlgroup = self.get_control_group(channel)
520         self.vbox.remove(ctlgroup)
521
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()
527
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
534
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
542
543     channel_properties_dialog = None
544
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()
550
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()
555
556     def on_mute_toggled(self, button):
557         self.channel.out_mute = self.mute.get_active()
558
559     def on_solo_toggled(self, button):
560         self.channel.solo = self.solo.get_active()
561
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)
567
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
590
591     @classmethod
592     def serialization_name(cls):
593         return 'input_channel'
594
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)
602
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)
615
616 class OutputChannel(Channel):
617     _display_solo_buttons = False
618
619     _init_muted_channels = None
620     _init_solo_channels = None
621
622     def __init__(self, app, name, stereo, value = None):
623         Channel.__init__(self, app, name, stereo, value)
624
625     def get_display_solo_buttons(self):
626         return self._display_solo_buttons
627
628     def set_display_solo_buttons(self, value):
629         self._display_solo_buttons = value
630         # notifying control groups
631         for inputchannel in self.app.channels:
632             inputchannel.update_control_group(self)
633
634     display_solo_buttons = property(get_display_solo_buttons, set_display_solo_buttons)
635
636     def realize(self):
637         self.channel = self.mixer.add_output_channel(self.channel_name, self.stereo)
638         if self.channel == None:
639             raise Exception("Cannot create a channel")
640         Channel.realize(self)
641         if self.future_volume_midi_cc != None:
642             self.channel.volume_midi_cc = self.future_volume_midi_cc
643         if self.future_balance_midi_cc != None:
644             self.channel.balance_midi_cc = self.future_balance_midi_cc
645         if self.future_mute_midi_cc != None:
646             self.channel.mute_midi_cc = self.future_mute_midi_cc
647         self.channel.midi_scale = self.slider_scale.scale
648
649         self.on_volume_changed(self.slider_adjustment)
650         self.on_balance_changed(self.balance_adjustment)
651
652         # vbox child at upper part
653         self.vbox = Gtk.VBox()
654         self.pack_start(self.vbox, False, True, 0)
655         self.label_name = Gtk.Label()
656         self.label_name.get_style_context().add_class('top_label')
657         self.label_name.set_text(self.channel_name)
658         self.label_name.set_width_chars(0)
659         self.label_name_event_box = Gtk.EventBox()
660         self.label_name_event_box.connect('button-press-event', self.on_label_mouse)
661         self.label_name_event_box.add(self.label_name)
662         if not hasattr(self, 'color'):
663             self.color = random_color()
664         set_background_color(self.label_name_event_box, self.css_name,
665                self.color.to_string())
666         self.vbox.pack_start(self.label_name_event_box, True, True, 0)
667         frame = Gtk.Frame()
668         frame.set_shadow_type(Gtk.ShadowType.IN)
669         frame.add(self.abspeak);
670         self.vbox.pack_start(frame, False, True, 0)
671
672         # hbox child at lower part
673         self.hbox = Gtk.HBox()
674         self.hbox.pack_start(self.slider, True, True, 0)
675         frame = Gtk.Frame()
676         frame.set_shadow_type(Gtk.ShadowType.IN)
677         frame.add(self.meter);
678         self.hbox.pack_start(frame, True, True, 0)
679         frame = Gtk.Frame()
680         frame.set_shadow_type(Gtk.ShadowType.IN)
681         frame.add(self.hbox);
682         self.pack_start(frame, True, True, 0)
683
684         self.volume_digits.set_width_chars(6)
685         self.pack_start(self.volume_digits, False, True, 0)
686
687         self.create_balance_widget()
688
689         self.mute = Gtk.ToggleButton()
690         self.mute.set_label("M")
691         self.mute.set_name("mute")
692         self.mute.set_active(self.channel.out_mute)
693         self.mute.connect("toggled", self.on_mute_toggled)
694
695         hbox = Gtk.HBox()
696         hbox.pack_start(self.mute, True, True, 0)
697         self.pack_start(hbox, False, False, 0)
698
699         self.monitor_button = Gtk.ToggleButton('MON')
700         self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
701         self.pack_start(self.monitor_button, False, False, 0)
702
703         # add control groups to the input channels, and initialize them
704         # appropriately
705         for input_channel in self.app.channels:
706             ctlgroup = input_channel.add_control_group(self)
707             if self._init_muted_channels and input_channel.channel.name in self._init_muted_channels:
708                 ctlgroup.mute.set_active(True)
709             if self._init_solo_channels and input_channel.channel.name in self._init_solo_channels:
710                 ctlgroup.solo.set_active(True)
711         self._init_muted_channels = None
712         self._init_solo_channels = None
713
714     channel_properties_dialog = None
715     def on_channel_properties(self):
716         if not self.channel_properties_dialog:
717             self.channel_properties_dialog = OutputChannelPropertiesDialog(self, self.app)
718         self.channel_properties_dialog.show()
719         self.channel_properties_dialog.present()
720
721     def on_label_mouse(self, widget, event):
722         if event.type == Gdk.EventType._2BUTTON_PRESS:
723             if event.button == 1:
724                 self.on_channel_properties()
725
726     def on_mute_toggled(self, button):
727         self.channel.out_mute = self.mute.get_active()
728
729     def midi_events_check(self):
730         if self.channel != None and self.channel.midi_in_got_events:
731             self.mute.set_active(self.channel.out_mute)
732             Channel.on_midi_event_received(self)
733
734     def unrealize(self):
735         # remove control groups from input channels
736         for input_channel in self.app.channels:
737             input_channel.remove_control_group(self)
738         # then remove itself
739         Channel.unrealize(self)
740         self.channel.remove()
741         self.channel = None
742
743     @classmethod
744     def serialization_name(cls):
745         return 'output_channel'
746
747     def serialize(self, object_backend):
748         object_backend.add_property("name", self.channel_name)
749         if self.stereo:
750             object_backend.add_property("type", "stereo")
751         else:
752             object_backend.add_property("type", "mono")
753         if self.display_solo_buttons:
754             object_backend.add_property("solo_buttons", "true")
755         muted_channels = []
756         solo_channels = []
757         for input_channel in self.app.channels:
758             if self.channel.is_muted(input_channel.channel):
759                 muted_channels.append(input_channel)
760             if self.channel.is_solo(input_channel.channel):
761                 solo_channels.append(input_channel)
762         if muted_channels:
763             object_backend.add_property('muted_channels', '|'.join([x.channel.name for x in muted_channels]))
764         if solo_channels:
765             object_backend.add_property('solo_channels', '|'.join([x.channel.name for x in solo_channels]))
766         object_backend.add_property("color", self.color.to_string())
767         Channel.serialize(self, object_backend)
768
769     def unserialize_property(self, name, value):
770         if name == "name":
771             self.channel_name = str(value)
772             return True
773         if name == "type":
774             if value == "stereo":
775                 self.stereo = True
776                 return True
777             if value == "mono":
778                 self.stereo = False
779                 return True
780         if name == "solo_buttons":
781             if value == "true":
782                 self.display_solo_buttons = True
783                 return True
784         if name == 'muted_channels':
785             self._init_muted_channels = value.split('|')
786             return True
787         if name == 'solo_channels':
788             self._init_solo_channels = value.split('|')
789             return True
790         if name == 'color':
791             c = Gdk.RGBA()
792             c.parse(value)
793             self.color = c
794             return True
795         return Channel.unserialize_property(self, name, value)
796
797 class ChannelPropertiesDialog(Gtk.Dialog):
798     channel = None
799
800     def __init__(self, parent, app):
801         self.channel = parent
802         self.app = app
803         self.mixer = self.channel.mixer
804         Gtk.Dialog.__init__(self, 'Channel "%s" Properties' % self.channel.channel_name, app.window)
805         self.set_default_size(365, -1)
806
807         self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
808         self.ok_button = self.add_button(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY)
809         self.set_default_response(Gtk.ResponseType.APPLY);
810
811         self.create_ui()
812         self.fill_ui()
813
814         self.connect('response', self.on_response_cb)
815         self.connect('delete-event', self.on_response_cb)
816
817     def create_frame(self, label, child):
818         frame = Gtk.Frame()
819         frame.set_label('')
820         frame.set_border_width(3)
821         #frame.set_shadow_type(Gtk.ShadowType.NONE)
822         frame.get_label_widget().set_markup('<b>%s</b>' % label)
823
824         alignment = Gtk.Alignment.new(0, 0, 1, 1)
825         alignment.set_padding(0, 0, 12, 0)
826         frame.add(alignment)
827         alignment.add(child)
828
829         return frame
830
831     def create_ui(self):
832         vbox = Gtk.VBox()
833         self.vbox.add(vbox)
834
835         self.properties_table = table = Gtk.Table(4, 3, False)
836         vbox.pack_start(self.create_frame('Properties', table), True, True, 0)
837         table.set_row_spacings(5)
838         table.set_col_spacings(5)
839
840         table.attach(Gtk.Label(label='Name'), 0, 1, 0, 1)
841         self.entry_name = Gtk.Entry()
842         self.entry_name.set_activates_default(True)
843         self.entry_name.connect('changed', self.on_entry_name_changed)
844         table.attach(self.entry_name, 1, 2, 0, 1)
845
846         table.attach(Gtk.Label(label='Mode'), 0, 1, 1, 2)
847         self.mode_hbox = Gtk.HBox()
848         table.attach(self.mode_hbox, 1, 2, 1, 2)
849         self.mono = Gtk.RadioButton(label='Mono')
850         self.stereo = Gtk.RadioButton(label='Stereo', group=self.mono)
851         self.mode_hbox.pack_start(self.mono, True, True, 0)
852         self.mode_hbox.pack_start(self.stereo, True, True, 0)
853
854         table = Gtk.Table(2, 3, False)
855         vbox.pack_start(self.create_frame('MIDI Control Changes', table), True, True, 0)
856         table.set_row_spacings(5)
857         table.set_col_spacings(5)
858
859         cc_tooltip = "{} MIDI Control Change number (0-127, set to -1 to assign next free CC #)"
860         table.attach(Gtk.Label(label='Volume'), 0, 1, 0, 1)
861         self.entry_volume_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
862         self.entry_volume_cc.set_tooltip_text(cc_tooltip.format("Volume"))
863         table.attach(self.entry_volume_cc, 1, 2, 0, 1)
864         self.button_sense_midi_volume = Gtk.Button('Learn')
865         self.button_sense_midi_volume.connect('clicked',
866                         self.on_sense_midi_volume_clicked)
867         table.attach(self.button_sense_midi_volume, 2, 3, 0, 1)
868
869         table.attach(Gtk.Label(label='Balance'), 0, 1, 1, 2)
870         self.entry_balance_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
871         self.entry_balance_cc.set_tooltip_text(cc_tooltip.format("Balance"))
872         table.attach(self.entry_balance_cc, 1, 2, 1, 2)
873         self.button_sense_midi_balance = Gtk.Button('Learn')
874         self.button_sense_midi_balance.connect('clicked',
875                         self.on_sense_midi_balance_clicked)
876         table.attach(self.button_sense_midi_balance, 2, 3, 1, 2)
877
878         table.attach(Gtk.Label(label='Mute'), 0, 1, 2, 3)
879         self.entry_mute_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
880         self.entry_mute_cc.set_tooltip_text(cc_tooltip.format("Mute"))
881         table.attach(self.entry_mute_cc, 1, 2, 2, 3)
882         self.button_sense_midi_mute = Gtk.Button('Learn')
883         self.button_sense_midi_mute.connect('clicked',
884                         self.on_sense_midi_mute_clicked)
885         table.attach(self.button_sense_midi_mute, 2, 3, 2, 3)
886
887         if (isinstance(self, NewChannelDialog) or (self.channel and
888             isinstance(self.channel, InputChannel))):
889             table.attach(Gtk.Label(label='Solo'), 0, 1, 3, 4)
890             self.entry_solo_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
891             self.entry_solo_cc.set_tooltip_text(cc_tooltip.format("Solo"))
892             table.attach(self.entry_solo_cc, 1, 2, 3, 4)
893             self.button_sense_midi_solo = Gtk.Button('Learn')
894             self.button_sense_midi_solo.connect('clicked',
895                             self.on_sense_midi_solo_clicked)
896             table.attach(self.button_sense_midi_solo, 2, 3, 3, 4)
897
898         self.vbox.show_all()
899
900     def fill_ui(self):
901         self.entry_name.set_text(self.channel.channel_name)
902         if self.channel.channel.is_stereo:
903             self.stereo.set_active(True)
904         else:
905             self.mono.set_active(True)
906         self.mode_hbox.set_sensitive(False)
907         self.entry_volume_cc.set_value(self.channel.channel.volume_midi_cc)
908         self.entry_balance_cc.set_value(self.channel.channel.balance_midi_cc)
909         self.entry_mute_cc.set_value(self.channel.channel.mute_midi_cc)
910         if (self.channel and isinstance(self.channel, InputChannel)):
911             self.entry_solo_cc.set_value(self.channel.channel.solo_midi_cc)
912
913     def sense_popup_dialog(self, entry):
914         window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
915         window.set_destroy_with_parent(True)
916         window.set_transient_for(self)
917         window.set_decorated(False)
918         window.set_modal(True)
919         window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
920         window.set_border_width(10)
921
922         vbox = Gtk.VBox(10)
923         window.add(vbox)
924         window.timeout = 5
925         vbox.pack_start(Gtk.Label(label='Please move the MIDI control you want to use for this function.'), True, True, 0)
926         timeout_label = Gtk.Label(label='This window will close in 5 seconds')
927         vbox.pack_start(timeout_label, True, True, 0)
928         def close_sense_timeout(window, entry):
929             window.timeout -= 1
930             timeout_label.set_text('This window will close in %d seconds.' % window.timeout)
931             if window.timeout == 0:
932                 window.destroy()
933                 entry.set_value(self.mixer.last_midi_channel)
934                 return False
935             return True
936         window.show_all()
937         GObject.timeout_add_seconds(1, close_sense_timeout, window, entry)
938
939     def on_sense_midi_volume_clicked(self, *args):
940         self.mixer.last_midi_channel = int(self.entry_volume_cc.get_value())
941         self.sense_popup_dialog(self.entry_volume_cc)
942
943     def on_sense_midi_balance_clicked(self, *args):
944         self.mixer.last_midi_channel = int(self.entry_balance_cc.get_value())
945         self.sense_popup_dialog(self.entry_balance_cc)
946
947     def on_sense_midi_mute_clicked(self, *args):
948         self.mixer.last_midi_channel = int(self.entry_mute_cc.get_value())
949         self.sense_popup_dialog(self.entry_mute_cc)
950
951     def on_sense_midi_solo_clicked(self, *args):
952         self.mixer.last_midi_channel = int(self.entry_solo_cc.get_value())
953         self.sense_popup_dialog(self.entry_solo_cc)
954
955     def on_response_cb(self, dlg, response_id, *args):
956         self.channel.channel_properties_dialog = None
957         name = self.entry_name.get_text()
958         if response_id == Gtk.ResponseType.APPLY:
959             if name != self.channel.channel_name:
960                 self.channel.channel_name = name
961             for control in ('volume', 'balance', 'mute', 'solo'):
962                 widget = getattr(self, 'entry_{}_cc'.format(control), None)
963                 if widget is not None:
964                     value = int(widget.get_value())
965                     if value != -1:
966                         setattr(self.channel.channel, '{}_midi_cc'.format(control), value)
967         self.destroy()
968
969     def on_entry_name_changed(self, entry):
970         sensitive = False
971         if len(entry.get_text()):
972             if self.channel and self.channel.channel.name == entry.get_text():
973                 sensitive = True
974             elif entry.get_text() not in [x.channel.name for x in self.app.channels] + \
975                         [x.channel.name for x in self.app.output_channels] + ['MAIN']:
976                 sensitive = True
977         self.ok_button.set_sensitive(sensitive)
978
979
980 class NewChannelDialog(ChannelPropertiesDialog):
981     def create_ui(self):
982         ChannelPropertiesDialog.create_ui(self)
983         self.properties_table.attach(Gtk.Label(label='Value'), 0, 1, 2, 3)
984
985         self.value_hbox = Gtk.HBox()
986         self.properties_table.attach(self.value_hbox, 1, 2, 2, 3)
987         self.minus_inf = Gtk.RadioButton(label='-Inf')
988         self.zero_dB = Gtk.RadioButton(label='0dB', group=self.minus_inf)
989         self.value_hbox.pack_start(self.minus_inf, True, True, 0)
990         self.value_hbox.pack_start(self.zero_dB, True, True, 0)
991
992         self.vbox.show_all()
993
994 class NewInputChannelDialog(NewChannelDialog):
995     def __init__(self, app):
996         Gtk.Dialog.__init__(self, 'New Input Channel', app.window)
997         self.set_default_size(365, -1)
998         self.mixer = app.mixer
999         self.app = app
1000         self.create_ui()
1001         self.fill_ui()
1002
1003         self.stereo.set_active(True) # default to stereo
1004
1005         self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1006         self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1007         self.ok_button.set_sensitive(False)
1008         self.set_default_response(Gtk.ResponseType.OK);
1009
1010     def fill_ui(self):
1011         self.entry_volume_cc.set_value(-1)
1012         self.entry_balance_cc.set_value(-1)
1013         self.entry_mute_cc.set_value(-1)
1014         self.entry_solo_cc.set_value(-1)
1015
1016     def get_result(self):
1017         log.debug('minus_inf active?: %s', self.zero_dB.get_active())
1018         return {'name': self.entry_name.get_text(),
1019                 'stereo': self.stereo.get_active(),
1020                 'volume_cc': int(self.entry_volume_cc.get_value()),
1021                 'balance_cc': int(self.entry_balance_cc.get_value()),
1022                 'mute_cc': int(self.entry_mute_cc.get_value()),
1023                 'solo_cc': int(self.entry_solo_cc.get_value()),
1024                 'value': self.minus_inf.get_active()
1025                }
1026
1027
1028 class OutputChannelPropertiesDialog(ChannelPropertiesDialog):
1029     def create_ui(self):
1030         NewChannelDialog.create_ui(self)
1031
1032         table = self.properties_table
1033         table.attach(Gtk.Label(label='Color'), 0, 1, 4, 5)
1034         self.color_chooser_button = Gtk.ColorButton()
1035         self.color_chooser_button.set_use_alpha(True)
1036         self.color_chooser_button.set_rgba(Gdk.RGBA(0, 0, 0, 0))
1037         table.attach(self.color_chooser_button, 1, 2, 4, 5)
1038
1039         vbox = Gtk.VBox()
1040         self.vbox.pack_start(self.create_frame('Input Channels', vbox), True, True, 0)
1041
1042         self.display_solo_buttons = Gtk.CheckButton('Display solo buttons')
1043         vbox.pack_start(self.display_solo_buttons, True, True, 0)
1044
1045         self.vbox.show_all()
1046
1047     def fill_ui(self):
1048         ChannelPropertiesDialog.fill_ui(self)
1049         self.display_solo_buttons.set_active(self.channel.display_solo_buttons)
1050         self.color_chooser_button.set_rgba(self.channel.color)
1051
1052     def on_response_cb(self, dlg, response_id, *args):
1053         ChannelPropertiesDialog.on_response_cb(self, dlg, response_id, *args)
1054         if response_id == Gtk.ResponseType.APPLY:
1055             self.channel.display_solo_buttons = self.display_solo_buttons.get_active()
1056             self.channel.set_color(self.color_chooser_button.get_rgba())
1057             for inputchannel in self.app.channels:
1058                 inputchannel.update_control_group(self.channel)
1059
1060
1061 class NewOutputChannelDialog(NewChannelDialog, OutputChannelPropertiesDialog):
1062     def __init__(self, app):
1063         Gtk.Dialog.__init__(self, 'New Output Channel', app.window)
1064         self.mixer = app.mixer
1065         self.app = app
1066         OutputChannelPropertiesDialog.create_ui(self)
1067         self.fill_ui()
1068         self.set_default_size(365, -1)
1069
1070         # TODO: disable mode for output channels as mono output channels may
1071         # not be correctly handled yet.
1072         self.mode_hbox.set_sensitive(False)
1073         self.stereo.set_active(True) # default to stereo
1074
1075         self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1076         self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1077         self.ok_button.set_sensitive(False)
1078         self.set_default_response(Gtk.ResponseType.OK);
1079
1080     def fill_ui(self):
1081         self.entry_volume_cc.set_value(-1)
1082         self.entry_balance_cc.set_value(-1)
1083         self.entry_mute_cc.set_value(-1)
1084
1085     def get_result(self):
1086         return {'name': self.entry_name.get_text(),
1087                 'stereo': self.stereo.get_active(),
1088                 'volume_cc': int(self.entry_volume_cc.get_value()),
1089                 'balance_cc': int(self.entry_balance_cc.get_value()),
1090                 'mute_cc': int(self.entry_mute_cc.get_value()),
1091                 'display_solo_buttons': self.display_solo_buttons.get_active(),
1092                 'color': self.color_chooser_button.get_rgba(),
1093                 'value': self.minus_inf.get_active()
1094                 }
1095
1096
1097 class ControlGroup(Gtk.Alignment):
1098     def __init__(self, output_channel, input_channel):
1099         GObject.GObject.__init__(self)
1100         self.set(0.5, 0.5, 1, 1)
1101         self.output_channel = output_channel
1102         self.input_channel = input_channel
1103         self.app = input_channel.app
1104
1105         self.hbox = Gtk.HBox()
1106         self.vbox = Gtk.VBox()
1107         self.add(self.vbox)
1108         self.buttons_box = Gtk.Box(False, button_padding)
1109
1110         set_background_color(self.vbox, output_channel.css_name,
1111                 output_channel.color.to_string())
1112
1113         self.vbox.pack_start(self.hbox, True, True, button_padding)
1114         css = b"""
1115         .control_group {
1116             min-width: 0px;
1117             padding: 0px;
1118         }
1119
1120         .control_group #label {
1121             font-size: smaller;
1122         }
1123
1124         .control_group #label,
1125         .control_group #mute,
1126         .control_group #solo {
1127             padding: 0px .2em;
1128         }
1129         """
1130
1131         css_provider = Gtk.CssProvider()
1132         css_provider.load_from_data(css)
1133         context = Gtk.StyleContext()
1134         screen = Gdk.Screen.get_default()
1135         context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
1136         hbox_context = self.hbox.get_style_context()
1137         hbox_context.add_class('control_group')
1138
1139         self.label = Gtk.Label(output_channel.channel.name)
1140         self.label.set_name("label")
1141         self.hbox.pack_start(self.label, False, False, button_padding)
1142         self.hbox.pack_end(self.buttons_box, False, False, button_padding)
1143         mute = Gtk.ToggleButton()
1144         mute.set_label("M")
1145         mute.set_name("mute")
1146         mute.connect("toggled", self.on_mute_toggled)
1147         self.mute = mute
1148         solo = Gtk.ToggleButton()
1149         solo.set_name("solo")
1150         solo.set_label("S")
1151         solo.connect("toggled", self.on_solo_toggled)
1152         self.solo = solo
1153
1154         if self.output_channel.display_solo_buttons:
1155             self.buttons_box.pack_end(solo, True, True, button_padding)
1156         self.buttons_box.pack_end(mute, True, True, button_padding)
1157
1158     def update(self):
1159         if self.output_channel.display_solo_buttons:
1160             if not self.solo in self.buttons_box.get_children():
1161                 self.buttons_box.pack_end(self.solo, True, True, button_padding)
1162                 self.buttons_box.reorder_child(self.mute, -1)
1163                 self.solo.show()
1164         else:
1165             if self.solo in self.hbox.get_children():
1166                 self.hbox.remove(self.solo)
1167
1168         self.label.set_text(self.output_channel.channel.name)
1169         set_background_color(self.vbox, self.output_channel.css_name, self.output_channel.color.to_string())
1170
1171     def on_mute_toggled(self, button):
1172         self.output_channel.channel.set_muted(self.input_channel.channel, button.get_active())
1173         self.app.update_monitor(self)
1174
1175     def on_solo_toggled(self, button):
1176         self.output_channel.channel.set_solo(self.input_channel.channel, button.get_active())
1177         self.app.update_monitor(self)