* Detect NaNs
[jack_mixer.git] / jack_mixer.py
blobef65a4415fbc3b47c74bac58cc76d10c1ead44a5
1 #!/usr/bin/env python
3 # This file is part of jack_mixer
5 # Copyright (C) 2006 Nedko Arnaudov <nedko@arnaudov.name>
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; version 2 of the License
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 import gtk
21 import gobject
22 import jack_mixer_c
23 import sys
24 from channel import *
25 import gtk.glade
26 import os
27 import gui
29 try:
30 import lash
31 except:
32 print "Cannot load LASH python bindings, you want LASH unless you enjoy manual jack plumbing each time you use this app"
33 lash = None
35 # no need for serialization if there is no LASH available
36 if lash:
37 from serialization_xml import xml_serialization
38 from serialization import serialized_object, serializator
40 class jack_mixer(serialized_object):
41 def __init__(self, name, lash_client):
42 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
43 self.window.set_title(name)
45 self.gui_factory = gui.factory(glade_xml, self.window, meter_scales, slider_scales)
47 self.vbox_top = gtk.VBox()
48 self.window.add(self.vbox_top)
50 self.menubar = gtk.MenuBar()
51 self.vbox_top.pack_start(self.menubar, False)
53 self.channels_menu_item = gtk.MenuItem("_Channel")
54 self.menubar.append(self.channels_menu_item)
56 self.settings_menu_item = gtk.MenuItem("_Settings")
57 self.menubar.append(self.settings_menu_item)
59 self.window.set_size_request(120,300)
61 self.channels_menu = gtk.Menu()
62 self.channels_menu_item.set_submenu(self.channels_menu)
64 self.channel_add_menu_item = gtk.ImageMenuItem(gtk.STOCK_ADD)
65 self.channels_menu.append(self.channel_add_menu_item)
66 self.channel_add_menu_item.connect("activate", self.on_add_channel)
68 self.channel_remove_menu_item = gtk.ImageMenuItem(gtk.STOCK_REMOVE)
69 self.channels_menu.append(self.channel_remove_menu_item)
71 self.channel_remove_menu = gtk.Menu()
72 self.channel_remove_menu_item.set_submenu(self.channel_remove_menu)
74 self.channel_remove_all_menu_item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
75 self.channels_menu.append(self.channel_remove_all_menu_item)
76 self.channel_remove_all_menu_item.connect("activate", self.on_channels_clear)
78 self.settings_menu = gtk.Menu()
79 self.settings_menu_item.set_submenu(self.settings_menu)
81 self.settings_choose_meter_scale_menu_item = gtk.MenuItem("_Meter scale")
82 self.settings_choose_meter_scale_menu_item.connect("activate", self.on_choose_meter_scale)
83 self.settings_menu.append(self.settings_choose_meter_scale_menu_item)
85 self.settings_choose_slider_scale_menu_item = gtk.MenuItem("_Slider scale")
86 self.settings_choose_slider_scale_menu_item.connect("activate", self.on_choose_slider_scale)
87 self.settings_menu.append(self.settings_choose_slider_scale_menu_item)
89 self.hbox_top = gtk.HBox()
90 self.vbox_top.pack_start(self.hbox_top, True)
92 self.scrolled_window = gtk.ScrolledWindow()
93 self.hbox_top.pack_start(self.scrolled_window, True)
95 self.hbox_inputs = gtk.HBox()
96 self.hbox_inputs.set_spacing(0)
97 self.hbox_inputs.set_border_width(0)
98 self.hbox_top.set_spacing(0)
99 self.hbox_top.set_border_width(0)
100 self.channels = []
102 self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
103 self.scrolled_window.add_with_viewport(self.hbox_inputs)
105 self.main_mix = main_mix(self.gui_factory)
106 self.main_mix.realize()
107 frame = gtk.Frame()
108 frame.add(self.main_mix)
109 self.hbox_top.pack_start(frame, False)
111 self.window.connect("destroy", gtk.main_quit)
113 gobject.timeout_add(80, self.read_meters)
114 self.lash_client = lash_client
116 if lash_client:
117 gobject.timeout_add(1000, self.lash_check_events)
119 def cleanup(self):
120 print "Cleaning jack_mixer"
121 for channel in self.channels:
122 channel.unrealize()
124 def on_choose_meter_scale(self, widget):
125 self.gui_factory.run_dialog_choose_meter_scale()
127 def on_choose_slider_scale(self, widget):
128 self.gui_factory.run_dialog_choose_slider_scale()
130 def on_add_channel(self, widget):
131 result = self.gui_factory.run_dialog_add_channel()
132 if result:
133 self.add_channel(result['name'], result['stereo'])
134 self.window.show_all()
136 def on_remove_channel(self, widget, channel, channel_remove_menu_item):
137 print 'Removing channel "%s"' % channel.channel_name
138 self.channel_remove_menu.remove(channel_remove_menu_item)
139 for i in range(len(self.channels)):
140 if self.channels[i] is channel:
141 channel.unrealize()
142 del self.channels[i]
143 self.hbox_inputs.remove(channel.parent)
144 break
145 if len(self.channels) == 0:
146 self.channel_remove_menu_item.set_sensitive(False)
148 def on_channels_clear(self, widget):
149 for channel in self.channels:
150 channel.unrealize()
151 self.hbox_inputs.remove(channel.parent)
152 self.channels = []
153 self.channel_remove_menu = gtk.Menu()
154 self.channel_remove_menu_item.set_submenu(self.channel_remove_menu)
155 self.channel_remove_menu_item.set_sensitive(False)
157 def add_channel(self, name, stereo):
158 channel = input_channel(self.gui_factory, name, stereo)
159 self.add_channel_precreated(channel)
161 def add_channel_precreated(self, channel):
162 self.channels.append(channel)
163 frame = gtk.Frame()
164 frame.add(channel)
165 self.hbox_inputs.pack_start(frame, False)
166 channel.realize()
167 channel_remove_menu_item = gtk.MenuItem(channel.channel_name)
168 self.channel_remove_menu.append(channel_remove_menu_item)
169 channel_remove_menu_item.connect("activate", self.on_remove_channel, channel, channel_remove_menu_item)
170 self.channel_remove_menu_item.set_sensitive(True)
172 def read_meters(self):
173 for channel in self.channels:
174 channel.read_meter()
175 self.main_mix.read_meter()
176 return True
178 def lash_check_events(self):
179 while lash.lash_get_pending_event_count(self.lash_client):
180 event = lash.lash_get_event(self.lash_client)
182 #print repr(event)
184 event_type = lash.lash_event_get_type(event)
185 if event_type == lash.LASH_Quit:
186 print "jack_mixer: LASH ordered quit."
187 gtk.main_quit()
188 return False
189 elif event_type == lash.LASH_Save_File:
190 directory = lash.lash_event_get_string(event)
191 print "jack_mixer: LASH ordered to save data in directory %s" % directory
192 filename = directory + os.sep + "jack_mixer.xml"
193 f = file(filename, "w")
194 self.save_to_xml(f)
195 f.close()
196 lash.lash_send_event(self.lash_client, event) # we crash with double free
197 elif event_type == lash.LASH_Restore_File:
198 directory = lash.lash_event_get_string(event)
199 print "jack_mixer: LASH ordered to restore data from directory %s" % directory
200 filename = directory + os.sep + "jack_mixer.xml"
201 f = file(filename, "r")
202 self.load_from_xml(f)
203 f.close()
204 else:
205 print "jack_mixer: Got unhandled LASH event, type " + str(event_type)
206 return True
208 #lash.lash_event_destroy(event)
210 return True
212 def save_to_xml(self, file):
213 #print "Saving to XML..."
214 b = xml_serialization()
215 s = serializator()
216 s.serialize(self, b)
217 b.save(file)
219 def load_from_xml(self, file):
220 #print "Loading from XML..."
221 self.on_channels_clear(None)
222 self.unserialized_channels = []
223 b = xml_serialization()
224 b.load(file)
225 s = serializator()
226 s.unserialize(self, b)
227 for channel in self.unserialized_channels:
228 self.add_channel_precreated(channel)
229 del self.unserialized_channels
230 self.window.show_all()
232 def serialize(self, object_backend):
233 pass
235 def unserialize_property(self, name, value):
236 pass
238 def unserialize_child(self, name):
239 if name == main_mix_serialization_name():
240 return self.main_mix
242 if name == input_channel_serialization_name():
243 channel = input_channel(self.gui_factory, "", True)
244 self.unserialized_channels.append(channel)
245 return channel
247 def serialization_get_childs(self):
248 '''Get child objects tha required and support serialization'''
249 childs = self.channels[:]
250 childs.append(self.main_mix)
251 return childs
253 def serialization_name(self):
254 return "jack_mixer"
256 def main(self):
257 self.window.show_all()
259 gtk.main()
261 #f = file("/dev/stdout", "w")
262 #self.save_to_xml(f)
263 #f.close
265 def help():
266 print "Usage: %s [mixer_name]" % sys.argv[0]
268 def main():
269 if lash: # If LASH python bindings are available
270 # sys.argv is modified by this call
271 lash_client = lash.init(sys.argv, "jack_mixer", lash.LASH_Config_File)
272 else:
273 lash_client = None
275 # check arguments
276 args = sys.argv[1:]
277 i = len(args)
278 for arg in reversed(args):
279 i -= 1
280 if len(arg) != 0 and arg[0] == '-':
281 if arg == "--help":
282 help()
283 return
284 else:
285 print 'Unknown option "%s"' % args[i]
286 help()
287 return
288 del args[i]
289 # else:
290 # print 'Non option argument "%s"' % args[i]
292 # Yeah , this sounds stupid, we connected earlier, but we dont want to show this if we got --help option
293 # This issue should be fixed in pylash, there is a reason for having two functions for initialization after all
294 if lash_client:
295 print "Successfully connected to LASH server at " + lash.lash_get_server_name(lash_client)
297 if len(args) == 1:
298 name = args[0]
299 else:
300 name = None
302 if not name:
303 name = "jack_mixer-%u" % os.getpid()
305 if not jack_mixer_c.init(name):
306 return
308 if lash_client:
309 # Send our client name to server
310 lash_event = lash.lash_event_new_with_type(lash.LASH_Client_Name)
311 lash.lash_event_set_string(lash_event, name)
312 lash.lash_send_event(lash_client, lash_event)
314 lash.lash_jack_client_name(lash_client, name)
316 mixer = jack_mixer(name, lash_client)
318 mixer.main()
320 mixer.cleanup()
322 jack_mixer_c.uninit()
324 glade_dir = os.path.dirname(sys.argv[0])
326 # since ppl tend to run "python jack_mixer.py", lets assume that it is in current directory
327 # "python ./jack_mixer.py" and "./jack_mixer.py" will work anyway.
328 if not glade_dir:
329 glade_dir = "."
331 glade_file = glade_dir + os.sep + "jack_mixer.glade"
333 if not os.path.isfile(glade_file):
334 glade_file = glade_dir + os.sep + ".." + os.sep + "share"+ os.sep + "jack_mixer" + os.sep + "jack_mixer.glade"
336 #print 'Loading glade from "%s"' % glade_file
338 glade_xml = gtk.glade.XML(glade_file)
340 # scales suitable as meter scales
341 meter_scales = [scale.iec_268(), scale.linear_70dB(), scale.iec_268_minimalistic()]
343 # scales suitable as volume slider scales
344 slider_scales = [scale.linear_30dB(), scale.linear_70dB()]
346 if __name__ == "__main__":
347 main()