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