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.
19 from gi
.repository
import Gtk
20 from gi
.repository
import Gdk
21 from gi
.repository
import GObject
25 from serialization
import SerializedObject
35 :not(button) > label {min-width: 100px;}
39 css_provider
= Gtk
.CssProvider()
40 css_provider
.load_from_data(css
)
41 context
= Gtk
.StyleContext()
42 screen
= Gdk
.Screen
.get_default()
43 context
.add_provider_for_screen(screen
, css_provider
, Gtk
.STYLE_PROVIDER_PRIORITY_APPLICATION
)
45 def set_background_color(widget
, name
, color_string
):
50 """ % (name
, color_string
)
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
)
58 widget_context
= widget
.get_style_context()
59 widget_context
.add_class(name
)
62 from random
import uniform
, seed
64 return Gdk
.RGBA(uniform(0, 1), uniform(0, 1), uniform(0, 1), 1)
66 class Channel(Gtk
.VBox
, SerializedObject
):
67 '''Widget with slider and meter used as base class for more specific
71 def __init__(self
, app
, name
, stereo
):
72 Gtk
.VBox
.__init
__(self
)
74 self
.mixer
= app
.mixer
75 self
.gui_factory
= app
.gui_factory
76 self
._channel
_name
= name
78 self
.meter_scale
= self
.gui_factory
.get_default_meter_scale()
79 self
.slider_scale
= self
.gui_factory
.get_default_slider_scale()
80 self
.slider_adjustment
= slider
.AdjustmentdBFS(self
.slider_scale
, 0.0, 0.02)
81 self
.balance_adjustment
= slider
.BalanceAdjustment()
82 self
.future_out_mute
= None
83 self
.future_volume_midi_cc
= None
84 self
.future_balance_midi_cc
= None
85 self
.future_mute_midi_cc
= None
86 self
.future_solo_midi_cc
= None
87 self
.css_name
= "css_name_%d" % Channel
.num_instances
88 Channel
.num_instances
+= 1
90 def get_channel_name(self
):
91 return self
._channel
_name
95 post_fader_output_channel
= None
96 def set_channel_name(self
, name
):
97 self
.app
.on_channel_rename(self
._channel
_name
, name
);
98 self
._channel
_name
= name
100 self
.label_name
.set_text(name
)
102 self
.channel
.name
= name
103 if self
.post_fader_output_channel
:
104 self
.post_fader_output_channel
.name
= "%s Out" % name
;
105 channel_name
= property(get_channel_name
, set_channel_name
)
108 #print "Realizing channel \"%s\"" % self.channel_name
109 if self
.future_out_mute
!= None:
110 self
.channel
.out_mute
= self
.future_out_mute
112 self
.slider_adjustment
.connect("volume-changed", self
.on_volume_changed
)
113 self
.balance_adjustment
.connect("balance-changed", self
.on_balance_changed
)
116 self
.create_slider_widget()
119 self
.meter
= meter
.StereoMeterWidget(self
.meter_scale
)
121 self
.meter
= meter
.MonoMeterWidget(self
.meter_scale
)
122 self
.on_vumeter_color_changed(self
.gui_factory
)
124 self
.meter
.set_events(Gdk
.EventMask
.SCROLL_MASK
)
126 self
.gui_factory
.connect("default-meter-scale-changed", self
.on_default_meter_scale_changed
)
127 self
.gui_factory
.connect("default-slider-scale-changed", self
.on_default_slider_scale_changed
)
128 self
.gui_factory
.connect('vumeter-color-changed', self
.on_vumeter_color_changed
)
129 self
.gui_factory
.connect('vumeter-color-scheme-changed', self
.on_vumeter_color_changed
)
130 self
.gui_factory
.connect('use-custom-widgets-changed', self
.on_custom_widgets_changed
)
132 self
.abspeak
= abspeak
.AbspeakWidget()
133 self
.abspeak
.connect("reset", self
.on_abspeak_reset
)
134 self
.abspeak
.connect("volume-adjust", self
.on_abspeak_adjust
)
136 self
.volume_digits
= Gtk
.Entry()
137 self
.volume_digits
.set_property('xalign', 0.5)
138 self
.volume_digits
.connect("key-press-event", self
.on_volume_digits_key_pressed
)
139 self
.volume_digits
.connect("focus-out-event", self
.on_volume_digits_focus_out
)
141 self
.connect("key-press-event", self
.on_key_pressed
)
142 self
.connect("scroll-event", self
.on_scroll
)
145 #print "Unrealizing channel \"%s\"" % self.channel_name
148 def balance_preferred_width(self
):
151 def _preferred_height(self
):
154 def create_balance_widget(self
):
155 if self
.gui_factory
.use_custom_widgets
and phat
:
156 self
.balance
= phat
.HFanSlider()
157 self
.balance
.set_default_value(0)
158 self
.balance
.set_adjustment(self
.balance_adjustment
)
160 self
.balance
= Gtk
.Scale()
161 self
.balance
.get_preferred_width
= self
.balance_preferred_width
162 self
.balance
.get_preferred_height
= self
._preferred
_height
163 self
.balance
.set_orientation(Gtk
.Orientation
.HORIZONTAL
)
164 self
.balance
.set_adjustment(self
.balance_adjustment
)
165 self
.balance
.set_has_origin(False)
166 self
.balance
.set_draw_value(False)
167 self
.balance
.button_down
= False
168 self
.balance
.connect('button-press-event', self
.on_balance_button_press_event
)
169 self
.balance
.connect('button-release-event', self
.on_balance_button_release_event
)
170 self
.balance
.connect("motion-notify-event", self
.on_balance_motion_notify_event
)
171 self
.balance
.connect("scroll-event", self
.on_balance_scroll_event
)
174 self
.pack_start(self
.balance
, False, True, 0)
175 if self
.monitor_button
:
176 self
.reorder_child(self
.monitor_button
, -1)
179 def on_balance_button_press_event(self
, widget
, event
):
180 if event
.button
== 1 and event
.type == Gdk
.EventType
.BUTTON_PRESS
:
181 self
.balance
.button_down
= True
182 self
.balance
.button_down_x
= event
.x
183 self
.balance
.button_down_value
= self
.balance
.get_value()
185 if event
.button
== 1 and event
.type == Gdk
.EventType
._2BUTTON
_PRESS
:
186 self
.balance_adjustment
.set_balance(0)
190 def on_balance_button_release_event(self
, widget
, event
):
191 self
.balance
.button_down
= False
194 def on_balance_motion_notify_event(self
, widget
, event
):
195 slider_length
= widget
.get_allocation().width
- widget
.get_style_context().get_property('min-width', Gtk
.StateFlags
.NORMAL
)
196 if self
.balance
.button_down
:
197 delta_x
= (event
.x
- self
.balance
.button_down_x
) / slider_length
198 x
= self
.balance
.button_down_value
+ 2 * delta_x
203 self
.balance_adjustment
.set_balance(x
)
206 def on_balance_scroll_event(self
, widget
, event
):
208 delta
= bal
.get_adjustment().get_step_increment()
209 value
= bal
.get_value()
210 if event
.direction
== Gdk
.ScrollDirection
.UP
:
212 elif event
.direction
== Gdk
.ScrollDirection
.DOWN
:
214 elif event
.direction
== Gdk
.ScrollDirection
.SMOOTH
:
215 x
= value
- event
.delta_y
* delta
224 def create_slider_widget(self
):
227 parent
= self
.slider
.get_parent()
228 self
.slider
.destroy()
229 if self
.gui_factory
.use_custom_widgets
:
230 self
.slider
= slider
.CustomSliderWidget(self
.slider_adjustment
)
232 self
.slider
= slider
.GtkSlider(self
.slider_adjustment
)
234 parent
.pack_start(self
.slider
, True, True, 0)
235 parent
.reorder_child(self
.slider
, 0)
238 def on_default_meter_scale_changed(self
, gui_factory
, scale
):
239 #print "Default meter scale change detected."
240 self
.meter
.set_scale(scale
)
242 def on_default_slider_scale_changed(self
, gui_factory
, scale
):
243 #print "Default slider scale change detected."
244 self
.slider_scale
= scale
245 self
.slider_adjustment
.set_scale(scale
)
246 self
.channel
.midi_scale
= self
.slider_scale
.scale
248 def on_vumeter_color_changed(self
, gui_factory
, *args
):
249 color
= gui_factory
.get_vumeter_color()
250 color_scheme
= gui_factory
.get_vumeter_color_scheme()
251 if color_scheme
!= 'solid':
252 self
.meter
.set_color(None)
254 self
.meter
.set_color(Gdk
.color_parse(color
))
256 def on_custom_widgets_changed(self
, gui_factory
, value
):
257 self
.balance
.destroy()
258 self
.create_balance_widget()
259 self
.create_slider_widget()
261 def on_abspeak_adjust(self
, abspeak
, adjust
):
262 #print "abspeak adjust %f" % adjust
263 self
.slider_adjustment
.set_value_db(self
.slider_adjustment
.get_value_db() + adjust
)
264 self
.channel
.abspeak
= None
265 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
267 def on_abspeak_reset(self
, abspeak
):
268 #print "abspeak reset"
269 self
.channel
.abspeak
= None
271 def on_volume_digits_key_pressed(self
, widget
, event
):
272 if (event
.keyval
== Gdk
.KEY_Return
or event
.keyval
== Gdk
.KEY_KP_Enter
):
273 db_text
= self
.volume_digits
.get_text()
276 #print "Volume digits confirmation \"%f dBFS\"" % db
277 except (ValueError) as e
:
278 #print "Volume digits confirmation ignore, reset to current"
279 self
.update_volume(False)
281 self
.slider_adjustment
.set_value_db(db
)
283 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
285 def on_volume_digits_focus_out(self
, widget
, event
):
286 #print "volume digits focus out detected"
287 self
.update_volume(False)
289 def read_meter(self
):
293 meter_left
, meter_right
= self
.channel
.meter
294 self
.meter
.set_values(meter_left
, meter_right
)
296 self
.meter
.set_value(self
.channel
.meter
[0])
298 self
.abspeak
.set_peak(self
.channel
.abspeak
)
300 def on_scroll(self
, widget
, event
):
301 if event
.direction
== Gdk
.ScrollDirection
.DOWN
:
302 self
.slider_adjustment
.step_down()
303 elif event
.direction
== Gdk
.ScrollDirection
.UP
:
304 self
.slider_adjustment
.step_up()
307 def update_volume(self
, update_engine
, from_midi
= False):
308 db
= self
.slider_adjustment
.get_value_db()
310 db_text
= "%.2f" % db
311 self
.volume_digits
.set_text(db_text
)
315 self
.channel
.volume
= db
317 self
.channel
.set_volume_from_midi(db
)
318 self
.app
.update_monitor(self
)
320 def on_volume_changed(self
, adjustment
):
321 self
.update_volume(True)
323 def on_volume_changed_from_midi(self
, adjustment
):
324 self
.update_volume(True, from_midi
= True)
326 def on_balance_changed(self
, adjustment
):
327 balance
= self
.balance_adjustment
.get_value()
328 #print("%s balance: %f" % (self.channel_name, balance))
329 self
.channel
.balance
= balance
330 self
.app
.update_monitor(self
)
332 def on_volume_changed_from_midi(self
, adjustment
):
333 balance
= self
.balance_adjustment
.get_value()
334 #print("%s balance from midi: %f" % (self.channel_name, balance))
335 self
.channel
.set_balance_from_midi(balance
)
336 self
.app
.update_monitor(self
)
338 def on_key_pressed(self
, widget
, event
):
339 if (event
.keyval
== Gdk
.KEY_Up
):
340 #print self.channel_name + " Up"
341 self
.slider_adjustment
.step_up()
343 elif (event
.keyval
== Gdk
.KEY_Down
):
344 #print self.channel_name + " Down"
345 self
.slider_adjustment
.step_down()
350 def serialize(self
, object_backend
):
351 object_backend
.add_property("volume", "%f" % self
.slider_adjustment
.get_value_db())
352 object_backend
.add_property("balance", "%f" % self
.balance_adjustment
.get_value())
354 if hasattr(self
.channel
, 'out_mute'):
355 object_backend
.add_property('out_mute', str(self
.channel
.out_mute
))
356 if self
.channel
.volume_midi_cc
!= -1:
357 object_backend
.add_property('volume_midi_cc', str(self
.channel
.volume_midi_cc
))
358 if self
.channel
.balance_midi_cc
!= -1:
359 object_backend
.add_property('balance_midi_cc', str(self
.channel
.balance_midi_cc
))
360 if self
.channel
.mute_midi_cc
!= -1:
361 object_backend
.add_property('mute_midi_cc', str(self
.channel
.mute_midi_cc
))
362 if self
.channel
.solo_midi_cc
!= -1:
363 object_backend
.add_property('solo_midi_cc', str(self
.channel
.solo_midi_cc
))
366 def unserialize_property(self
, name
, value
):
368 self
.slider_adjustment
.set_value_db(float(value
))
370 if name
== "balance":
371 self
.balance_adjustment
.set_value(float(value
))
373 if name
== 'out_mute':
374 self
.future_out_mute
= (value
== 'True')
376 if name
== 'volume_midi_cc':
377 self
.future_volume_midi_cc
= int(value
)
379 if name
== 'balance_midi_cc':
380 self
.future_balance_midi_cc
= int(value
)
382 if name
== 'mute_midi_cc':
383 self
.future_mute_midi_cc
= int(value
)
385 if name
== 'solo_midi_cc':
386 self
.future_solo_midi_cc
= int(value
)
390 def on_midi_event_received(self
, *args
):
391 self
.slider_adjustment
.set_value_db(self
.channel
.volume
, from_midi
= True)
392 self
.balance_adjustment
.set_balance(self
.channel
.balance
, from_midi
= True)
394 def on_monitor_button_toggled(self
, button
):
395 if button
.get_active():
396 for channel
in self
.app
.channels
+ self
.app
.output_channels
:
397 if channel
.monitor_button
.get_active() and channel
.monitor_button
is not button
:
398 channel
.monitor_button
.handler_block_by_func(
399 channel
.on_monitor_button_toggled
)
400 channel
.monitor_button
.set_active(False)
401 channel
.monitor_button
.handler_unblock_by_func(
402 channel
.on_monitor_button_toggled
)
403 self
.app
.set_monitored_channel(self
)
405 def set_monitored(self
):
407 self
.app
.set_monitored_channel(self
)
408 self
.monitor_button
.set_active(True)
410 def set_color(self
, color
):
412 set_background_color(self
.label_name_event_box
, self
.css_name
, self
.color
.to_string())
414 class InputChannel(Channel
):
415 post_fader_output_channel
= None
417 def __init__(self
, app
, name
, stereo
):
418 Channel
.__init
__(self
, app
, name
, stereo
)
421 self
.channel
= self
.mixer
.add_channel(self
.channel_name
, self
.stereo
)
423 if self
.channel
== None:
424 raise Exception("Cannot create a channel")
425 Channel
.realize(self
)
426 if self
.future_volume_midi_cc
!= None:
427 self
.channel
.volume_midi_cc
= self
.future_volume_midi_cc
428 if self
.future_balance_midi_cc
!= None:
429 self
.channel
.balance_midi_cc
= self
.future_balance_midi_cc
430 if self
.future_mute_midi_cc
!= None:
431 self
.channel
.mute_midi_cc
= self
.future_mute_midi_cc
432 if self
.future_solo_midi_cc
!= None:
433 self
.channel
.solo_midi_cc
= self
.future_solo_midi_cc
434 if self
.app
._init
_solo
_channels
and self
.channel_name
in self
.app
._init
_solo
_channels
:
435 self
.channel
.solo
= True
437 self
.channel
.midi_scale
= self
.slider_scale
.scale
439 self
.on_volume_changed(self
.slider_adjustment
)
440 self
.on_balance_changed(self
.balance_adjustment
)
442 # vbox child at upper part
443 self
.vbox
= Gtk
.VBox()
444 self
.pack_start(self
.vbox
, False, True, 0)
445 self
.label_name
= Gtk
.Label()
446 self
.label_name
.set_text(self
.channel_name
)
447 self
.label_name
.set_width_chars(0)
448 self
.label_name_event_box
= Gtk
.EventBox()
449 self
.label_name_event_box
.connect("button-press-event", self
.on_label_mouse
)
450 self
.label_name_event_box
.add(self
.label_name
)
451 self
.vbox
.pack_start(self
.label_name_event_box
, True, True, 0)
452 # self.label_stereo = Gtk.Label()
454 # self.label_stereo.set_text("stereo")
456 # self.label_stereo.set_text("mono")
457 # self.label_stereo.set_size_request(0, -1)
458 # self.vbox.pack_start(self.label_stereo, True)
460 self
.hbox_mutesolo
= Gtk
.HBox()
461 vbox_mutesolo
= Gtk
.VBox()
462 vbox_mutesolo
.pack_start(self
.hbox_mutesolo
, True, True, button_padding
)
463 self
.vbox
.pack_start(vbox_mutesolo
, True, True, 0)
465 self
.mute
= Gtk
.ToggleButton()
466 self
.mute
.set_label("M")
467 self
.mute
.set_name("mute")
468 self
.mute
.set_active(self
.channel
.out_mute
)
469 self
.mute
.connect("toggled", self
.on_mute_toggled
)
470 self
.hbox_mutesolo
.pack_start(self
.mute
, True, True, button_padding
)
472 self
.solo
= Gtk
.ToggleButton()
473 self
.solo
.set_label("S")
474 self
.solo
.set_name("solo")
475 self
.solo
.set_active(self
.channel
.solo
)
476 self
.solo
.connect("toggled", self
.on_solo_toggled
)
477 self
.hbox_mutesolo
.pack_start(self
.solo
, True, True, button_padding
)
479 self
.vbox
.pack_start(self
.hbox_mutesolo
, True, True, 0)
482 frame
.set_shadow_type(Gtk
.ShadowType
.IN
)
483 frame
.add(self
.abspeak
);
484 self
.pack_start(frame
, False, True, 0)
486 # hbox child at lower part
487 self
.hbox
= Gtk
.HBox()
488 self
.hbox
.pack_start(self
.slider
, True, True, 0)
490 frame
.set_shadow_type(Gtk
.ShadowType
.IN
)
491 frame
.add(self
.meter
);
492 self
.hbox
.pack_start(frame
, True, True, 0)
494 frame
.set_shadow_type(Gtk
.ShadowType
.IN
)
495 frame
.add(self
.hbox
);
496 self
.pack_start(frame
, True, True, 0)
498 self
.volume_digits
.set_width_chars(6)
499 self
.pack_start(self
.volume_digits
, False, False, 0)
501 self
.create_balance_widget()
503 self
.monitor_button
= Gtk
.ToggleButton('MON')
504 self
.monitor_button
.connect('toggled', self
.on_monitor_button_toggled
)
505 self
.pack_start(self
.monitor_button
, False, False, 0)
507 def add_control_group(self
, channel
):
508 control_group
= ControlGroup(channel
, self
)
509 control_group
.show_all()
510 self
.vbox
.pack_start(control_group
, True, True, 0)
513 def remove_control_group(self
, channel
):
514 ctlgroup
= self
.get_control_group(channel
)
515 self
.vbox
.remove(ctlgroup
)
517 def update_control_group(self
, channel
):
518 for control_group
in self
.vbox
.get_children():
519 if isinstance(control_group
, ControlGroup
):
520 if control_group
.output_channel
is channel
:
521 control_group
.update()
523 def get_control_group(self
, channel
):
524 for control_group
in self
.vbox
.get_children():
525 if isinstance(control_group
, ControlGroup
):
526 if control_group
.output_channel
is channel
:
531 Channel
.unrealize(self
)
532 if self
.post_fader_output_channel
:
533 self
.post_fader_output_channel
.remove()
534 self
.post_fader_output_channel
= None
535 self
.channel
.remove()
538 channel_properties_dialog
= None
540 def on_channel_properties(self
):
541 if not self
.channel_properties_dialog
:
542 self
.channel_properties_dialog
= ChannelPropertiesDialog(self
, self
.app
)
543 self
.channel_properties_dialog
.show()
544 self
.channel_properties_dialog
.present()
546 def on_label_mouse(self
, widget
, event
):
547 if event
.type == Gdk
.EventType
._2BUTTON
_PRESS
:
548 if event
.button
== 1:
549 self
.on_channel_properties()
551 def on_mute_toggled(self
, button
):
552 self
.channel
.out_mute
= self
.mute
.get_active()
554 def on_solo_toggled(self
, button
):
555 self
.channel
.solo
= self
.solo
.get_active()
557 def midi_events_check(self
):
558 if hasattr(self
, 'channel') and self
.channel
.midi_in_got_events
:
559 self
.mute
.set_active(self
.channel
.out_mute
)
560 self
.solo
.set_active(self
.channel
.solo
)
561 Channel
.on_midi_event_received(self
)
563 def on_solo_button_pressed(self
, button
, event
, *args
):
564 if event
.button
== 3:
565 # right click on the solo button, act on all output channels
566 if button
.get_active(): # was soloed
567 button
.set_active(False)
568 if hasattr(button
, 'touched_channels'):
569 touched_channels
= button
.touched_channels
570 for chan
in touched_channels
:
571 ctlgroup
= self
.get_control_group(chan
)
572 ctlgroup
.solo
.set_active(False)
573 del button
.touched_channels
574 else: # was not soloed
575 button
.set_active(True)
576 touched_channels
= []
577 for chan
in self
.app
.output_channels
:
578 ctlgroup
= self
.get_control_group(chan
)
579 if not ctlgroup
.solo
.get_active():
580 ctlgroup
.solo
.set_active(True)
581 touched_channels
.append(chan
)
582 button
.touched_channels
= touched_channels
587 def serialization_name(cls
):
588 return 'input_channel'
590 def serialize(self
, object_backend
):
591 object_backend
.add_property("name", self
.channel_name
)
593 object_backend
.add_property("type", "stereo")
595 object_backend
.add_property("type", "mono")
596 Channel
.serialize(self
, object_backend
)
598 def unserialize_property(self
, name
, value
):
600 self
.channel_name
= str(value
)
603 if value
== "stereo":
609 return Channel
.unserialize_property(self
, name
, value
)
611 class OutputChannel(Channel
):
612 _display_solo_buttons
= False
614 _init_muted_channels
= None
615 _init_solo_channels
= None
617 def __init__(self
, app
, name
, stereo
):
618 Channel
.__init
__(self
, app
, name
, stereo
)
620 def get_display_solo_buttons(self
):
621 return self
._display
_solo
_buttons
623 def set_display_solo_buttons(self
, value
):
624 self
._display
_solo
_buttons
= value
625 # notifying control groups
626 for inputchannel
in self
.app
.channels
:
627 inputchannel
.update_control_group(self
)
629 display_solo_buttons
= property(get_display_solo_buttons
, set_display_solo_buttons
)
632 self
.channel
= self
.mixer
.add_output_channel(self
.channel_name
, self
.stereo
)
633 if self
.channel
== None:
634 raise Exception("Cannot create a channel")
635 Channel
.realize(self
)
636 if self
.future_volume_midi_cc
!= None:
637 self
.channel
.volume_midi_cc
= self
.future_volume_midi_cc
638 if self
.future_balance_midi_cc
!= None:
639 self
.channel
.balance_midi_cc
= self
.future_balance_midi_cc
640 if self
.future_mute_midi_cc
!= None:
641 self
.channel
.mute_midi_cc
= self
.future_mute_midi_cc
642 self
.channel
.midi_scale
= self
.slider_scale
.scale
644 self
.on_volume_changed(self
.slider_adjustment
)
645 self
.on_balance_changed(self
.balance_adjustment
)
647 # vbox child at upper part
648 self
.vbox
= Gtk
.VBox()
649 self
.pack_start(self
.vbox
, False, True, 0)
650 self
.label_name
= Gtk
.Label()
651 self
.label_name
.set_text(self
.channel_name
)
652 self
.label_name
.set_width_chars(0)
653 self
.label_name_event_box
= Gtk
.EventBox()
654 self
.label_name_event_box
.connect('button-press-event', self
.on_label_mouse
)
655 self
.label_name_event_box
.add(self
.label_name
)
656 if not hasattr(self
, 'color'):
657 self
.color
= random_color()
658 set_background_color(self
.label_name_event_box
, self
.css_name
,
659 self
.color
.to_string())
660 self
.vbox
.pack_start(self
.label_name_event_box
, True, True, 0)
661 self
.mute
= Gtk
.ToggleButton()
662 self
.mute
.set_label("M")
663 self
.mute
.set_name("mute")
664 self
.mute
.set_active(self
.channel
.out_mute
)
665 self
.mute
.connect("toggled", self
.on_mute_toggled
)
667 hbox
.pack_start(self
.mute
, True, True, button_padding
)
668 self
.vbox
.pack_start(hbox
, True, True, button_padding
)
671 frame
.set_shadow_type(Gtk
.ShadowType
.IN
)
672 frame
.add(self
.abspeak
);
673 self
.vbox
.pack_start(frame
, False, True, 0)
675 # hbox child at lower part
676 self
.hbox
= Gtk
.HBox()
677 self
.hbox
.pack_start(self
.slider
, True, True, 0)
679 frame
.set_shadow_type(Gtk
.ShadowType
.IN
)
680 frame
.add(self
.meter
);
681 self
.hbox
.pack_start(frame
, True, True, 0)
683 frame
.set_shadow_type(Gtk
.ShadowType
.IN
)
684 frame
.add(self
.hbox
);
685 self
.pack_start(frame
, True, True, 0)
687 self
.volume_digits
.set_width_chars(6)
688 self
.pack_start(self
.volume_digits
, False, True, 0)
690 self
.create_balance_widget()
692 self
.monitor_button
= Gtk
.ToggleButton('MON')
693 self
.monitor_button
.connect('toggled', self
.on_monitor_button_toggled
)
694 self
.pack_start(self
.monitor_button
, False, False, 0)
696 # add control groups to the input channels, and initialize them
698 for input_channel
in self
.app
.channels
:
699 ctlgroup
= input_channel
.add_control_group(self
)
700 if self
._init
_muted
_channels
and input_channel
.channel
.name
in self
._init
_muted
_channels
:
701 ctlgroup
.mute
.set_active(True)
702 if self
._init
_solo
_channels
and input_channel
.channel
.name
in self
._init
_solo
_channels
:
703 ctlgroup
.solo
.set_active(True)
704 self
._init
_muted
_channels
= None
705 self
._init
_solo
_channels
= None
707 channel_properties_dialog
= None
708 def on_channel_properties(self
):
709 if not self
.channel_properties_dialog
:
710 self
.channel_properties_dialog
= OutputChannelPropertiesDialog(self
, self
.app
)
711 self
.channel_properties_dialog
.show()
712 self
.channel_properties_dialog
.present()
714 def on_label_mouse(self
, widget
, event
):
715 if event
.type == Gdk
.EventType
._2BUTTON
_PRESS
:
716 if event
.button
== 1:
717 self
.on_channel_properties()
719 def on_mute_toggled(self
, button
):
720 self
.channel
.out_mute
= self
.mute
.get_active()
722 def midi_events_check(self
):
723 if self
.channel
!= None and self
.channel
.midi_in_got_events
:
724 self
.mute
.set_active(self
.channel
.out_mute
)
725 Channel
.on_midi_event_received(self
)
728 # remove control groups from input channels
729 for input_channel
in self
.app
.channels
:
730 input_channel
.remove_control_group(self
)
732 Channel
.unrealize(self
)
733 self
.channel
.remove()
737 def serialization_name(cls
):
738 return 'output_channel'
740 def serialize(self
, object_backend
):
741 object_backend
.add_property("name", self
.channel_name
)
743 object_backend
.add_property("type", "stereo")
745 object_backend
.add_property("type", "mono")
746 if self
.display_solo_buttons
:
747 object_backend
.add_property("solo_buttons", "true")
750 for input_channel
in self
.app
.channels
:
751 if self
.channel
.is_muted(input_channel
.channel
):
752 muted_channels
.append(input_channel
)
753 if self
.channel
.is_solo(input_channel
.channel
):
754 solo_channels
.append(input_channel
)
756 object_backend
.add_property('muted_channels', '|'.join([x
.channel
.name
for x
in muted_channels
]))
758 object_backend
.add_property('solo_channels', '|'.join([x
.channel
.name
for x
in solo_channels
]))
759 object_backend
.add_property("color", self
.color
.to_string())
760 Channel
.serialize(self
, object_backend
)
762 def unserialize_property(self
, name
, value
):
764 self
.channel_name
= str(value
)
767 if value
== "stereo":
773 if name
== "solo_buttons":
775 self
.display_solo_buttons
= True
777 if name
== 'muted_channels':
778 self
._init
_muted
_channels
= value
.split('|')
780 if name
== 'solo_channels':
781 self
._init
_solo
_channels
= value
.split('|')
788 return Channel
.unserialize_property(self
, name
, value
)
790 class ChannelPropertiesDialog(Gtk
.Dialog
):
793 def __init__(self
, parent
, app
):
794 self
.channel
= parent
796 self
.mixer
= self
.channel
.mixer
797 Gtk
.Dialog
.__init
__(self
, 'Channel "%s" Properties' % self
.channel
.channel_name
, app
.window
)
799 self
.add_button(Gtk
.STOCK_CANCEL
, Gtk
.ResponseType
.CANCEL
)
800 self
.ok_button
= self
.add_button(Gtk
.STOCK_APPLY
, Gtk
.ResponseType
.APPLY
)
801 self
.set_default_response(Gtk
.ResponseType
.APPLY
);
806 self
.connect('response', self
.on_response_cb
)
807 self
.connect('delete-event', self
.on_response_cb
)
809 def create_frame(self
, label
, child
):
812 frame
.set_border_width(3)
813 #frame.set_shadow_type(Gtk.ShadowType.NONE)
814 frame
.get_label_widget().set_markup('<b>%s</b>' % label
)
816 alignment
= Gtk
.Alignment
.new(0, 0, 1, 1)
817 alignment
.set_padding(0, 0, 12, 0)
827 self
.properties_table
= table
= Gtk
.Table(3, 3, False)
828 vbox
.pack_start(self
.create_frame('Properties', table
), True, True, 0)
829 table
.set_row_spacings(5)
830 table
.set_col_spacings(5)
832 table
.attach(Gtk
.Label(label
='Name'), 0, 1, 0, 1)
833 self
.entry_name
= Gtk
.Entry()
834 self
.entry_name
.set_activates_default(True)
835 self
.entry_name
.connect('changed', self
.on_entry_name_changed
)
836 table
.attach(self
.entry_name
, 1, 2, 0, 1)
838 table
.attach(Gtk
.Label(label
='Mode'), 0, 1, 1, 2)
839 self
.mode_hbox
= Gtk
.HBox()
840 table
.attach(self
.mode_hbox
, 1, 2, 1, 2)
841 self
.mono
= Gtk
.RadioButton(label
='Mono')
842 self
.stereo
= Gtk
.RadioButton(label
='Stereo', group
=self
.mono
)
843 self
.mode_hbox
.pack_start(self
.mono
, True, True, 0)
844 self
.mode_hbox
.pack_start(self
.stereo
, True, True, 0)
846 table
= Gtk
.Table(2, 3, False)
847 vbox
.pack_start(self
.create_frame('MIDI Control Channels', table
), True, True, 0)
848 table
.set_row_spacings(5)
849 table
.set_col_spacings(5)
851 cc_tooltip
= "{} MIDI Control Change number (0-127, set to -1 to assign next free CC #)"
852 table
.attach(Gtk
.Label(label
='Volume'), 0, 1, 0, 1)
853 self
.entry_volume_cc
= Gtk
.SpinButton
.new_with_range(-1, 127, 1)
854 self
.entry_volume_cc
.set_tooltip_text(cc_tooltip
.format("Volume"))
855 table
.attach(self
.entry_volume_cc
, 1, 2, 0, 1)
856 self
.button_sense_midi_volume
= Gtk
.Button('Learn')
857 self
.button_sense_midi_volume
.connect('clicked',
858 self
.on_sense_midi_volume_clicked
)
859 table
.attach(self
.button_sense_midi_volume
, 2, 3, 0, 1)
861 table
.attach(Gtk
.Label(label
='Balance'), 0, 1, 1, 2)
862 self
.entry_balance_cc
= Gtk
.SpinButton
.new_with_range(-1, 127, 1)
863 self
.entry_balance_cc
.set_tooltip_text(cc_tooltip
.format("Balance"))
864 table
.attach(self
.entry_balance_cc
, 1, 2, 1, 2)
865 self
.button_sense_midi_balance
= Gtk
.Button('Learn')
866 self
.button_sense_midi_balance
.connect('clicked',
867 self
.on_sense_midi_balance_clicked
)
868 table
.attach(self
.button_sense_midi_balance
, 2, 3, 1, 2)
870 table
.attach(Gtk
.Label(label
='Mute'), 0, 1, 2, 3)
871 self
.entry_mute_cc
= Gtk
.SpinButton
.new_with_range(-1, 127, 1)
872 self
.entry_mute_cc
.set_tooltip_text(cc_tooltip
.format("Mute"))
873 table
.attach(self
.entry_mute_cc
, 1, 2, 2, 3)
874 self
.button_sense_midi_mute
= Gtk
.Button('Learn')
875 self
.button_sense_midi_mute
.connect('clicked',
876 self
.on_sense_midi_mute_clicked
)
877 table
.attach(self
.button_sense_midi_mute
, 2, 3, 2, 3)
879 if (isinstance(self
, NewChannelDialog
) or (self
.channel
and
880 isinstance(self
.channel
, InputChannel
))):
881 table
.attach(Gtk
.Label(label
='Solo'), 0, 1, 3, 4)
882 self
.entry_solo_cc
= Gtk
.SpinButton
.new_with_range(-1, 127, 1)
883 self
.entry_solo_cc
.set_tooltip_text(cc_tooltip
.format("Solo"))
884 table
.attach(self
.entry_solo_cc
, 1, 2, 3, 4)
885 self
.button_sense_midi_solo
= Gtk
.Button('Learn')
886 self
.button_sense_midi_solo
.connect('clicked',
887 self
.on_sense_midi_solo_clicked
)
888 table
.attach(self
.button_sense_midi_solo
, 2, 3, 3, 4)
893 self
.entry_name
.set_text(self
.channel
.channel_name
)
894 if self
.channel
.channel
.is_stereo
:
895 self
.stereo
.set_active(True)
897 self
.mono
.set_active(True)
898 self
.mode_hbox
.set_sensitive(False)
899 self
.entry_volume_cc
.set_value(self
.channel
.channel
.volume_midi_cc
)
900 self
.entry_balance_cc
.set_value(self
.channel
.channel
.balance_midi_cc
)
901 self
.entry_mute_cc
.set_value(self
.channel
.channel
.mute_midi_cc
)
902 if (self
.channel
and isinstance(self
.channel
, InputChannel
)):
903 self
.entry_solo_cc
.set_value(self
.channel
.channel
.solo_midi_cc
)
905 def sense_popup_dialog(self
, entry
):
906 window
= Gtk
.Window
.new(Gtk
.WindowType
.TOPLEVEL
)
907 window
.set_destroy_with_parent(True)
908 window
.set_transient_for(self
)
909 window
.set_decorated(False)
910 window
.set_modal(True)
911 window
.set_position(Gtk
.WindowPosition
.CENTER_ON_PARENT
)
912 window
.set_border_width(10)
917 vbox
.pack_start(Gtk
.Label(label
='Please move the MIDI control you want to use for this function.'), True, True, 0)
918 timeout_label
= Gtk
.Label(label
='This window will close in 5 seconds')
919 vbox
.pack_start(timeout_label
, True, True, 0)
920 def close_sense_timeout(window
, entry
):
922 timeout_label
.set_text('This window will close in %d seconds.' % window
.timeout
)
923 if window
.timeout
== 0:
925 entry
.set_value(self
.mixer
.last_midi_channel
)
929 GObject
.timeout_add_seconds(1, close_sense_timeout
, window
, entry
)
931 def on_sense_midi_volume_clicked(self
, *args
):
932 self
.mixer
.last_midi_channel
= int(self
.entry_volume_cc
.get_value())
933 self
.sense_popup_dialog(self
.entry_volume_cc
)
935 def on_sense_midi_balance_clicked(self
, *args
):
936 self
.mixer
.last_midi_channel
= int(self
.entry_balance_cc
.get_value())
937 self
.sense_popup_dialog(self
.entry_balance_cc
)
939 def on_sense_midi_mute_clicked(self
, *args
):
940 self
.mixer
.last_midi_channel
= int(self
.entry_mute_cc
.get_value())
941 self
.sense_popup_dialog(self
.entry_mute_cc
)
943 def on_sense_midi_solo_clicked(self
, *args
):
944 self
.mixer
.last_midi_channel
= int(self
.entry_solo_cc
.get_value())
945 self
.sense_popup_dialog(self
.entry_solo_cc
)
947 def on_response_cb(self
, dlg
, response_id
, *args
):
948 self
.channel
.channel_properties_dialog
= None
949 name
= self
.entry_name
.get_text()
950 if response_id
== Gtk
.ResponseType
.APPLY
:
951 if name
!= self
.channel
.channel_name
:
952 self
.channel
.channel_name
= name
953 for control
in ('volume', 'balance', 'mute', 'solo'):
954 widget
= getattr(self
, 'entry_{}_cc'.format(control
), None)
955 if widget
is not None:
956 value
= int(widget
.get_value())
958 setattr(self
.channel
.channel
, '{}_midi_cc'.format(control
), value
)
961 def on_entry_name_changed(self
, entry
):
963 if len(entry
.get_text()):
964 if self
.channel
and self
.channel
.channel
.name
== entry
.get_text():
966 elif entry
.get_text() not in [x
.channel
.name
for x
in self
.app
.channels
] + \
967 [x
.channel
.name
for x
in self
.app
.output_channels
] + ['MAIN']:
969 self
.ok_button
.set_sensitive(sensitive
)
972 class NewChannelDialog(ChannelPropertiesDialog
):
973 def __init__(self
, app
):
974 Gtk
.Dialog
.__init
__(self
, 'New Channel', app
.window
)
975 self
.mixer
= app
.mixer
980 self
.stereo
.set_active(True) # default to stereo
982 self
.add_button(Gtk
.STOCK_CANCEL
, Gtk
.ResponseType
.CANCEL
)
983 self
.ok_button
= self
.add_button(Gtk
.STOCK_ADD
, Gtk
.ResponseType
.OK
)
984 self
.ok_button
.set_sensitive(False)
985 self
.set_default_response(Gtk
.ResponseType
.OK
);
988 self
.entry_volume_cc
.set_value(-1)
989 self
.entry_balance_cc
.set_value(-1)
990 self
.entry_mute_cc
.set_value(-1)
991 self
.entry_solo_cc
.set_value(-1)
993 def get_result(self
):
994 return {'name': self
.entry_name
.get_text(),
995 'stereo': self
.stereo
.get_active(),
996 'volume_cc': int(self
.entry_volume_cc
.get_value()),
997 'balance_cc': int(self
.entry_balance_cc
.get_value()),
998 'mute_cc': int(self
.entry_mute_cc
.get_value()),
999 'solo_cc': int(self
.entry_solo_cc
.get_value())
1002 class OutputChannelPropertiesDialog(ChannelPropertiesDialog
):
1003 def create_ui(self
):
1004 ChannelPropertiesDialog
.create_ui(self
)
1006 table
= self
.properties_table
1007 table
.attach(Gtk
.Label(label
='Color'), 0, 1, 2, 3)
1008 self
.color_chooser_button
= Gtk
.ColorButton()
1009 table
.attach(self
.color_chooser_button
, 1, 2, 2, 3)
1013 self
.vbox
.pack_start(self
.create_frame('Input Channels', vbox
), True, True, 0)
1015 self
.display_solo_buttons
= Gtk
.CheckButton('Display solo buttons')
1016 vbox
.pack_start(self
.display_solo_buttons
, True, True, 0)
1018 self
.vbox
.show_all()
1021 ChannelPropertiesDialog
.fill_ui(self
)
1022 self
.display_solo_buttons
.set_active(self
.channel
.display_solo_buttons
)
1023 self
.color_chooser_button
.set_rgba(self
.channel
.color
)
1025 def on_response_cb(self
, dlg
, response_id
, *args
):
1026 ChannelPropertiesDialog
.on_response_cb(self
, dlg
, response_id
, *args
)
1027 if response_id
== Gtk
.ResponseType
.APPLY
:
1028 self
.channel
.display_solo_buttons
= self
.display_solo_buttons
.get_active()
1029 self
.channel
.set_color(self
.color_chooser_button
.get_rgba())
1030 for inputchannel
in self
.app
.channels
:
1031 inputchannel
.update_control_group(self
.channel
)
1035 class NewOutputChannelDialog(OutputChannelPropertiesDialog
):
1036 def __init__(self
, app
):
1037 Gtk
.Dialog
.__init
__(self
, 'New Output Channel', app
.window
)
1038 self
.mixer
= app
.mixer
1043 # TODO: disable mode for output channels as mono output channels may
1044 # not be correctly handled yet.
1045 self
.mode_hbox
.set_sensitive(False)
1046 self
.stereo
.set_active(True) # default to stereo
1048 self
.add_button(Gtk
.STOCK_CANCEL
, Gtk
.ResponseType
.CANCEL
)
1049 self
.ok_button
= self
.add_button(Gtk
.STOCK_ADD
, Gtk
.ResponseType
.OK
)
1050 self
.ok_button
.set_sensitive(False)
1051 self
.set_default_response(Gtk
.ResponseType
.OK
);
1054 self
.entry_volume_cc
.set_value(-1)
1055 self
.entry_balance_cc
.set_value(-1)
1056 self
.entry_mute_cc
.set_value(-1)
1058 def get_result(self
):
1059 return {'name': self
.entry_name
.get_text(),
1060 'stereo': self
.stereo
.get_active(),
1061 'volume_cc': int(self
.entry_volume_cc
.get_value()),
1062 'balance_cc': int(self
.entry_balance_cc
.get_value()),
1063 'mute_cc': int(self
.entry_mute_cc
.get_value()),
1064 'display_solo_buttons': self
.display_solo_buttons
.get_active(),
1065 'color': self
.color_chooser_button
.get_rgba()
1068 class ControlGroup(Gtk
.Alignment
):
1069 def __init__(self
, output_channel
, input_channel
):
1070 GObject
.GObject
.__init
__(self
)
1071 self
.set(0.5, 0.5, 1, 1)
1072 self
.output_channel
= output_channel
1073 self
.input_channel
= input_channel
1074 self
.app
= input_channel
.app
1077 self
.vbox
= Gtk
.VBox()
1080 set_background_color(self
.vbox
, output_channel
.css_name
,
1081 output_channel
.color
.to_string())
1084 self
.vbox
.pack_start(hbox
, True, True, button_padding
)
1085 css
= b
""" .control_group {
1086 min-width: 0px; padding: 0px;} """
1088 css_provider
= Gtk
.CssProvider()
1089 css_provider
.load_from_data(css
)
1090 context
= Gtk
.StyleContext()
1091 screen
= Gdk
.Screen
.get_default()
1092 context
.add_provider_for_screen(screen
, css_provider
, Gtk
.STYLE_PROVIDER_PRIORITY_APPLICATION
)
1094 self
.label
= Gtk
.Label(output_channel
.channel
.name
)
1095 label_context
= self
.label
.get_style_context()
1096 label_context
.add_class('control_group')
1098 self
.hbox
.pack_start(self
.label
, False, False, button_padding
)
1099 mute
= Gtk
.ToggleButton()
1101 mute
.set_name("mute")
1102 mute
.connect("toggled", self
.on_mute_toggled
)
1104 solo
= Gtk
.ToggleButton()
1105 solo
.set_name("solo")
1107 solo
.connect("toggled", self
.on_solo_toggled
)
1110 if self
.output_channel
.display_solo_buttons
:
1111 hbox
.pack_end(solo
, False, False, button_padding
)
1112 hbox
.pack_end(mute
, False, False, button_padding
)
1115 if self
.output_channel
.display_solo_buttons
:
1116 if not self
.solo
in self
.hbox
.get_children():
1117 self
.hbox
.pack_end(self
.solo
, False, False, button_padding
)
1118 self
.hbox
.reorder_child(self
.mute
, -1)
1121 if self
.solo
in self
.hbox
.get_children():
1122 self
.hbox
.remove(self
.solo
)
1124 self
.label
.set_text(self
.output_channel
.channel
.name
)
1125 set_background_color(self
.vbox
, self
.output_channel
.css_name
, self
.output_channel
.color
.to_string())
1128 def on_mute_toggled(self
, button
):
1129 self
.output_channel
.channel
.set_muted(self
.input_channel
.channel
, button
.get_active())
1130 self
.app
.update_monitor(self
)
1132 def on_solo_toggled(self
, button
):
1133 self
.output_channel
.channel
.set_solo(self
.input_channel
.channel
, button
.get_active())
1134 self
.app
.update_monitor(self
)