]> git.0d.be Git - jack_mixer.git/blob - channel.py
Remove commented code
[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.balance_adjustment.connect("balance-changed", self.on_balance_changed)
121
122         self.slider = None
123         self.create_slider_widget()
124
125         if self.stereo:
126             self.meter = meter.StereoMeterWidget(self.meter_scale)
127         else:
128             self.meter = meter.MonoMeterWidget(self.meter_scale)
129         self.on_vumeter_color_changed(self.gui_factory)
130
131         self.meter.set_events(Gdk.EventMask.SCROLL_MASK)
132
133         self.gui_factory.connect("default-meter-scale-changed", self.on_default_meter_scale_changed)
134         self.gui_factory.connect("default-slider-scale-changed", self.on_default_slider_scale_changed)
135         self.gui_factory.connect('vumeter-color-changed', self.on_vumeter_color_changed)
136         self.gui_factory.connect('vumeter-color-scheme-changed', self.on_vumeter_color_changed)
137         self.gui_factory.connect('use-custom-widgets-changed', self.on_custom_widgets_changed)
138
139         self.abspeak = abspeak.AbspeakWidget()
140         self.abspeak.connect("reset", self.on_abspeak_reset)
141         self.abspeak.connect("volume-adjust", self.on_abspeak_adjust)
142
143         self.volume_digits = Gtk.Entry()
144         self.volume_digits.set_property('xalign', 0.5)
145         self.volume_digits.connect("key-press-event", self.on_volume_digits_key_pressed)
146         self.volume_digits.connect("focus-out-event", self.on_volume_digits_focus_out)
147
148         if self.initial_value != None:
149             if self.initial_value == True:
150                 self.slider_adjustment.set_value(0)
151             else:
152                 self.slider_adjustment.set_value_db(0)
153
154         self.connect("key-press-event", self.on_key_pressed)
155         self.connect("scroll-event", self.on_scroll)
156
157     def unrealize(self):
158         log.debug('Unrealizing channel "%s".', self.channel_name)
159         pass
160
161     def balance_preferred_width(self):
162         return (20, 20)
163
164     def _preferred_height(self):
165         return (0, 100)
166
167     def create_balance_widget(self):
168         if self.gui_factory.use_custom_widgets and phat:
169             self.balance = phat.HFanSlider()
170             self.balance.set_default_value(0)
171             self.balance.set_adjustment(self.balance_adjustment)
172         else:
173             self.balance = Gtk.Scale()
174             self.balance.get_preferred_width = self.balance_preferred_width
175             self.balance.get_preferred_height = self._preferred_height
176             self.balance.set_orientation(Gtk.Orientation.HORIZONTAL)
177             self.balance.set_adjustment(self.balance_adjustment)
178             self.balance.set_has_origin(False)
179             self.balance.set_draw_value(False)
180             self.balance.button_down = False
181             self.balance.connect('button-press-event', self.on_balance_button_press_event)
182             self.balance.connect('button-release-event', self.on_balance_button_release_event)
183             self.balance.connect("motion-notify-event", self.on_balance_motion_notify_event)
184             self.balance.connect("scroll-event", self.on_balance_scroll_event)
185
186
187         self.pack_start(self.balance, False, True, 0)
188         if self.monitor_button:
189             self.reorder_child(self.monitor_button, -1)
190         self.balance.show()
191
192     def on_balance_button_press_event(self, widget, event):
193         if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
194             self.balance.button_down = True
195             self.balance.button_down_x = event.x
196             self.balance.button_down_value = self.balance.get_value()
197             return True
198         if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
199             self.balance_adjustment.set_balance(0)
200             return True
201         return False
202
203     def on_balance_button_release_event(self, widget, event):
204         self.balance.button_down = False
205         return False
206
207     def on_balance_motion_notify_event(self, widget, event):
208         slider_length = widget.get_allocation().width - widget.get_style_context().get_property('min-width', Gtk.StateFlags.NORMAL)
209         if self.balance.button_down:
210             delta_x = (event.x - self.balance.button_down_x) / slider_length
211             x = self.balance.button_down_value + 2 * delta_x
212             if x >= 1:
213                 x = 1
214             elif x <= -1:
215                 x = -1
216             self.balance_adjustment.set_balance(x)
217             return True
218
219     def on_balance_scroll_event(self, widget, event):
220         bal = self.balance
221         delta = bal.get_adjustment().get_step_increment()
222         value = bal.get_value()
223         if event.direction == Gdk.ScrollDirection.UP:
224             x = value - delta
225         elif event.direction == Gdk.ScrollDirection.DOWN:
226             x = value + delta
227         elif event.direction == Gdk.ScrollDirection.SMOOTH:
228             x = value - event.delta_y * delta
229
230         if x >= 1:
231             x = 1
232         elif x <= -1:
233             x = -1
234         bal.set_value(x)
235         return True
236
237     def create_slider_widget(self):
238         parent = None
239         if self.slider:
240             parent = self.slider.get_parent()
241             self.slider.destroy()
242         if self.gui_factory.use_custom_widgets:
243             self.slider = slider.CustomSliderWidget(self.slider_adjustment)
244         else:
245             self.slider = slider.GtkSlider(self.slider_adjustment)
246         if parent:
247             parent.pack_start(self.slider, True, True, 0)
248             parent.reorder_child(self.slider, 0)
249         self.slider.show()
250
251     def on_default_meter_scale_changed(self, gui_factory, scale):
252         log.debug("Default meter scale change detected.")
253         self.meter.set_scale(scale)
254
255     def on_default_slider_scale_changed(self, gui_factory, scale):
256         log.debug("Default slider scale change detected.")
257         self.slider_scale = scale
258         self.slider_adjustment.set_scale(scale)
259         if self.channel:
260             self.channel.midi_scale = self.slider_scale.scale
261
262     def on_vumeter_color_changed(self, gui_factory, *args):
263         color = gui_factory.get_vumeter_color()
264         color_scheme = gui_factory.get_vumeter_color_scheme()
265         if color_scheme != 'solid':
266             self.meter.set_color(None)
267         else:
268             self.meter.set_color(Gdk.color_parse(color))
269
270     def on_custom_widgets_changed(self, gui_factory, value):
271         self.balance.destroy()
272         self.create_balance_widget()
273         self.create_slider_widget()
274
275     def on_abspeak_adjust(self, abspeak, adjust):
276         log.debug("abspeak adjust %f", adjust)
277         self.slider_adjustment.set_value_db(self.slider_adjustment.get_value_db() + adjust)
278         self.channel.abspeak = None
279         #self.update_volume(False)   # We want to update gui even if actual decibels have not changed (scale wrap for example)
280
281     def on_abspeak_reset(self, abspeak):
282         log.debug("abspeak reset")
283         self.channel.abspeak = None
284
285     def on_volume_digits_key_pressed(self, widget, event):
286         if (event.keyval == Gdk.KEY_Return or event.keyval == Gdk.KEY_KP_Enter):
287             db_text = self.volume_digits.get_text()
288             try:
289                 db = float(db_text)
290                 log.debug('Volume digits confirmation "%f dBFS".', db)
291             except (ValueError) as e:
292                 log.debug("Volume digits confirmation ignore, reset to current.")
293                 self.update_volume(False)
294                 return
295             self.slider_adjustment.set_value_db(db)
296             #self.grab_focus()
297             #self.update_volume(False)   # We want to update gui even if actual decibels have not changed (scale wrap for example)
298
299     def on_volume_digits_focus_out(self, widget, event):
300         log.debug("Volume digits focus out detected.")
301         self.update_volume(False)
302
303     def read_meter(self):
304         if not self.channel:
305             return
306         if self.stereo:
307             meter_left, meter_right = self.channel.meter
308             self.meter.set_values(meter_left, meter_right)
309         else:
310             self.meter.set_value(self.channel.meter[0])
311
312         self.abspeak.set_peak(self.channel.abspeak)
313
314     def on_scroll(self, widget, event):
315         if event.direction == Gdk.ScrollDirection.DOWN:
316             self.slider_adjustment.step_down()
317         elif event.direction == Gdk.ScrollDirection.UP:
318             self.slider_adjustment.step_up()
319         return True
320
321     def update_volume(self, update_engine, from_midi = False):
322         db = self.slider_adjustment.get_value_db()
323
324         db_text = "%.2f" % db
325         self.volume_digits.set_text(db_text)
326
327         if update_engine:
328             if not from_midi:
329                 self.channel.volume = db
330             else:
331                 self.channel.set_volume_from_midi(db)
332             self.app.update_monitor(self)
333
334     def on_volume_changed(self, adjustment):
335         self.update_volume(True)
336
337     def on_volume_changed_from_midi(self, adjustment):
338         self.update_volume(True, from_midi = True)
339
340     def on_balance_changed(self, adjustment):
341         balance = self.balance_adjustment.get_value()
342         log.debug("%s balance: %f", self.channel_name, balance)
343         self.channel.balance = balance
344         self.app.update_monitor(self)
345
346     def on_volume_changed_from_midi(self, adjustment):
347         balance = self.balance_adjustment.get_value()
348         log.debug("%s balance from midi: %f", self.channel_name, balance)
349         self.channel.set_balance_from_midi(balance)
350         self.app.update_monitor(self)
351
352     def on_key_pressed(self, widget, event):
353         if (event.keyval == Gdk.KEY_Up):
354             log.debug(self.channel_name + " Up")
355             self.slider_adjustment.step_up()
356             return True
357         elif (event.keyval == Gdk.KEY_Down):
358             log.debug(self.channel_name + " Down")
359             self.slider_adjustment.step_down()
360             return True
361
362         return False
363
364     def serialize(self, object_backend):
365         object_backend.add_property("volume", "%f" % self.slider_adjustment.get_value_db())
366         object_backend.add_property("balance", "%f" % self.balance_adjustment.get_value())
367
368         if hasattr(self.channel, 'out_mute'):
369             object_backend.add_property('out_mute', str(self.channel.out_mute))
370         if self.channel.volume_midi_cc != -1:
371             object_backend.add_property('volume_midi_cc', str(self.channel.volume_midi_cc))
372         if self.channel.balance_midi_cc != -1:
373             object_backend.add_property('balance_midi_cc', str(self.channel.balance_midi_cc))
374         if self.channel.mute_midi_cc != -1:
375             object_backend.add_property('mute_midi_cc', str(self.channel.mute_midi_cc))
376         if self.channel.solo_midi_cc != -1:
377             object_backend.add_property('solo_midi_cc', str(self.channel.solo_midi_cc))
378
379
380     def unserialize_property(self, name, value):
381         if name == "volume":
382             self.slider_adjustment.set_value_db(float(value))
383             return True
384         if name == "balance":
385             self.balance_adjustment.set_value(float(value))
386             return True
387         if name == 'out_mute':
388             self.future_out_mute = (value == 'True')
389             return True
390         if name == 'volume_midi_cc':
391             self.future_volume_midi_cc = int(value)
392             return True
393         if name == 'balance_midi_cc':
394             self.future_balance_midi_cc = int(value)
395             return True
396         if name == 'mute_midi_cc':
397             self.future_mute_midi_cc = int(value)
398             return True
399         if name == 'solo_midi_cc':
400             self.future_solo_midi_cc = int(value)
401             return True
402         return False
403
404     def on_midi_event_received(self, *args):
405         self.slider_adjustment.set_value_db(self.channel.volume, from_midi = True)
406         self.balance_adjustment.set_balance(self.channel.balance, from_midi = True)
407
408     def on_monitor_button_toggled(self, button):
409         if button.get_active():
410             for channel in self.app.channels + self.app.output_channels:
411                 if channel.monitor_button.get_active() and channel.monitor_button is not button:
412                     channel.monitor_button.handler_block_by_func(
413                                 channel.on_monitor_button_toggled)
414                     channel.monitor_button.set_active(False)
415                     channel.monitor_button.handler_unblock_by_func(
416                                 channel.on_monitor_button_toggled)
417             self.app.set_monitored_channel(self)
418
419     def set_monitored(self):
420         if self.channel:
421             self.app.set_monitored_channel(self)
422         self.monitor_button.set_active(True)
423
424     def set_color(self, color):
425         self.color = color
426         set_background_color(self.label_name_event_box, self.css_name, self.color.to_string())
427
428 class InputChannel(Channel):
429     post_fader_output_channel = None
430
431     def __init__(self, app, name, stereo, value = None):
432         Channel.__init__(self, app, name, stereo, value)
433
434     def realize(self):
435         self.channel = self.mixer.add_channel(self.channel_name, self.stereo)
436
437         if self.channel == None:
438             raise Exception("Cannot create a channel")
439         Channel.realize(self)
440         if self.future_volume_midi_cc != None:
441             self.channel.volume_midi_cc = self.future_volume_midi_cc
442         if self.future_balance_midi_cc != None:
443             self.channel.balance_midi_cc = self.future_balance_midi_cc
444         if self.future_mute_midi_cc != None:
445             self.channel.mute_midi_cc = self.future_mute_midi_cc
446         if self.future_solo_midi_cc != None:
447             self.channel.solo_midi_cc = self.future_solo_midi_cc
448         if self.app._init_solo_channels and self.channel_name in self.app._init_solo_channels:
449             self.channel.solo = True
450
451         self.channel.midi_scale = self.slider_scale.scale
452
453         self.on_volume_changed(self.slider_adjustment)
454         self.on_balance_changed(self.balance_adjustment)
455
456         # vbox child at upper part
457         self.vbox = Gtk.VBox()
458         self.pack_start(self.vbox, False, True, 0)
459         self.label_name = Gtk.Label()
460         self.label_name.get_style_context().add_class('top_label')
461         self.label_name.set_text(self.channel_name)
462         self.label_name.set_width_chars(0)
463         self.label_name_event_box = Gtk.EventBox()
464         self.label_name_event_box.connect("button-press-event", self.on_label_mouse)
465         self.label_name_event_box.add(self.label_name)
466         self.vbox.pack_start(self.label_name_event_box, True, True, 0)
467 #         self.label_stereo = Gtk.Label()
468 #         if self.stereo:
469 #             self.label_stereo.set_text("stereo")
470 #         else:
471 #             self.label_stereo.set_text("mono")
472 #         self.label_stereo.set_size_request(0, -1)
473 #         self.vbox.pack_start(self.label_stereo, True)
474
475         frame = Gtk.Frame()
476         frame.set_shadow_type(Gtk.ShadowType.IN)
477         frame.add(self.abspeak);
478         self.pack_start(frame, False, True, 0)
479
480         # hbox child at lower part
481         self.hbox = Gtk.HBox()
482         self.hbox.pack_start(self.slider, True, True, 0)
483         frame = Gtk.Frame()
484         frame.set_shadow_type(Gtk.ShadowType.IN)
485         frame.add(self.meter);
486         self.hbox.pack_start(frame, True, True, 0)
487         frame = Gtk.Frame()
488         frame.set_shadow_type(Gtk.ShadowType.IN)
489         frame.add(self.hbox);
490         self.pack_start(frame, True, True, 0)
491
492         self.volume_digits.set_width_chars(6)
493         self.pack_start(self.volume_digits, False, False, 0)
494
495         self.create_balance_widget()
496
497         self.hbox_mutesolo = Gtk.Box(False, 0)
498
499         self.mute = Gtk.ToggleButton()
500         self.mute.set_label("M")
501         self.mute.set_name("mute")
502         self.mute.set_active(self.channel.out_mute)
503         self.mute.connect("toggled", self.on_mute_toggled)
504         self.hbox_mutesolo.pack_start(self.mute, True, True, 0)
505
506         self.solo = Gtk.ToggleButton()
507         self.solo.set_label("S")
508         self.solo.set_name("solo")
509         self.solo.set_active(self.channel.solo)
510         self.solo.connect("toggled", self.on_solo_toggled)
511         self.hbox_mutesolo.pack_start(self.solo, True, True, 0)
512
513         self.pack_start(self.hbox_mutesolo, False, False, 0)
514
515         self.monitor_button = Gtk.ToggleButton('MON')
516         self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
517         self.pack_start(self.monitor_button, False, False, 0)
518
519     def add_control_group(self, channel):
520         control_group = ControlGroup(channel, self)
521         control_group.show_all()
522         self.vbox.pack_start(control_group, True, True, 0)
523         return control_group
524
525     def remove_control_group(self, channel):
526         ctlgroup = self.get_control_group(channel)
527         self.vbox.remove(ctlgroup)
528
529     def update_control_group(self, channel):
530         for control_group in self.vbox.get_children():
531             if isinstance(control_group, ControlGroup):
532                 if control_group.output_channel is channel:
533                     control_group.update()
534
535     def get_control_group(self, channel):
536         for control_group in self.vbox.get_children():
537             if isinstance(control_group, ControlGroup):
538                 if control_group.output_channel is channel:
539                     return control_group
540         return None
541
542     def unrealize(self):
543         Channel.unrealize(self)
544         if self.post_fader_output_channel:
545             self.post_fader_output_channel.remove()
546             self.post_fader_output_channel = None
547         self.channel.remove()
548         self.channel = None
549
550     channel_properties_dialog = None
551
552     def on_channel_properties(self):
553         if not self.channel_properties_dialog:
554             self.channel_properties_dialog = ChannelPropertiesDialog(self, self.app)
555         self.channel_properties_dialog.show()
556         self.channel_properties_dialog.present()
557
558     def on_label_mouse(self, widget, event):
559         if event.type == Gdk.EventType._2BUTTON_PRESS:
560             if event.button == 1:
561                 self.on_channel_properties()
562
563     def on_mute_toggled(self, button):
564         self.channel.out_mute = self.mute.get_active()
565
566     def on_solo_toggled(self, button):
567         self.channel.solo = self.solo.get_active()
568
569     def midi_events_check(self):
570         if hasattr(self, 'channel') and self.channel.midi_in_got_events:
571             self.mute.set_active(self.channel.out_mute)
572             self.solo.set_active(self.channel.solo)
573             Channel.on_midi_event_received(self)
574
575     def on_solo_button_pressed(self, button, event, *args):
576         if event.button == 3:
577             # right click on the solo button, act on all output channels
578             if button.get_active(): # was soloed
579                 button.set_active(False)
580                 if hasattr(button, 'touched_channels'):
581                     touched_channels = button.touched_channels
582                     for chan in touched_channels:
583                         ctlgroup = self.get_control_group(chan)
584                         ctlgroup.solo.set_active(False)
585                     del button.touched_channels
586             else: # was not soloed
587                 button.set_active(True)
588                 touched_channels = []
589                 for chan in self.app.output_channels:
590                     ctlgroup = self.get_control_group(chan)
591                     if not ctlgroup.solo.get_active():
592                         ctlgroup.solo.set_active(True)
593                         touched_channels.append(chan)
594                 button.touched_channels = touched_channels
595             return True
596         return False
597
598     @classmethod
599     def serialization_name(cls):
600         return 'input_channel'
601
602     def serialize(self, object_backend):
603         object_backend.add_property("name", self.channel_name)
604         if self.stereo:
605             object_backend.add_property("type", "stereo")
606         else:
607             object_backend.add_property("type", "mono")
608         Channel.serialize(self, object_backend)
609
610     def unserialize_property(self, name, value):
611         if name == "name":
612             self.channel_name = str(value)
613             return True
614         if name == "type":
615             if value == "stereo":
616                 self.stereo = True
617                 return True
618             if value == "mono":
619                 self.stereo = False
620                 return True
621         return Channel.unserialize_property(self, name, value)
622
623 class OutputChannel(Channel):
624     _display_solo_buttons = False
625
626     _init_muted_channels = None
627     _init_solo_channels = None
628
629     def __init__(self, app, name, stereo, value = None):
630         Channel.__init__(self, app, name, stereo, value)
631
632     def get_display_solo_buttons(self):
633         return self._display_solo_buttons
634
635     def set_display_solo_buttons(self, value):
636         self._display_solo_buttons = value
637         # notifying control groups
638         for inputchannel in self.app.channels:
639             inputchannel.update_control_group(self)
640
641     display_solo_buttons = property(get_display_solo_buttons, set_display_solo_buttons)
642
643     def realize(self):
644         self.channel = self.mixer.add_output_channel(self.channel_name, self.stereo)
645         if self.channel == None:
646             raise Exception("Cannot create a channel")
647         Channel.realize(self)
648         if self.future_volume_midi_cc != None:
649             self.channel.volume_midi_cc = self.future_volume_midi_cc
650         if self.future_balance_midi_cc != None:
651             self.channel.balance_midi_cc = self.future_balance_midi_cc
652         if self.future_mute_midi_cc != None:
653             self.channel.mute_midi_cc = self.future_mute_midi_cc
654         self.channel.midi_scale = self.slider_scale.scale
655
656         self.on_volume_changed(self.slider_adjustment)
657         self.on_balance_changed(self.balance_adjustment)
658
659         # vbox child at upper part
660         self.vbox = Gtk.VBox()
661         self.pack_start(self.vbox, False, True, 0)
662         self.label_name = Gtk.Label()
663         self.label_name.get_style_context().add_class('top_label')
664         self.label_name.set_text(self.channel_name)
665         self.label_name.set_width_chars(0)
666         self.label_name_event_box = Gtk.EventBox()
667         self.label_name_event_box.connect('button-press-event', self.on_label_mouse)
668         self.label_name_event_box.add(self.label_name)
669         if not hasattr(self, 'color'):
670             self.color = random_color()
671         set_background_color(self.label_name_event_box, self.css_name,
672                self.color.to_string())
673         self.vbox.pack_start(self.label_name_event_box, True, True, 0)
674         frame = Gtk.Frame()
675         frame.set_shadow_type(Gtk.ShadowType.IN)
676         frame.add(self.abspeak);
677         self.vbox.pack_start(frame, False, True, 0)
678
679         # hbox child at lower part
680         self.hbox = Gtk.HBox()
681         self.hbox.pack_start(self.slider, True, True, 0)
682         frame = Gtk.Frame()
683         frame.set_shadow_type(Gtk.ShadowType.IN)
684         frame.add(self.meter);
685         self.hbox.pack_start(frame, True, True, 0)
686         frame = Gtk.Frame()
687         frame.set_shadow_type(Gtk.ShadowType.IN)
688         frame.add(self.hbox);
689         self.pack_start(frame, True, True, 0)
690
691         self.volume_digits.set_width_chars(6)
692         self.pack_start(self.volume_digits, False, True, 0)
693
694         self.create_balance_widget()
695
696         self.mute = Gtk.ToggleButton()
697         self.mute.set_label("M")
698         self.mute.set_name("mute")
699         self.mute.set_active(self.channel.out_mute)
700         self.mute.connect("toggled", self.on_mute_toggled)
701
702         hbox = Gtk.HBox()
703         hbox.pack_start(self.mute, True, True, 0)
704         self.pack_start(hbox, False, False, 0)
705
706         self.monitor_button = Gtk.ToggleButton('MON')
707         self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
708         self.pack_start(self.monitor_button, False, False, 0)
709
710         # add control groups to the input channels, and initialize them
711         # appropriately
712         for input_channel in self.app.channels:
713             ctlgroup = input_channel.add_control_group(self)
714             if self._init_muted_channels and input_channel.channel.name in self._init_muted_channels:
715                 ctlgroup.mute.set_active(True)
716             if self._init_solo_channels and input_channel.channel.name in self._init_solo_channels:
717                 ctlgroup.solo.set_active(True)
718         self._init_muted_channels = None
719         self._init_solo_channels = None
720
721     channel_properties_dialog = None
722     def on_channel_properties(self):
723         if not self.channel_properties_dialog:
724             self.channel_properties_dialog = OutputChannelPropertiesDialog(self, self.app)
725         self.channel_properties_dialog.show()
726         self.channel_properties_dialog.present()
727
728     def on_label_mouse(self, widget, event):
729         if event.type == Gdk.EventType._2BUTTON_PRESS:
730             if event.button == 1:
731                 self.on_channel_properties()
732
733     def on_mute_toggled(self, button):
734         self.channel.out_mute = self.mute.get_active()
735
736     def midi_events_check(self):
737         if self.channel != None and self.channel.midi_in_got_events:
738             self.mute.set_active(self.channel.out_mute)
739             Channel.on_midi_event_received(self)
740
741     def unrealize(self):
742         # remove control groups from input channels
743         for input_channel in self.app.channels:
744             input_channel.remove_control_group(self)
745         # then remove itself
746         Channel.unrealize(self)
747         self.channel.remove()
748         self.channel = None
749
750     @classmethod
751     def serialization_name(cls):
752         return 'output_channel'
753
754     def serialize(self, object_backend):
755         object_backend.add_property("name", self.channel_name)
756         if self.stereo:
757             object_backend.add_property("type", "stereo")
758         else:
759             object_backend.add_property("type", "mono")
760         if self.display_solo_buttons:
761             object_backend.add_property("solo_buttons", "true")
762         muted_channels = []
763         solo_channels = []
764         for input_channel in self.app.channels:
765             if self.channel.is_muted(input_channel.channel):
766                 muted_channels.append(input_channel)
767             if self.channel.is_solo(input_channel.channel):
768                 solo_channels.append(input_channel)
769         if muted_channels:
770             object_backend.add_property('muted_channels', '|'.join([x.channel.name for x in muted_channels]))
771         if solo_channels:
772             object_backend.add_property('solo_channels', '|'.join([x.channel.name for x in solo_channels]))
773         object_backend.add_property("color", self.color.to_string())
774         Channel.serialize(self, object_backend)
775
776     def unserialize_property(self, name, value):
777         if name == "name":
778             self.channel_name = str(value)
779             return True
780         if name == "type":
781             if value == "stereo":
782                 self.stereo = True
783                 return True
784             if value == "mono":
785                 self.stereo = False
786                 return True
787         if name == "solo_buttons":
788             if value == "true":
789                 self.display_solo_buttons = True
790                 return True
791         if name == 'muted_channels':
792             self._init_muted_channels = value.split('|')
793             return True
794         if name == 'solo_channels':
795             self._init_solo_channels = value.split('|')
796             return True
797         if name == 'color':
798             c = Gdk.RGBA()
799             c.parse(value)
800             self.color = c
801             return True
802         return Channel.unserialize_property(self, name, value)
803
804 class ChannelPropertiesDialog(Gtk.Dialog):
805     channel = None
806
807     def __init__(self, parent, app):
808         self.channel = parent
809         self.app = app
810         self.mixer = self.channel.mixer
811         Gtk.Dialog.__init__(self, 'Channel "%s" Properties' % self.channel.channel_name, app.window)
812         self.set_default_size(365, -1)
813
814         self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
815         self.ok_button = self.add_button(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY)
816         self.set_default_response(Gtk.ResponseType.APPLY);
817
818         self.create_ui()
819         self.fill_ui()
820
821         self.connect('response', self.on_response_cb)
822         self.connect('delete-event', self.on_response_cb)
823
824     def create_frame(self, label, child):
825         frame = Gtk.Frame()
826         frame.set_label('')
827         frame.set_border_width(3)
828         #frame.set_shadow_type(Gtk.ShadowType.NONE)
829         frame.get_label_widget().set_markup('<b>%s</b>' % label)
830
831         alignment = Gtk.Alignment.new(0, 0, 1, 1)
832         alignment.set_padding(0, 0, 12, 0)
833         frame.add(alignment)
834         alignment.add(child)
835
836         return frame
837
838     def create_ui(self):
839         vbox = Gtk.VBox()
840         self.vbox.add(vbox)
841
842         self.properties_table = table = Gtk.Table(4, 3, False)
843         vbox.pack_start(self.create_frame('Properties', table), True, True, 0)
844         table.set_row_spacings(5)
845         table.set_col_spacings(5)
846
847         table.attach(Gtk.Label(label='Name'), 0, 1, 0, 1)
848         self.entry_name = Gtk.Entry()
849         self.entry_name.set_activates_default(True)
850         self.entry_name.connect('changed', self.on_entry_name_changed)
851         table.attach(self.entry_name, 1, 2, 0, 1)
852
853         table.attach(Gtk.Label(label='Mode'), 0, 1, 1, 2)
854         self.mode_hbox = Gtk.HBox()
855         table.attach(self.mode_hbox, 1, 2, 1, 2)
856         self.mono = Gtk.RadioButton(label='Mono')
857         self.stereo = Gtk.RadioButton(label='Stereo', group=self.mono)
858         self.mode_hbox.pack_start(self.mono, True, True, 0)
859         self.mode_hbox.pack_start(self.stereo, True, True, 0)
860
861         table = Gtk.Table(2, 3, False)
862         vbox.pack_start(self.create_frame('MIDI Control Changes', table), True, True, 0)
863         table.set_row_spacings(5)
864         table.set_col_spacings(5)
865
866         cc_tooltip = "{} MIDI Control Change number (0-127, set to -1 to assign next free CC #)"
867         table.attach(Gtk.Label(label='Volume'), 0, 1, 0, 1)
868         self.entry_volume_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
869         self.entry_volume_cc.set_tooltip_text(cc_tooltip.format("Volume"))
870         table.attach(self.entry_volume_cc, 1, 2, 0, 1)
871         self.button_sense_midi_volume = Gtk.Button('Learn')
872         self.button_sense_midi_volume.connect('clicked',
873                         self.on_sense_midi_volume_clicked)
874         table.attach(self.button_sense_midi_volume, 2, 3, 0, 1)
875
876         table.attach(Gtk.Label(label='Balance'), 0, 1, 1, 2)
877         self.entry_balance_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
878         self.entry_balance_cc.set_tooltip_text(cc_tooltip.format("Balance"))
879         table.attach(self.entry_balance_cc, 1, 2, 1, 2)
880         self.button_sense_midi_balance = Gtk.Button('Learn')
881         self.button_sense_midi_balance.connect('clicked',
882                         self.on_sense_midi_balance_clicked)
883         table.attach(self.button_sense_midi_balance, 2, 3, 1, 2)
884
885         table.attach(Gtk.Label(label='Mute'), 0, 1, 2, 3)
886         self.entry_mute_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
887         self.entry_mute_cc.set_tooltip_text(cc_tooltip.format("Mute"))
888         table.attach(self.entry_mute_cc, 1, 2, 2, 3)
889         self.button_sense_midi_mute = Gtk.Button('Learn')
890         self.button_sense_midi_mute.connect('clicked',
891                         self.on_sense_midi_mute_clicked)
892         table.attach(self.button_sense_midi_mute, 2, 3, 2, 3)
893
894         if (isinstance(self, NewChannelDialog) or (self.channel and
895             isinstance(self.channel, InputChannel))):
896             table.attach(Gtk.Label(label='Solo'), 0, 1, 3, 4)
897             self.entry_solo_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
898             self.entry_solo_cc.set_tooltip_text(cc_tooltip.format("Solo"))
899             table.attach(self.entry_solo_cc, 1, 2, 3, 4)
900             self.button_sense_midi_solo = Gtk.Button('Learn')
901             self.button_sense_midi_solo.connect('clicked',
902                             self.on_sense_midi_solo_clicked)
903             table.attach(self.button_sense_midi_solo, 2, 3, 3, 4)
904
905         self.vbox.show_all()
906
907     def fill_ui(self):
908         self.entry_name.set_text(self.channel.channel_name)
909         if self.channel.channel.is_stereo:
910             self.stereo.set_active(True)
911         else:
912             self.mono.set_active(True)
913         self.mode_hbox.set_sensitive(False)
914         self.entry_volume_cc.set_value(self.channel.channel.volume_midi_cc)
915         self.entry_balance_cc.set_value(self.channel.channel.balance_midi_cc)
916         self.entry_mute_cc.set_value(self.channel.channel.mute_midi_cc)
917         if (self.channel and isinstance(self.channel, InputChannel)):
918             self.entry_solo_cc.set_value(self.channel.channel.solo_midi_cc)
919
920     def sense_popup_dialog(self, entry):
921         window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
922         window.set_destroy_with_parent(True)
923         window.set_transient_for(self)
924         window.set_decorated(False)
925         window.set_modal(True)
926         window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
927         window.set_border_width(10)
928
929         vbox = Gtk.VBox(10)
930         window.add(vbox)
931         window.timeout = 5
932         vbox.pack_start(Gtk.Label(label='Please move the MIDI control you want to use for this function.'), True, True, 0)
933         timeout_label = Gtk.Label(label='This window will close in 5 seconds')
934         vbox.pack_start(timeout_label, True, True, 0)
935         def close_sense_timeout(window, entry):
936             window.timeout -= 1
937             timeout_label.set_text('This window will close in %d seconds.' % window.timeout)
938             if window.timeout == 0:
939                 window.destroy()
940                 entry.set_value(self.mixer.last_midi_channel)
941                 return False
942             return True
943         window.show_all()
944         GObject.timeout_add_seconds(1, close_sense_timeout, window, entry)
945
946     def on_sense_midi_volume_clicked(self, *args):
947         self.mixer.last_midi_channel = int(self.entry_volume_cc.get_value())
948         self.sense_popup_dialog(self.entry_volume_cc)
949
950     def on_sense_midi_balance_clicked(self, *args):
951         self.mixer.last_midi_channel = int(self.entry_balance_cc.get_value())
952         self.sense_popup_dialog(self.entry_balance_cc)
953
954     def on_sense_midi_mute_clicked(self, *args):
955         self.mixer.last_midi_channel = int(self.entry_mute_cc.get_value())
956         self.sense_popup_dialog(self.entry_mute_cc)
957
958     def on_sense_midi_solo_clicked(self, *args):
959         self.mixer.last_midi_channel = int(self.entry_solo_cc.get_value())
960         self.sense_popup_dialog(self.entry_solo_cc)
961
962     def on_response_cb(self, dlg, response_id, *args):
963         self.channel.channel_properties_dialog = None
964         name = self.entry_name.get_text()
965         if response_id == Gtk.ResponseType.APPLY:
966             if name != self.channel.channel_name:
967                 self.channel.channel_name = name
968             for control in ('volume', 'balance', 'mute', 'solo'):
969                 widget = getattr(self, 'entry_{}_cc'.format(control), None)
970                 if widget is not None:
971                     value = int(widget.get_value())
972                     if value != -1:
973                         setattr(self.channel.channel, '{}_midi_cc'.format(control), value)
974         self.destroy()
975
976     def on_entry_name_changed(self, entry):
977         sensitive = False
978         if len(entry.get_text()):
979             if self.channel and self.channel.channel.name == entry.get_text():
980                 sensitive = True
981             elif entry.get_text() not in [x.channel.name for x in self.app.channels] + \
982                         [x.channel.name for x in self.app.output_channels] + ['MAIN']:
983                 sensitive = True
984         self.ok_button.set_sensitive(sensitive)
985
986
987 class NewChannelDialog(ChannelPropertiesDialog):
988     def create_ui(self):
989         ChannelPropertiesDialog.create_ui(self)
990         self.properties_table.attach(Gtk.Label(label='Value'), 0, 1, 2, 3)
991
992         self.value_hbox = Gtk.HBox()
993         self.properties_table.attach(self.value_hbox, 1, 2, 2, 3)
994         self.minus_inf = Gtk.RadioButton(label='-Inf')
995         self.zero_dB = Gtk.RadioButton(label='0dB', group=self.minus_inf)
996         self.value_hbox.pack_start(self.minus_inf, True, True, 0)
997         self.value_hbox.pack_start(self.zero_dB, True, True, 0)
998
999         self.vbox.show_all()
1000
1001 class NewInputChannelDialog(NewChannelDialog):
1002     def __init__(self, app):
1003         Gtk.Dialog.__init__(self, 'New Input Channel', app.window)
1004         self.set_default_size(365, -1)
1005         self.mixer = app.mixer
1006         self.app = app
1007         self.create_ui()
1008         self.fill_ui()
1009
1010         self.stereo.set_active(True) # default to stereo
1011
1012         self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1013         self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1014         self.ok_button.set_sensitive(False)
1015         self.set_default_response(Gtk.ResponseType.OK);
1016
1017     def fill_ui(self):
1018         self.entry_volume_cc.set_value(-1)
1019         self.entry_balance_cc.set_value(-1)
1020         self.entry_mute_cc.set_value(-1)
1021         self.entry_solo_cc.set_value(-1)
1022
1023     def get_result(self):
1024         log.debug('minus_inf active?: %s', self.zero_dB.get_active())
1025         return {'name': self.entry_name.get_text(),
1026                 'stereo': self.stereo.get_active(),
1027                 'volume_cc': int(self.entry_volume_cc.get_value()),
1028                 'balance_cc': int(self.entry_balance_cc.get_value()),
1029                 'mute_cc': int(self.entry_mute_cc.get_value()),
1030                 'solo_cc': int(self.entry_solo_cc.get_value()),
1031                 'value': self.minus_inf.get_active()
1032                }
1033
1034
1035 class OutputChannelPropertiesDialog(ChannelPropertiesDialog):
1036     def create_ui(self):
1037         NewChannelDialog.create_ui(self)
1038
1039         table = self.properties_table
1040         table.attach(Gtk.Label(label='Color'), 0, 1, 4, 5)
1041         self.color_chooser_button = Gtk.ColorButton()
1042         self.color_chooser_button.set_use_alpha(True)
1043         self.color_chooser_button.set_rgba(Gdk.RGBA(0, 0, 0, 0))
1044         table.attach(self.color_chooser_button, 1, 2, 4, 5)
1045
1046         vbox = Gtk.VBox()
1047         self.vbox.pack_start(self.create_frame('Input Channels', vbox), True, True, 0)
1048
1049         self.display_solo_buttons = Gtk.CheckButton('Display solo buttons')
1050         vbox.pack_start(self.display_solo_buttons, True, True, 0)
1051
1052         self.vbox.show_all()
1053
1054     def fill_ui(self):
1055         ChannelPropertiesDialog.fill_ui(self)
1056         self.display_solo_buttons.set_active(self.channel.display_solo_buttons)
1057         self.color_chooser_button.set_rgba(self.channel.color)
1058
1059     def on_response_cb(self, dlg, response_id, *args):
1060         ChannelPropertiesDialog.on_response_cb(self, dlg, response_id, *args)
1061         if response_id == Gtk.ResponseType.APPLY:
1062             self.channel.display_solo_buttons = self.display_solo_buttons.get_active()
1063             self.channel.set_color(self.color_chooser_button.get_rgba())
1064             for inputchannel in self.app.channels:
1065                 inputchannel.update_control_group(self.channel)
1066
1067
1068 class NewOutputChannelDialog(NewChannelDialog, OutputChannelPropertiesDialog):
1069     def __init__(self, app):
1070         Gtk.Dialog.__init__(self, 'New Output Channel', app.window)
1071         self.mixer = app.mixer
1072         self.app = app
1073         OutputChannelPropertiesDialog.create_ui(self)
1074         self.fill_ui()
1075         self.set_default_size(365, -1)
1076
1077         # TODO: disable mode for output channels as mono output channels may
1078         # not be correctly handled yet.
1079         self.mode_hbox.set_sensitive(False)
1080         self.stereo.set_active(True) # default to stereo
1081
1082         self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1083         self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1084         self.ok_button.set_sensitive(False)
1085         self.set_default_response(Gtk.ResponseType.OK);
1086
1087     def fill_ui(self):
1088         self.entry_volume_cc.set_value(-1)
1089         self.entry_balance_cc.set_value(-1)
1090         self.entry_mute_cc.set_value(-1)
1091
1092     def get_result(self):
1093         return {'name': self.entry_name.get_text(),
1094                 'stereo': self.stereo.get_active(),
1095                 'volume_cc': int(self.entry_volume_cc.get_value()),
1096                 'balance_cc': int(self.entry_balance_cc.get_value()),
1097                 'mute_cc': int(self.entry_mute_cc.get_value()),
1098                 'display_solo_buttons': self.display_solo_buttons.get_active(),
1099                 'color': self.color_chooser_button.get_rgba(),
1100                 'value': self.minus_inf.get_active()
1101                 }
1102
1103
1104 class ControlGroup(Gtk.Alignment):
1105     def __init__(self, output_channel, input_channel):
1106         GObject.GObject.__init__(self)
1107         self.set(0.5, 0.5, 1, 1)
1108         self.output_channel = output_channel
1109         self.input_channel = input_channel
1110         self.app = input_channel.app
1111
1112         self.hbox = Gtk.HBox()
1113         self.vbox = Gtk.VBox()
1114         self.add(self.vbox)
1115         self.buttons_box = Gtk.Box(False, button_padding)
1116
1117         set_background_color(self.vbox, output_channel.css_name,
1118                 output_channel.color.to_string())
1119
1120         self.vbox.pack_start(self.hbox, True, True, button_padding)
1121         css = b"""
1122         .control_group {
1123             min-width: 0px;
1124             padding: 0px;
1125         }
1126
1127         .control_group #label {
1128             font-size: smaller;
1129         }
1130
1131         .control_group #label,
1132         .control_group #mute,
1133         .control_group #solo {
1134             padding: 0px .2em;
1135         }
1136         """
1137
1138         css_provider = Gtk.CssProvider()
1139         css_provider.load_from_data(css)
1140         context = Gtk.StyleContext()
1141         screen = Gdk.Screen.get_default()
1142         context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
1143         hbox_context = self.hbox.get_style_context()
1144         hbox_context.add_class('control_group')
1145
1146         self.label = Gtk.Label(output_channel.channel.name)
1147         self.label.set_name("label")
1148         self.hbox.pack_start(self.label, False, False, button_padding)
1149         self.hbox.pack_end(self.buttons_box, False, False, button_padding)
1150         mute = Gtk.ToggleButton()
1151         mute.set_label("M")
1152         mute.set_name("mute")
1153         mute.connect("toggled", self.on_mute_toggled)
1154         self.mute = mute
1155         solo = Gtk.ToggleButton()
1156         solo.set_name("solo")
1157         solo.set_label("S")
1158         solo.connect("toggled", self.on_solo_toggled)
1159         self.solo = solo
1160
1161         if self.output_channel.display_solo_buttons:
1162             self.buttons_box.pack_end(solo, True, True, button_padding)
1163         self.buttons_box.pack_end(mute, True, True, button_padding)
1164
1165     def update(self):
1166         if self.output_channel.display_solo_buttons:
1167             if not self.solo in self.buttons_box.get_children():
1168                 self.buttons_box.pack_end(self.solo, True, True, button_padding)
1169                 self.buttons_box.reorder_child(self.mute, -1)
1170                 self.solo.show()
1171         else:
1172             if self.solo in self.hbox.get_children():
1173                 self.hbox.remove(self.solo)
1174
1175         self.label.set_text(self.output_channel.channel.name)
1176         set_background_color(self.vbox, self.output_channel.css_name, self.output_channel.color.to_string())
1177
1178     def on_mute_toggled(self, button):
1179         self.output_channel.channel.set_muted(self.input_channel.channel, button.get_active())
1180         self.app.update_monitor(self)
1181
1182     def on_solo_toggled(self, button):
1183         self.output_channel.channel.set_solo(self.input_channel.channel, button.get_active())
1184         self.app.update_monitor(self)