1 # This file is part of jack_mixer
3 # Copyright (C) 2006 Nedko Arnaudov <nedko@arnaudov.name>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; version 2 of the License
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21 from gi
.repository
import Gtk
22 from gi
.repository
import Gdk
23 from gi
.repository
import GObject
28 from serialization
import SerializedObject
36 log
= logging
.getLogger(__name__
)
39 .top_label {min-width: 100px;}
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
)
49 def set_background_color(widget
, name
, color_string
):
54 """ % (name
, color_string
)
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
)
62 widget_context
= widget
.get_style_context()
63 widget_context
.add_class(name
)
67 from random
import uniform
, seed
69 return Gdk
.RGBA(uniform(0, 1), uniform(0, 1), uniform(0, 1), 1)
72 class Channel(Gtk
.VBox
, SerializedObject
):
73 '''Widget with slider and meter used as base class for more specific
77 def __init__(self
, app
, name
, stereo
, value
= None):
78 Gtk
.VBox
.__init
__(self
)
80 self
.mixer
= app
.mixer
81 self
.gui_factory
= app
.gui_factory
82 self
._channel
_name
= name
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
97 def get_channel_name(self
):
98 return self
._channel
_name
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
107 self
.label_name
.set_text(name
)
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
)
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
119 self
.slider_adjustment
.connect("volume-changed", self
.on_volume_changed
)
120 self
.slider_adjustment
.connect("volume-changed-from-midi", self
.on_volume_changed_from_midi
)
121 self
.balance_adjustment
.connect("balance-changed", self
.on_balance_changed
)
124 self
.create_slider_widget()
127 self
.meter
= meter
.StereoMeterWidget(self
.meter_scale
)
129 self
.meter
= meter
.MonoMeterWidget(self
.meter_scale
)
130 self
.on_vumeter_color_changed(self
.gui_factory
)
132 self
.meter
.set_events(Gdk
.EventMask
.SCROLL_MASK
)
134 self
.gui_factory
.connect("default-meter-scale-changed", self
.on_default_meter_scale_changed
)
135 self
.gui_factory
.connect("default-slider-scale-changed", self
.on_default_slider_scale_changed
)
136 self
.gui_factory
.connect('vumeter-color-changed', self
.on_vumeter_color_changed
)
137 self
.gui_factory
.connect('vumeter-color-scheme-changed', self
.on_vumeter_color_changed
)
138 self
.gui_factory
.connect('use-custom-widgets-changed', self
.on_custom_widgets_changed
)
140 self
.abspeak
= abspeak
.AbspeakWidget()
141 self
.abspeak
.connect("reset", self
.on_abspeak_reset
)
142 self
.abspeak
.connect("volume-adjust", self
.on_abspeak_adjust
)
144 self
.volume_digits
= Gtk
.Entry()
145 self
.volume_digits
.set_property('xalign', 0.5)
146 self
.volume_digits
.connect("key-press-event", self
.on_volume_digits_key_pressed
)
147 self
.volume_digits
.connect("focus-out-event", self
.on_volume_digits_focus_out
)
149 if self
.initial_value
!= None:
150 if self
.initial_value
== True:
151 self
.slider_adjustment
.set_value(0)
153 self
.slider_adjustment
.set_value_db(0)
155 self
.connect("key-press-event", self
.on_key_pressed
)
156 self
.connect("scroll-event", self
.on_scroll
)
159 log
.debug('Unrealizing channel "%s".', self
.channel_name
)
162 def balance_preferred_width(self
):
165 def _preferred_height(self
):
168 def create_balance_widget(self
):
169 if self
.gui_factory
.use_custom_widgets
and phat
:
170 self
.balance
= phat
.HFanSlider()
171 self
.balance
.set_default_value(0)
172 self
.balance
.set_adjustment(self
.balance_adjustment
)
174 self
.balance
= Gtk
.Scale()
175 self
.balance
.get_preferred_width
= self
.balance_preferred_width
176 self
.balance
.get_preferred_height
= self
._preferred
_height
177 self
.balance
.set_orientation(Gtk
.Orientation
.HORIZONTAL
)
178 self
.balance
.set_adjustment(self
.balance_adjustment
)
179 self
.balance
.set_has_origin(False)
180 self
.balance
.set_draw_value(False)
181 self
.balance
.button_down
= False
182 self
.balance
.connect('button-press-event', self
.on_balance_button_press_event
)
183 self
.balance
.connect('button-release-event', self
.on_balance_button_release_event
)
184 self
.balance
.connect("motion-notify-event", self
.on_balance_motion_notify_event
)
185 self
.balance
.connect("scroll-event", self
.on_balance_scroll_event
)
188 self
.pack_start(self
.balance
, False, True, 0)
189 if self
.monitor_button
:
190 self
.reorder_child(self
.monitor_button
, -1)
193 def on_balance_button_press_event(self
, widget
, event
):
194 if event
.button
== 1 and event
.type == Gdk
.EventType
.BUTTON_PRESS
:
195 self
.balance
.button_down
= True
196 self
.balance
.button_down_x
= event
.x
197 self
.balance
.button_down_value
= self
.balance
.get_value()
199 if event
.button
== 1 and event
.type == Gdk
.EventType
._2BUTTON
_PRESS
:
200 self
.balance_adjustment
.set_balance(0)
204 def on_balance_button_release_event(self
, widget
, event
):
205 self
.balance
.button_down
= False
208 def on_balance_motion_notify_event(self
, widget
, event
):
209 slider_length
= widget
.get_allocation().width
- widget
.get_style_context().get_property('min-width', Gtk
.StateFlags
.NORMAL
)
210 if self
.balance
.button_down
:
211 delta_x
= (event
.x
- self
.balance
.button_down_x
) / slider_length
212 x
= self
.balance
.button_down_value
+ 2 * delta_x
217 self
.balance_adjustment
.set_balance(x
)
220 def on_balance_scroll_event(self
, widget
, event
):
222 delta
= bal
.get_adjustment().get_step_increment()
223 value
= bal
.get_value()
224 if event
.direction
== Gdk
.ScrollDirection
.UP
:
226 elif event
.direction
== Gdk
.ScrollDirection
.DOWN
:
228 elif event
.direction
== Gdk
.ScrollDirection
.SMOOTH
:
229 x
= value
- event
.delta_y
* delta
238 def create_slider_widget(self
):
241 parent
= self
.slider
.get_parent()
242 self
.slider
.destroy()
243 if self
.gui_factory
.use_custom_widgets
:
244 self
.slider
= slider
.CustomSliderWidget(self
.slider_adjustment
)
246 self
.slider
= slider
.GtkSlider(self
.slider_adjustment
)
248 parent
.pack_start(self
.slider
, True, True, 0)
249 parent
.reorder_child(self
.slider
, 0)
252 def on_default_meter_scale_changed(self
, gui_factory
, scale
):
253 log
.debug("Default meter scale change detected.")
254 self
.meter
.set_scale(scale
)
256 def on_default_slider_scale_changed(self
, gui_factory
, scale
):
257 log
.debug("Default slider scale change detected.")
258 self
.slider_scale
= scale
259 self
.slider_adjustment
.set_scale(scale
)
261 self
.channel
.midi_scale
= self
.slider_scale
.scale
263 def on_vumeter_color_changed(self
, gui_factory
, *args
):
264 color
= gui_factory
.get_vumeter_color()
265 color_scheme
= gui_factory
.get_vumeter_color_scheme()
266 if color_scheme
!= 'solid':
267 self
.meter
.set_color(None)
269 self
.meter
.set_color(Gdk
.color_parse(color
))
271 def on_custom_widgets_changed(self
, gui_factory
, value
):
272 self
.balance
.destroy()
273 self
.create_balance_widget()
274 self
.create_slider_widget()
276 def on_abspeak_adjust(self
, abspeak
, adjust
):
277 log
.debug("abspeak adjust %f", adjust
)
278 self
.slider_adjustment
.set_value_db(self
.slider_adjustment
.get_value_db() + adjust
)
279 self
.channel
.abspeak
= None
280 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
282 def on_abspeak_reset(self
, abspeak
):
283 log
.debug("abspeak reset")
284 self
.channel
.abspeak
= None
286 def on_volume_digits_key_pressed(self
, widget
, event
):
287 if (event
.keyval
== Gdk
.KEY_Return
or event
.keyval
== Gdk
.KEY_KP_Enter
):
288 db_text
= self
.volume_digits
.get_text()
291 log
.debug('Volume digits confirmation "%f dBFS".', db
)
292 except (ValueError) as e
:
293 log
.debug("Volume digits confirmation ignore, reset to current.")
294 self
.update_volume(False)
296 self
.slider_adjustment
.set_value_db(db
)
298 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
300 def on_volume_digits_focus_out(self
, widget
, event
):
301 log
.debug("Volume digits focus out detected.")
302 self
.update_volume(False)
304 def read_meter(self
):
308 meter_left
, meter_right
= self
.channel
.meter
309 self
.meter
.set_values(meter_left
, meter_right
)
311 self
.meter
.set_value(self
.channel
.meter
[0])
313 self
.abspeak
.set_peak(self
.channel
.abspeak
)
315 def on_scroll(self
, widget
, event
):
316 if event
.direction
== Gdk
.ScrollDirection
.DOWN
:
317 self
.slider_adjustment
.step_down()
318 elif event
.direction
== Gdk
.ScrollDirection
.UP
:
319 self
.slider_adjustment
.step_up()
322 def update_volume(self
, update_engine
, from_midi
= False):
323 db
= self
.slider_adjustment
.get_value_db()
325 db_text
= "%.2f" % db
326 self
.volume_digits
.set_text(db_text
)
330 self
.channel
.volume
= db
331 self
.app
.update_monitor(self
)
333 def on_volume_changed(self
, adjustment
):
334 self
.update_volume(True)
336 def on_volume_changed_from_midi(self
, adjustment
):
337 self
.update_volume(True, from_midi
= True)
339 def on_balance_changed(self
, adjustment
):
340 balance
= self
.balance_adjustment
.get_value()
341 log
.debug("%s balance: %f", self
.channel_name
, balance
)
342 self
.channel
.balance
= balance
343 self
.app
.update_monitor(self
)
345 def on_key_pressed(self
, widget
, event
):
346 if (event
.keyval
== Gdk
.KEY_Up
):
347 log
.debug(self
.channel_name
+ " Up")
348 self
.slider_adjustment
.step_up()
350 elif (event
.keyval
== Gdk
.KEY_Down
):
351 log
.debug(self
.channel_name
+ " Down")
352 self
.slider_adjustment
.step_down()
357 def serialize(self
, object_backend
):
358 object_backend
.add_property("volume", "%f" % self
.slider_adjustment
.get_value_db())
359 object_backend
.add_property("balance", "%f" % self
.balance_adjustment
.get_value())
361 if hasattr(self
.channel
, 'out_mute'):
362 object_backend
.add_property('out_mute', str(self
.channel
.out_mute
))
363 if self
.channel
.volume_midi_cc
!= -1:
364 object_backend
.add_property('volume_midi_cc', str(self
.channel
.volume_midi_cc
))
365 if self
.channel
.balance_midi_cc
!= -1:
366 object_backend
.add_property('balance_midi_cc', str(self
.channel
.balance_midi_cc
))
367 if self
.channel
.mute_midi_cc
!= -1:
368 object_backend
.add_property('mute_midi_cc', str(self
.channel
.mute_midi_cc
))
369 if self
.channel
.solo_midi_cc
!= -1:
370 object_backend
.add_property('solo_midi_cc', str(self
.channel
.solo_midi_cc
))
373 def unserialize_property(self
, name
, value
):
375 self
.slider_adjustment
.set_value_db(float(value
))
377 if name
== "balance":
378 self
.balance_adjustment
.set_value(float(value
))
380 if name
== 'out_mute':
381 self
.future_out_mute
= (value
== 'True')
383 if name
== 'volume_midi_cc':
384 self
.future_volume_midi_cc
= int(value
)
386 if name
== 'balance_midi_cc':
387 self
.future_balance_midi_cc
= int(value
)
389 if name
== 'mute_midi_cc':
390 self
.future_mute_midi_cc
= int(value
)
392 if name
== 'solo_midi_cc':
393 self
.future_solo_midi_cc
= int(value
)
397 def on_midi_event_received(self
, *args
):
398 self
.slider_adjustment
.set_value_db(self
.channel
.volume
, from_midi
= True)
399 self
.balance_adjustment
.set_balance(self
.channel
.balance
, from_midi
= True)
401 def on_monitor_button_toggled(self
, button
):
402 if button
.get_active():
403 for channel
in self
.app
.channels
+ self
.app
.output_channels
:
404 if channel
.monitor_button
.get_active() and channel
.monitor_button
is not button
:
405 channel
.monitor_button
.handler_block_by_func(
406 channel
.on_monitor_button_toggled
)
407 channel
.monitor_button
.set_active(False)
408 channel
.monitor_button
.handler_unblock_by_func(
409 channel
.on_monitor_button_toggled
)
410 self
.app
.set_monitored_channel(self
)
412 def set_monitored(self
):
414 self
.app
.set_monitored_channel(self
)
415 self
.monitor_button
.set_active(True)
417 def set_color(self
, color
):
419 set_background_color(self
.label_name_event_box
, self
.css_name
, self
.color
.to_string())
421 class InputChannel(Channel
):
422 post_fader_output_channel
= None
424 def __init__(self
, app
, name
, stereo
, value
= None):
425 Channel
.__init
__(self
, app
, name
, stereo
, value
)
428 self
.channel
= self
.mixer
.add_channel(self
.channel_name
, self
.stereo
)
430 if self
.channel
== None:
431 raise Exception("Cannot create a channel")
432 Channel
.realize(self
)
433 if self
.future_volume_midi_cc
!= None:
434 self
.channel
.volume_midi_cc
= self
.future_volume_midi_cc
435 if self
.future_balance_midi_cc
!= None:
436 self
.channel
.balance_midi_cc
= self
.future_balance_midi_cc
437 if self
.future_mute_midi_cc
!= None:
438 self
.channel
.mute_midi_cc
= self
.future_mute_midi_cc
439 if self
.future_solo_midi_cc
!= None:
440 self
.channel
.solo_midi_cc
= self
.future_solo_midi_cc
441 if self
.app
._init
_solo
_channels
and self
.channel_name
in self
.app
._init
_solo
_channels
:
442 self
.channel
.solo
= True
444 self
.channel
.midi_scale
= self
.slider_scale
.scale
446 self
.on_volume_changed(self
.slider_adjustment
)
447 self
.on_balance_changed(self
.balance_adjustment
)
449 # vbox child at upper part
450 self
.vbox
= Gtk
.VBox()
451 self
.pack_start(self
.vbox
, False, True, 0)
452 self
.label_name
= Gtk
.Label()
453 self
.label_name
.get_style_context().add_class('top_label')
454 self
.label_name
.set_text(self
.channel_name
)
455 self
.label_name
.set_width_chars(0)
456 self
.label_name_event_box
= Gtk
.EventBox()
457 self
.label_name_event_box
.connect("button-press-event", self
.on_label_mouse
)
458 self
.label_name_event_box
.add(self
.label_name
)
459 self
.vbox
.pack_start(self
.label_name_event_box
, True, True, 0)
460 # self.label_stereo = Gtk.Label()
462 # self.label_stereo.set_text("stereo")
464 # self.label_stereo.set_text("mono")
465 # self.label_stereo.set_size_request(0, -1)
466 # self.vbox.pack_start(self.label_stereo, True)
469 frame
.set_shadow_type(Gtk
.ShadowType
.IN
)
470 frame
.add(self
.abspeak
);
471 self
.pack_start(frame
, False, True, 0)
473 # hbox child at lower part
474 self
.hbox
= Gtk
.HBox()
475 self
.hbox
.pack_start(self
.slider
, True, True, 0)
477 frame
.set_shadow_type(Gtk
.ShadowType
.IN
)
478 frame
.add(self
.meter
);
479 self
.hbox
.pack_start(frame
, True, True, 0)
481 frame
.set_shadow_type(Gtk
.ShadowType
.IN
)
482 frame
.add(self
.hbox
);
483 self
.pack_start(frame
, True, True, 0)
485 self
.volume_digits
.set_width_chars(6)
486 self
.pack_start(self
.volume_digits
, False, False, 0)
488 self
.create_balance_widget()
490 self
.hbox_mutesolo
= Gtk
.Box(False, 0)
492 self
.mute
= Gtk
.ToggleButton()
493 self
.mute
.set_label("M")
494 self
.mute
.set_name("mute")
495 self
.mute
.set_active(self
.channel
.out_mute
)
496 self
.mute
.connect("toggled", self
.on_mute_toggled
)
497 self
.hbox_mutesolo
.pack_start(self
.mute
, True, True, 0)
499 self
.solo
= Gtk
.ToggleButton()
500 self
.solo
.set_label("S")
501 self
.solo
.set_name("solo")
502 self
.solo
.set_active(self
.channel
.solo
)
503 self
.solo
.connect("toggled", self
.on_solo_toggled
)
504 self
.hbox_mutesolo
.pack_start(self
.solo
, True, True, 0)
506 self
.pack_start(self
.hbox_mutesolo
, False, False, 0)
508 self
.monitor_button
= Gtk
.ToggleButton('MON')
509 self
.monitor_button
.connect('toggled', self
.on_monitor_button_toggled
)
510 self
.pack_start(self
.monitor_button
, False, False, 0)
512 def add_control_group(self
, channel
):
513 control_group
= ControlGroup(channel
, self
)
514 control_group
.show_all()
515 self
.vbox
.pack_start(control_group
, True, True, 0)
518 def remove_control_group(self
, channel
):
519 ctlgroup
= self
.get_control_group(channel
)
520 self
.vbox
.remove(ctlgroup
)
522 def update_control_group(self
, channel
):
523 for control_group
in self
.vbox
.get_children():
524 if isinstance(control_group
, ControlGroup
):
525 if control_group
.output_channel
is channel
:
526 control_group
.update()
528 def get_control_group(self
, channel
):
529 for control_group
in self
.vbox
.get_children():
530 if isinstance(control_group
, ControlGroup
):
531 if control_group
.output_channel
is channel
:
536 Channel
.unrealize(self
)
537 if self
.post_fader_output_channel
:
538 self
.post_fader_output_channel
.remove()
539 self
.post_fader_output_channel
= None
540 self
.channel
.remove()
543 channel_properties_dialog
= None
545 def on_channel_properties(self
):
546 if not self
.channel_properties_dialog
:
547 self
.channel_properties_dialog
= ChannelPropertiesDialog(self
, self
.app
)
548 self
.channel_properties_dialog
.show()
549 self
.channel_properties_dialog
.present()
551 def on_label_mouse(self
, widget
, event
):
552 if event
.type == Gdk
.EventType
._2BUTTON
_PRESS
:
553 if event
.button
== 1:
554 self
.on_channel_properties()
556 def on_mute_toggled(self
, button
):
557 self
.channel
.out_mute
= self
.mute
.get_active()
559 def on_solo_toggled(self
, button
):
560 self
.channel
.solo
= self
.solo
.get_active()
562 def midi_events_check(self
):
563 if hasattr(self
, 'channel') and self
.channel
.midi_in_got_events
:
564 self
.mute
.set_active(self
.channel
.out_mute
)
565 self
.solo
.set_active(self
.channel
.solo
)
566 Channel
.on_midi_event_received(self
)
568 def on_solo_button_pressed(self
, button
, event
, *args
):
569 if event
.button
== 3:
570 # right click on the solo button, act on all output channels
571 if button
.get_active(): # was soloed
572 button
.set_active(False)
573 if hasattr(button
, 'touched_channels'):
574 touched_channels
= button
.touched_channels
575 for chan
in touched_channels
:
576 ctlgroup
= self
.get_control_group(chan
)
577 ctlgroup
.solo
.set_active(False)
578 del button
.touched_channels
579 else: # was not soloed
580 button
.set_active(True)
581 touched_channels
= []
582 for chan
in self
.app
.output_channels
:
583 ctlgroup
= self
.get_control_group(chan
)
584 if not ctlgroup
.solo
.get_active():
585 ctlgroup
.solo
.set_active(True)
586 touched_channels
.append(chan
)
587 button
.touched_channels
= touched_channels
592 def serialization_name(cls
):
593 return 'input_channel'
595 def serialize(self
, object_backend
):
596 object_backend
.add_property("name", self
.channel_name
)
598 object_backend
.add_property("type", "stereo")
600 object_backend
.add_property("type", "mono")
601 Channel
.serialize(self
, object_backend
)
603 def unserialize_property(self
, name
, value
):
605 self
.channel_name
= str(value
)
608 if value
== "stereo":
614 return Channel
.unserialize_property(self
, name
, value
)
616 class OutputChannel(Channel
):
617 _display_solo_buttons
= False
619 _init_muted_channels
= None
620 _init_solo_channels
= None
621 _init_prefader_channels
= None
623 def __init__(self
, app
, name
, stereo
, value
= None):
624 Channel
.__init
__(self
, app
, name
, stereo
, value
)
626 def get_display_solo_buttons(self
):
627 return self
._display
_solo
_buttons
629 def set_display_solo_buttons(self
, value
):
630 self
._display
_solo
_buttons
= value
631 # notifying control groups
632 for inputchannel
in self
.app
.channels
:
633 inputchannel
.update_control_group(self
)
635 display_solo_buttons
= property(get_display_solo_buttons
, set_display_solo_buttons
)
638 self
.channel
= self
.mixer
.add_output_channel(self
.channel_name
, self
.stereo
)
639 if self
.channel
== None:
640 raise Exception("Cannot create a channel")
641 Channel
.realize(self
)
642 if self
.future_volume_midi_cc
!= None:
643 self
.channel
.volume_midi_cc
= self
.future_volume_midi_cc
644 if self
.future_balance_midi_cc
!= None:
645 self
.channel
.balance_midi_cc
= self
.future_balance_midi_cc
646 if self
.future_mute_midi_cc
!= None:
647 self
.channel
.mute_midi_cc
= self
.future_mute_midi_cc
648 self
.channel
.midi_scale
= self
.slider_scale
.scale
650 self
.on_volume_changed(self
.slider_adjustment
)
651 self
.on_balance_changed(self
.balance_adjustment
)
653 # vbox child at upper part
654 self
.vbox
= Gtk
.VBox()
655 self
.pack_start(self
.vbox
, False, True, 0)
656 self
.label_name
= Gtk
.Label()
657 self
.label_name
.get_style_context().add_class('top_label')
658 self
.label_name
.set_text(self
.channel_name
)
659 self
.label_name
.set_width_chars(0)
660 self
.label_name_event_box
= Gtk
.EventBox()
661 self
.label_name_event_box
.connect('button-press-event', self
.on_label_mouse
)
662 self
.label_name_event_box
.add(self
.label_name
)
663 if not hasattr(self
, 'color'):
664 self
.color
= random_color()
665 set_background_color(self
.label_name_event_box
, self
.css_name
,
666 self
.color
.to_string())
667 self
.vbox
.pack_start(self
.label_name_event_box
, True, True, 0)
669 frame
.set_shadow_type(Gtk
.ShadowType
.IN
)
670 frame
.add(self
.abspeak
);
671 self
.vbox
.pack_start(frame
, False, True, 0)
673 # hbox child at lower part
674 self
.hbox
= Gtk
.HBox()
675 self
.hbox
.pack_start(self
.slider
, True, True, 0)
677 frame
.set_shadow_type(Gtk
.ShadowType
.IN
)
678 frame
.add(self
.meter
);
679 self
.hbox
.pack_start(frame
, True, True, 0)
681 frame
.set_shadow_type(Gtk
.ShadowType
.IN
)
682 frame
.add(self
.hbox
);
683 self
.pack_start(frame
, True, True, 0)
685 self
.volume_digits
.set_width_chars(6)
686 self
.pack_start(self
.volume_digits
, False, True, 0)
688 self
.create_balance_widget()
690 self
.mute
= Gtk
.ToggleButton()
691 self
.mute
.set_label("M")
692 self
.mute
.set_name("mute")
693 self
.mute
.set_active(self
.channel
.out_mute
)
694 self
.mute
.connect("toggled", self
.on_mute_toggled
)
697 hbox
.pack_start(self
.mute
, True, True, 0)
698 self
.pack_start(hbox
, False, False, 0)
700 self
.monitor_button
= Gtk
.ToggleButton('MON')
701 self
.monitor_button
.connect('toggled', self
.on_monitor_button_toggled
)
702 self
.pack_start(self
.monitor_button
, False, False, 0)
704 # add control groups to the input channels, and initialize them
706 for input_channel
in self
.app
.channels
:
707 ctlgroup
= input_channel
.add_control_group(self
)
708 if self
._init
_muted
_channels
and input_channel
.channel
.name
in self
._init
_muted
_channels
:
709 ctlgroup
.mute
.set_active(True)
710 if self
._init
_solo
_channels
and input_channel
.channel
.name
in self
._init
_solo
_channels
:
711 ctlgroup
.solo
.set_active(True)
712 if self
._init
_prefader
_channels
and input_channel
.channel
.name
in self
._init
_prefader
_channels
:
713 ctlgroup
.prefader
.set_active(True)
714 self
._init
_muted
_channels
= None
715 self
._init
_solo
_channels
= None
716 self
._init
_prefader
_channels
= None
718 channel_properties_dialog
= None
719 def on_channel_properties(self
):
720 if not self
.channel_properties_dialog
:
721 self
.channel_properties_dialog
= OutputChannelPropertiesDialog(self
, self
.app
)
722 self
.channel_properties_dialog
.show()
723 self
.channel_properties_dialog
.present()
725 def on_label_mouse(self
, widget
, event
):
726 if event
.type == Gdk
.EventType
._2BUTTON
_PRESS
:
727 if event
.button
== 1:
728 self
.on_channel_properties()
730 def on_mute_toggled(self
, button
):
731 self
.channel
.out_mute
= self
.mute
.get_active()
733 def midi_events_check(self
):
734 if self
.channel
!= None and self
.channel
.midi_in_got_events
:
735 self
.mute
.set_active(self
.channel
.out_mute
)
736 Channel
.on_midi_event_received(self
)
739 # remove control groups from input channels
740 for input_channel
in self
.app
.channels
:
741 input_channel
.remove_control_group(self
)
743 Channel
.unrealize(self
)
744 self
.channel
.remove()
748 def serialization_name(cls
):
749 return 'output_channel'
751 def serialize(self
, object_backend
):
752 object_backend
.add_property("name", self
.channel_name
)
754 object_backend
.add_property("type", "stereo")
756 object_backend
.add_property("type", "mono")
757 if self
.display_solo_buttons
:
758 object_backend
.add_property("solo_buttons", "true")
761 prefader_in_channels
= []
762 for input_channel
in self
.app
.channels
:
763 if self
.channel
.is_muted(input_channel
.channel
):
764 muted_channels
.append(input_channel
)
765 if self
.channel
.is_solo(input_channel
.channel
):
766 solo_channels
.append(input_channel
)
767 if self
.channel
.is_in_prefader(input_channel
.channel
):
768 prefader_in_channels
.append(input_channel
)
770 object_backend
.add_property('muted_channels', '|'.join([x
.channel
.name
for x
in muted_channels
]))
772 object_backend
.add_property('solo_channels', '|'.join([x
.channel
.name
for x
in solo_channels
]))
773 if prefader_in_channels
:
774 object_backend
.add_property('prefader_channels', '|'.join([x
.channel
.name
for x
in prefader_in_channels
]))
775 object_backend
.add_property("color", self
.color
.to_string())
776 Channel
.serialize(self
, object_backend
)
778 def unserialize_property(self
, name
, value
):
780 self
.channel_name
= str(value
)
783 if value
== "stereo":
789 if name
== "solo_buttons":
791 self
.display_solo_buttons
= True
793 if name
== 'muted_channels':
794 self
._init
_muted
_channels
= value
.split('|')
796 if name
== 'solo_channels':
797 self
._init
_solo
_channels
= value
.split('|')
799 if name
== 'prefader_channels':
800 self
._init
_prefader
_channels
= value
.split('|')
807 return Channel
.unserialize_property(self
, name
, value
)
809 class ChannelPropertiesDialog(Gtk
.Dialog
):
812 def __init__(self
, parent
, app
):
813 self
.channel
= parent
815 self
.mixer
= self
.channel
.mixer
816 Gtk
.Dialog
.__init
__(self
, 'Channel "%s" Properties' % self
.channel
.channel_name
, app
.window
)
817 self
.set_default_size(365, -1)
819 self
.add_button(Gtk
.STOCK_CANCEL
, Gtk
.ResponseType
.CANCEL
)
820 self
.ok_button
= self
.add_button(Gtk
.STOCK_APPLY
, Gtk
.ResponseType
.APPLY
)
821 self
.set_default_response(Gtk
.ResponseType
.APPLY
);
826 self
.connect('response', self
.on_response_cb
)
827 self
.connect('delete-event', self
.on_response_cb
)
829 def create_frame(self
, label
, child
):
832 frame
.set_border_width(3)
833 #frame.set_shadow_type(Gtk.ShadowType.NONE)
834 frame
.get_label_widget().set_markup('<b>%s</b>' % label
)
836 alignment
= Gtk
.Alignment
.new(0, 0, 1, 1)
837 alignment
.set_padding(0, 0, 12, 0)
847 self
.properties_table
= table
= Gtk
.Table(4, 3, False)
848 vbox
.pack_start(self
.create_frame('Properties', table
), True, True, 0)
849 table
.set_row_spacings(5)
850 table
.set_col_spacings(5)
852 table
.attach(Gtk
.Label(label
='Name'), 0, 1, 0, 1)
853 self
.entry_name
= Gtk
.Entry()
854 self
.entry_name
.set_activates_default(True)
855 self
.entry_name
.connect('changed', self
.on_entry_name_changed
)
856 table
.attach(self
.entry_name
, 1, 2, 0, 1)
858 table
.attach(Gtk
.Label(label
='Mode'), 0, 1, 1, 2)
859 self
.mode_hbox
= Gtk
.HBox()
860 table
.attach(self
.mode_hbox
, 1, 2, 1, 2)
861 self
.mono
= Gtk
.RadioButton(label
='Mono')
862 self
.stereo
= Gtk
.RadioButton(label
='Stereo', group
=self
.mono
)
863 self
.mode_hbox
.pack_start(self
.mono
, True, True, 0)
864 self
.mode_hbox
.pack_start(self
.stereo
, True, True, 0)
866 table
= Gtk
.Table(2, 3, False)
867 vbox
.pack_start(self
.create_frame('MIDI Control Changes', table
), True, True, 0)
868 table
.set_row_spacings(5)
869 table
.set_col_spacings(5)
871 cc_tooltip
= "{} MIDI Control Change number (0-127, set to -1 to assign next free CC #)"
872 table
.attach(Gtk
.Label(label
='Volume'), 0, 1, 0, 1)
873 self
.entry_volume_cc
= Gtk
.SpinButton
.new_with_range(-1, 127, 1)
874 self
.entry_volume_cc
.set_tooltip_text(cc_tooltip
.format("Volume"))
875 table
.attach(self
.entry_volume_cc
, 1, 2, 0, 1)
876 self
.button_sense_midi_volume
= Gtk
.Button('Learn')
877 self
.button_sense_midi_volume
.connect('clicked',
878 self
.on_sense_midi_volume_clicked
)
879 table
.attach(self
.button_sense_midi_volume
, 2, 3, 0, 1)
881 table
.attach(Gtk
.Label(label
='Balance'), 0, 1, 1, 2)
882 self
.entry_balance_cc
= Gtk
.SpinButton
.new_with_range(-1, 127, 1)
883 self
.entry_balance_cc
.set_tooltip_text(cc_tooltip
.format("Balance"))
884 table
.attach(self
.entry_balance_cc
, 1, 2, 1, 2)
885 self
.button_sense_midi_balance
= Gtk
.Button('Learn')
886 self
.button_sense_midi_balance
.connect('clicked',
887 self
.on_sense_midi_balance_clicked
)
888 table
.attach(self
.button_sense_midi_balance
, 2, 3, 1, 2)
890 table
.attach(Gtk
.Label(label
='Mute'), 0, 1, 2, 3)
891 self
.entry_mute_cc
= Gtk
.SpinButton
.new_with_range(-1, 127, 1)
892 self
.entry_mute_cc
.set_tooltip_text(cc_tooltip
.format("Mute"))
893 table
.attach(self
.entry_mute_cc
, 1, 2, 2, 3)
894 self
.button_sense_midi_mute
= Gtk
.Button('Learn')
895 self
.button_sense_midi_mute
.connect('clicked',
896 self
.on_sense_midi_mute_clicked
)
897 table
.attach(self
.button_sense_midi_mute
, 2, 3, 2, 3)
899 if (isinstance(self
, NewChannelDialog
) or (self
.channel
and
900 isinstance(self
.channel
, InputChannel
))):
901 table
.attach(Gtk
.Label(label
='Solo'), 0, 1, 3, 4)
902 self
.entry_solo_cc
= Gtk
.SpinButton
.new_with_range(-1, 127, 1)
903 self
.entry_solo_cc
.set_tooltip_text(cc_tooltip
.format("Solo"))
904 table
.attach(self
.entry_solo_cc
, 1, 2, 3, 4)
905 self
.button_sense_midi_solo
= Gtk
.Button('Learn')
906 self
.button_sense_midi_solo
.connect('clicked',
907 self
.on_sense_midi_solo_clicked
)
908 table
.attach(self
.button_sense_midi_solo
, 2, 3, 3, 4)
913 self
.entry_name
.set_text(self
.channel
.channel_name
)
914 if self
.channel
.channel
.is_stereo
:
915 self
.stereo
.set_active(True)
917 self
.mono
.set_active(True)
918 self
.mode_hbox
.set_sensitive(False)
919 self
.entry_volume_cc
.set_value(self
.channel
.channel
.volume_midi_cc
)
920 self
.entry_balance_cc
.set_value(self
.channel
.channel
.balance_midi_cc
)
921 self
.entry_mute_cc
.set_value(self
.channel
.channel
.mute_midi_cc
)
922 if (self
.channel
and isinstance(self
.channel
, InputChannel
)):
923 self
.entry_solo_cc
.set_value(self
.channel
.channel
.solo_midi_cc
)
925 def sense_popup_dialog(self
, entry
):
926 window
= Gtk
.Window
.new(Gtk
.WindowType
.TOPLEVEL
)
927 window
.set_destroy_with_parent(True)
928 window
.set_transient_for(self
)
929 window
.set_decorated(False)
930 window
.set_modal(True)
931 window
.set_position(Gtk
.WindowPosition
.CENTER_ON_PARENT
)
932 window
.set_border_width(10)
937 vbox
.pack_start(Gtk
.Label(label
='Please move the MIDI control you want to use for this function.'), True, True, 0)
938 timeout_label
= Gtk
.Label(label
='This window will close in 5 seconds')
939 vbox
.pack_start(timeout_label
, True, True, 0)
940 def close_sense_timeout(window
, entry
):
942 timeout_label
.set_text('This window will close in %d seconds.' % window
.timeout
)
943 if window
.timeout
== 0:
945 entry
.set_value(self
.mixer
.last_midi_channel
)
949 GObject
.timeout_add_seconds(1, close_sense_timeout
, window
, entry
)
951 def on_sense_midi_volume_clicked(self
, *args
):
952 self
.mixer
.last_midi_channel
= int(self
.entry_volume_cc
.get_value())
953 self
.sense_popup_dialog(self
.entry_volume_cc
)
955 def on_sense_midi_balance_clicked(self
, *args
):
956 self
.mixer
.last_midi_channel
= int(self
.entry_balance_cc
.get_value())
957 self
.sense_popup_dialog(self
.entry_balance_cc
)
959 def on_sense_midi_mute_clicked(self
, *args
):
960 self
.mixer
.last_midi_channel
= int(self
.entry_mute_cc
.get_value())
961 self
.sense_popup_dialog(self
.entry_mute_cc
)
963 def on_sense_midi_solo_clicked(self
, *args
):
964 self
.mixer
.last_midi_channel
= int(self
.entry_solo_cc
.get_value())
965 self
.sense_popup_dialog(self
.entry_solo_cc
)
967 def on_response_cb(self
, dlg
, response_id
, *args
):
968 self
.channel
.channel_properties_dialog
= None
969 name
= self
.entry_name
.get_text()
970 if response_id
== Gtk
.ResponseType
.APPLY
:
971 if name
!= self
.channel
.channel_name
:
972 self
.channel
.channel_name
= name
973 for control
in ('volume', 'balance', 'mute', 'solo'):
974 widget
= getattr(self
, 'entry_{}_cc'.format(control
), None)
975 if widget
is not None:
976 value
= int(widget
.get_value())
978 setattr(self
.channel
.channel
, '{}_midi_cc'.format(control
), value
)
981 def on_entry_name_changed(self
, entry
):
983 if len(entry
.get_text()):
984 if self
.channel
and self
.channel
.channel
.name
== entry
.get_text():
986 elif entry
.get_text() not in [x
.channel
.name
for x
in self
.app
.channels
] + \
987 [x
.channel
.name
for x
in self
.app
.output_channels
] + ['MAIN']:
989 self
.ok_button
.set_sensitive(sensitive
)
992 class NewChannelDialog(ChannelPropertiesDialog
):
994 ChannelPropertiesDialog
.create_ui(self
)
995 self
.properties_table
.attach(Gtk
.Label(label
='Value'), 0, 1, 2, 3)
997 self
.value_hbox
= Gtk
.HBox()
998 self
.properties_table
.attach(self
.value_hbox
, 1, 2, 2, 3)
999 self
.minus_inf
= Gtk
.RadioButton(label
='-Inf')
1000 self
.zero_dB
= Gtk
.RadioButton(label
='0dB', group
=self
.minus_inf
)
1001 self
.value_hbox
.pack_start(self
.minus_inf
, True, True, 0)
1002 self
.value_hbox
.pack_start(self
.zero_dB
, True, True, 0)
1004 self
.vbox
.show_all()
1006 class NewInputChannelDialog(NewChannelDialog
):
1007 def __init__(self
, app
):
1008 Gtk
.Dialog
.__init
__(self
, 'New Input Channel', app
.window
)
1009 self
.set_default_size(365, -1)
1010 self
.mixer
= app
.mixer
1015 self
.stereo
.set_active(True) # default to stereo
1017 self
.add_button(Gtk
.STOCK_CANCEL
, Gtk
.ResponseType
.CANCEL
)
1018 self
.ok_button
= self
.add_button(Gtk
.STOCK_ADD
, Gtk
.ResponseType
.OK
)
1019 self
.ok_button
.set_sensitive(False)
1020 self
.set_default_response(Gtk
.ResponseType
.OK
);
1023 self
.entry_volume_cc
.set_value(-1)
1024 self
.entry_balance_cc
.set_value(-1)
1025 self
.entry_mute_cc
.set_value(-1)
1026 self
.entry_solo_cc
.set_value(-1)
1028 def get_result(self
):
1029 log
.debug('minus_inf active?: %s', self
.zero_dB
.get_active())
1030 return {'name': self
.entry_name
.get_text(),
1031 'stereo': self
.stereo
.get_active(),
1032 'volume_cc': int(self
.entry_volume_cc
.get_value()),
1033 'balance_cc': int(self
.entry_balance_cc
.get_value()),
1034 'mute_cc': int(self
.entry_mute_cc
.get_value()),
1035 'solo_cc': int(self
.entry_solo_cc
.get_value()),
1036 'value': self
.minus_inf
.get_active()
1040 class OutputChannelPropertiesDialog(ChannelPropertiesDialog
):
1041 def create_ui(self
):
1042 ChannelPropertiesDialog
.create_ui(self
)
1044 table
= self
.properties_table
1045 table
.attach(Gtk
.Label(label
='Color'), 0, 1, 4, 5)
1046 self
.color_chooser_button
= Gtk
.ColorButton()
1047 self
.color_chooser_button
.set_use_alpha(True)
1048 self
.color_chooser_button
.set_rgba(Gdk
.RGBA(0, 0, 0, 0))
1049 table
.attach(self
.color_chooser_button
, 1, 2, 4, 5)
1052 self
.vbox
.pack_start(self
.create_frame('Input Channels', vbox
), True, True, 0)
1054 self
.display_solo_buttons
= Gtk
.CheckButton('Display solo buttons')
1055 vbox
.pack_start(self
.display_solo_buttons
, True, True, 0)
1057 self
.vbox
.show_all()
1060 ChannelPropertiesDialog
.fill_ui(self
)
1061 self
.display_solo_buttons
.set_active(self
.channel
.display_solo_buttons
)
1062 self
.color_chooser_button
.set_rgba(self
.channel
.color
)
1064 def on_response_cb(self
, dlg
, response_id
, *args
):
1065 ChannelPropertiesDialog
.on_response_cb(self
, dlg
, response_id
, *args
)
1066 if response_id
== Gtk
.ResponseType
.APPLY
:
1067 self
.channel
.display_solo_buttons
= self
.display_solo_buttons
.get_active()
1068 self
.channel
.set_color(self
.color_chooser_button
.get_rgba())
1069 for inputchannel
in self
.app
.channels
:
1070 inputchannel
.update_control_group(self
.channel
)
1073 class NewOutputChannelDialog(NewChannelDialog
, OutputChannelPropertiesDialog
):
1074 def __init__(self
, app
):
1075 Gtk
.Dialog
.__init
__(self
, 'New Output Channel', app
.window
)
1076 self
.mixer
= app
.mixer
1078 OutputChannelPropertiesDialog
.create_ui(self
)
1080 self
.set_default_size(365, -1)
1082 # TODO: disable mode for output channels as mono output channels may
1083 # not be correctly handled yet.
1084 self
.mode_hbox
.set_sensitive(False)
1085 self
.stereo
.set_active(True) # default to stereo
1087 self
.add_button(Gtk
.STOCK_CANCEL
, Gtk
.ResponseType
.CANCEL
)
1088 self
.ok_button
= self
.add_button(Gtk
.STOCK_ADD
, Gtk
.ResponseType
.OK
)
1089 self
.ok_button
.set_sensitive(False)
1090 self
.set_default_response(Gtk
.ResponseType
.OK
);
1093 self
.entry_volume_cc
.set_value(-1)
1094 self
.entry_balance_cc
.set_value(-1)
1095 self
.entry_mute_cc
.set_value(-1)
1097 def get_result(self
):
1098 return {'name': self
.entry_name
.get_text(),
1099 'stereo': self
.stereo
.get_active(),
1100 'volume_cc': int(self
.entry_volume_cc
.get_value()),
1101 'balance_cc': int(self
.entry_balance_cc
.get_value()),
1102 'mute_cc': int(self
.entry_mute_cc
.get_value()),
1103 'display_solo_buttons': self
.display_solo_buttons
.get_active(),
1104 'color': self
.color_chooser_button
.get_rgba(),
1105 'value': self
.minus_inf
.get_active()
1109 class ControlGroup(Gtk
.Alignment
):
1110 def __init__(self
, output_channel
, input_channel
):
1111 GObject
.GObject
.__init
__(self
)
1112 self
.set(0.5, 0.5, 1, 1)
1113 self
.output_channel
= output_channel
1114 self
.input_channel
= input_channel
1115 self
.app
= input_channel
.app
1117 self
.hbox
= Gtk
.HBox()
1118 self
.vbox
= Gtk
.VBox()
1120 self
.buttons_box
= Gtk
.Box(False, button_padding
)
1122 set_background_color(self
.vbox
, output_channel
.css_name
,
1123 output_channel
.color
.to_string())
1125 self
.vbox
.pack_start(self
.hbox
, True, True, button_padding
)
1132 .control_group #label,
1133 .control_group #mute,
1134 .control_group #pre_fader,
1135 .control_group #solo {
1141 css_provider
= Gtk
.CssProvider()
1142 css_provider
.load_from_data(css
)
1143 context
= Gtk
.StyleContext()
1144 screen
= Gdk
.Screen
.get_default()
1145 context
.add_provider_for_screen(screen
, css_provider
, Gtk
.STYLE_PROVIDER_PRIORITY_APPLICATION
)
1146 hbox_context
= self
.hbox
.get_style_context()
1147 hbox_context
.add_class('control_group')
1149 self
.label
= Gtk
.Label(output_channel
.channel
.name
)
1150 self
.label
.set_name("label")
1151 self
.hbox
.pack_start(self
.label
, False, False, button_padding
)
1152 self
.hbox
.pack_end(self
.buttons_box
, False, False, button_padding
)
1153 mute
= Gtk
.ToggleButton()
1155 mute
.set_name("mute")
1156 mute
.set_tooltip_text("Mute output channel send")
1157 mute
.connect("toggled", self
.on_mute_toggled
)
1159 solo
= Gtk
.ToggleButton()
1160 solo
.set_name("solo")
1162 solo
.set_tooltip_text("Solo output send")
1163 solo
.connect("toggled", self
.on_solo_toggled
)
1165 pre
= Gtk
.ToggleButton("P")
1166 pre
.set_name("pre_fader")
1167 pre
.set_tooltip_text("Pre (on) / Post (off) fader send")
1168 pre
.connect("toggled", self
.on_prefader_toggled
)
1170 self
.buttons_box
.pack_start(pre
, True, True, button_padding
)
1171 self
.buttons_box
.pack_start(mute
, True, True, button_padding
)
1172 if self
.output_channel
.display_solo_buttons
:
1173 self
.buttons_box
.pack_start(solo
, True, True, button_padding
)
1176 if self
.output_channel
.display_solo_buttons
:
1177 if not self
.solo
in self
.buttons_box
.get_children():
1178 self
.buttons_box
.pack_start(self
.solo
, True, True, button_padding
)
1181 if self
.solo
in self
.buttons_box
.get_children():
1182 self
.buttons_box
.remove(self
.solo
)
1184 self
.label
.set_text(self
.output_channel
.channel
.name
)
1185 set_background_color(self
.vbox
, self
.output_channel
.css_name
, self
.output_channel
.color
.to_string())
1187 def on_mute_toggled(self
, button
):
1188 self
.output_channel
.channel
.set_muted(self
.input_channel
.channel
, button
.get_active())
1189 self
.app
.update_monitor(self
)
1191 def on_solo_toggled(self
, button
):
1192 self
.output_channel
.channel
.set_solo(self
.input_channel
.channel
, button
.get_active())
1193 self
.app
.update_monitor(self
)
1195 def on_prefader_toggled(self
, button
):
1196 self
.output_channel
.channel
.set_in_prefader(self
.input_channel
.channel
, button
.get_active())