Make JACK MIDI optional and autodetected
[jack_mixer.git] / jack_mixer.py
blob1bb3f2d7f3b9d4cd2b451109fd5a36e38e682c4e
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 import gtk.glade
25 import os
27 try:
28 import lash
29 except:
30 print "Cannot load LASH python bindings, you want LASH unless you enjoy manual jack plumbing each time you use this app"
31 lash = None
33 old_path = sys.path
34 sys.path.insert(0, os.path.dirname(sys.argv[0]) + os.sep + ".." + os.sep + "share"+ os.sep + "jack_mixer")
35 from channel import *
36 import gui
37 sys.path = old_path
40 # no need for serialization if there is no LASH available
41 if lash:
42 from serialization_xml import xml_serialization
43 from serialization import serialized_object, serializator
45 class jack_mixer(serialized_object):
46 def __init__(self, name, lash_client):
47 self.mixer = jack_mixer_c.create(name)
48 if not self.mixer:
49 return
51 if lash_client:
52 # Send our client name to server
53 lash_event = lash.lash_event_new_with_type(lash.LASH_Client_Name)
54 lash.lash_event_set_string(lash_event, name)
55 lash.lash_send_event(lash_client, lash_event)
57 lash.lash_jack_client_name(lash_client, name)
59 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
60 self.window.set_title(name)
62 self.gui_factory = gui.factory(glade_xml, self.window, meter_scales, slider_scales)
64 self.vbox_top = gtk.VBox()
65 self.window.add(self.vbox_top)
67 self.menubar = gtk.MenuBar()
68 self.vbox_top.pack_start(self.menubar, False)
70 self.channels_menu_item = gtk.MenuItem("_Channel")
71 self.menubar.append(self.channels_menu_item)
73 self.settings_menu_item = gtk.MenuItem("_Settings")
74 self.menubar.append(self.settings_menu_item)
76 self.window.set_size_request(120,300)
78 self.channels_menu = gtk.Menu()
79 self.channels_menu_item.set_submenu(self.channels_menu)
81 self.channel_add_menu_item = gtk.ImageMenuItem(gtk.STOCK_ADD)
82 self.channels_menu.append(self.channel_add_menu_item)
83 self.channel_add_menu_item.connect("activate", self.on_add_channel)
85 self.channel_remove_menu_item = gtk.ImageMenuItem(gtk.STOCK_REMOVE)
86 self.channels_menu.append(self.channel_remove_menu_item)
88 self.channel_remove_menu = gtk.Menu()
89 self.channel_remove_menu_item.set_submenu(self.channel_remove_menu)
91 self.channel_remove_all_menu_item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
92 self.channels_menu.append(self.channel_remove_all_menu_item)
93 self.channel_remove_all_menu_item.connect("activate", self.on_channels_clear)
95 self.settings_menu = gtk.Menu()
96 self.settings_menu_item.set_submenu(self.settings_menu)
98 self.settings_choose_meter_scale_menu_item = gtk.MenuItem("_Meter scale")
99 self.settings_choose_meter_scale_menu_item.connect("activate", self.on_choose_meter_scale)
100 self.settings_menu.append(self.settings_choose_meter_scale_menu_item)
102 self.settings_choose_slider_scale_menu_item = gtk.MenuItem("_Slider scale")
103 self.settings_choose_slider_scale_menu_item.connect("activate", self.on_choose_slider_scale)
104 self.settings_menu.append(self.settings_choose_slider_scale_menu_item)
106 self.hbox_top = gtk.HBox()
107 self.vbox_top.pack_start(self.hbox_top, True)
109 self.scrolled_window = gtk.ScrolledWindow()
110 self.hbox_top.pack_start(self.scrolled_window, True)
112 self.hbox_inputs = gtk.HBox()
113 self.hbox_inputs.set_spacing(0)
114 self.hbox_inputs.set_border_width(0)
115 self.hbox_top.set_spacing(0)
116 self.hbox_top.set_border_width(0)
117 self.channels = []
119 self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
120 self.scrolled_window.add_with_viewport(self.hbox_inputs)
122 self.main_mix = main_mix(self.mixer, self.gui_factory)
123 self.main_mix.realize()
124 frame = gtk.Frame()
125 frame.add(self.main_mix)
126 self.hbox_top.pack_start(frame, False)
128 self.window.connect("destroy", gtk.main_quit)
130 gobject.timeout_add(80, self.read_meters)
131 self.lash_client = lash_client
133 if lash_client:
134 gobject.timeout_add(1000, self.lash_check_events)
136 gobject.timeout_add(100, self.midi_change_check)
138 def cleanup(self):
139 print "Cleaning jack_mixer"
140 if not self.mixer:
141 return
143 for channel in self.channels:
144 channel.unrealize()
146 jack_mixer_c.destroy(self.mixer)
148 def on_choose_meter_scale(self, widget):
149 self.gui_factory.run_dialog_choose_meter_scale()
151 def on_choose_slider_scale(self, widget):
152 self.gui_factory.run_dialog_choose_slider_scale()
154 def on_add_channel(self, widget):
155 result = self.gui_factory.run_dialog_add_channel()
156 if result:
157 self.add_channel(result['name'], result['stereo'])
158 self.window.show_all()
160 def on_remove_channel(self, widget, channel, channel_remove_menu_item):
161 print 'Removing channel "%s"' % channel.channel_name
162 self.channel_remove_menu.remove(channel_remove_menu_item)
163 for i in range(len(self.channels)):
164 if self.channels[i] is channel:
165 channel.unrealize()
166 del self.channels[i]
167 self.hbox_inputs.remove(channel.parent)
168 break
169 if len(self.channels) == 0:
170 self.channel_remove_menu_item.set_sensitive(False)
172 def on_channels_clear(self, widget):
173 for channel in self.channels:
174 channel.unrealize()
175 self.hbox_inputs.remove(channel.parent)
176 self.channels = []
177 self.channel_remove_menu = gtk.Menu()
178 self.channel_remove_menu_item.set_submenu(self.channel_remove_menu)
179 self.channel_remove_menu_item.set_sensitive(False)
181 def add_channel(self, name, stereo):
182 channel = input_channel(self.mixer, self.gui_factory, name, stereo)
183 self.add_channel_precreated(channel)
185 def add_channel_precreated(self, channel):
186 self.channels.append(channel)
187 frame = gtk.Frame()
188 frame.add(channel)
189 self.hbox_inputs.pack_start(frame, False)
190 channel.realize()
191 channel_remove_menu_item = gtk.MenuItem(channel.channel_name)
192 self.channel_remove_menu.append(channel_remove_menu_item)
193 channel_remove_menu_item.connect("activate", self.on_remove_channel, channel, channel_remove_menu_item)
194 self.channel_remove_menu_item.set_sensitive(True)
196 def read_meters(self):
197 for channel in self.channels:
198 channel.read_meter()
199 self.main_mix.read_meter()
200 return True
202 def midi_change_check(self):
203 for channel in self.channels:
204 channel.midi_change_check()
205 self.main_mix.midi_change_check()
206 return True
208 def lash_check_events(self):
209 while lash.lash_get_pending_event_count(self.lash_client):
210 event = lash.lash_get_event(self.lash_client)
212 #print repr(event)
214 event_type = lash.lash_event_get_type(event)
215 if event_type == lash.LASH_Quit:
216 print "jack_mixer: LASH ordered quit."
217 gtk.main_quit()
218 return False
219 elif event_type == lash.LASH_Save_File:
220 directory = lash.lash_event_get_string(event)
221 print "jack_mixer: LASH ordered to save data in directory %s" % directory
222 filename = directory + os.sep + "jack_mixer.xml"
223 f = file(filename, "w")
224 self.save_to_xml(f)
225 f.close()
226 lash.lash_send_event(self.lash_client, event) # we crash with double free
227 elif event_type == lash.LASH_Restore_File:
228 directory = lash.lash_event_get_string(event)
229 print "jack_mixer: LASH ordered to restore data from directory %s" % directory
230 filename = directory + os.sep + "jack_mixer.xml"
231 f = file(filename, "r")
232 self.load_from_xml(f)
233 f.close()
234 else:
235 print "jack_mixer: Got unhandled LASH event, type " + str(event_type)
236 return True
238 #lash.lash_event_destroy(event)
240 return True
242 def save_to_xml(self, file):
243 #print "Saving to XML..."
244 b = xml_serialization()
245 s = serializator()
246 s.serialize(self, b)
247 b.save(file)
249 def load_from_xml(self, file):
250 #print "Loading from XML..."
251 self.on_channels_clear(None)
252 self.unserialized_channels = []
253 b = xml_serialization()
254 b.load(file)
255 s = serializator()
256 s.unserialize(self, b)
257 for channel in self.unserialized_channels:
258 self.add_channel_precreated(channel)
259 del self.unserialized_channels
260 self.window.show_all()
262 def serialize(self, object_backend):
263 pass
265 def unserialize_property(self, name, value):
266 pass
268 def unserialize_child(self, name):
269 if name == main_mix_serialization_name():
270 return self.main_mix
272 if name == input_channel_serialization_name():
273 channel = input_channel(self.gui_factory, "", True)
274 self.unserialized_channels.append(channel)
275 return channel
277 def serialization_get_childs(self):
278 '''Get child objects tha required and support serialization'''
279 childs = self.channels[:]
280 childs.append(self.main_mix)
281 return childs
283 def serialization_name(self):
284 return "jack_mixer"
286 def main(self):
287 if not self.mixer:
288 return
290 self.window.show_all()
292 gtk.main()
294 #f = file("/dev/stdout", "w")
295 #self.save_to_xml(f)
296 #f.close
298 def help():
299 print "Usage: %s [mixer_name]" % sys.argv[0]
301 def main():
302 if lash: # If LASH python bindings are available
303 # sys.argv is modified by this call
304 lash_client = lash.init(sys.argv, "jack_mixer", lash.LASH_Config_File)
305 else:
306 lash_client = None
308 # check arguments
309 args = sys.argv[1:]
310 i = len(args)
311 for arg in reversed(args):
312 i -= 1
313 if len(arg) != 0 and arg[0] == '-':
314 if arg == "--help":
315 help()
316 return
317 else:
318 print 'Unknown option "%s"' % args[i]
319 help()
320 return
321 del args[i]
322 # else:
323 # print 'Non option argument "%s"' % args[i]
325 # Yeah , this sounds stupid, we connected earlier, but we dont want to show this if we got --help option
326 # This issue should be fixed in pylash, there is a reason for having two functions for initialization after all
327 if lash_client:
328 print "Successfully connected to LASH server at " + lash.lash_get_server_name(lash_client)
330 if len(args) == 1:
331 name = args[0]
332 else:
333 name = None
335 if not name:
336 name = "jack_mixer-%u" % os.getpid()
338 mixer = jack_mixer(name, lash_client)
340 mixer.main()
342 mixer.cleanup()
344 glade_dir = os.path.dirname(sys.argv[0])
346 # since ppl tend to run "python jack_mixer.py", lets assume that it is in current directory
347 # "python ./jack_mixer.py" and "./jack_mixer.py" will work anyway.
348 if not glade_dir:
349 glade_dir = "."
351 glade_file = glade_dir + os.sep + "jack_mixer.glade"
353 if not os.path.isfile(glade_file):
354 glade_file = glade_dir + os.sep + ".." + os.sep + "share"+ os.sep + "jack_mixer" + os.sep + "jack_mixer.glade"
356 #print 'Loading glade from "%s"' % glade_file
358 glade_xml = gtk.glade.XML(glade_file)
360 # scales suitable as meter scales
361 meter_scales = [scale.iec_268(), scale.linear_70dB(), scale.iec_268_minimalistic()]
363 # scales suitable as volume slider scales
364 slider_scales = [scale.linear_30dB(), scale.linear_70dB()]
366 if __name__ == "__main__":
367 main()