Move channel buffers from stack to structs so theyt can be reused when mixing with...
[jack_mixer.git] / jack_mixer.py
blobac721624375d3815ed00e6157a94e9de6f28c89f
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 def cleanup(self):
137 print "Cleaning jack_mixer"
138 if not self.mixer:
139 return
141 for channel in self.channels:
142 channel.unrealize()
144 jack_mixer_c.destroy(self.mixer)
146 def on_choose_meter_scale(self, widget):
147 self.gui_factory.run_dialog_choose_meter_scale()
149 def on_choose_slider_scale(self, widget):
150 self.gui_factory.run_dialog_choose_slider_scale()
152 def on_add_channel(self, widget):
153 result = self.gui_factory.run_dialog_add_channel()
154 if result:
155 self.add_channel(result['name'], result['stereo'])
156 self.window.show_all()
158 def on_remove_channel(self, widget, channel, channel_remove_menu_item):
159 print 'Removing channel "%s"' % channel.channel_name
160 self.channel_remove_menu.remove(channel_remove_menu_item)
161 for i in range(len(self.channels)):
162 if self.channels[i] is channel:
163 channel.unrealize()
164 del self.channels[i]
165 self.hbox_inputs.remove(channel.parent)
166 break
167 if len(self.channels) == 0:
168 self.channel_remove_menu_item.set_sensitive(False)
170 def on_channels_clear(self, widget):
171 for channel in self.channels:
172 channel.unrealize()
173 self.hbox_inputs.remove(channel.parent)
174 self.channels = []
175 self.channel_remove_menu = gtk.Menu()
176 self.channel_remove_menu_item.set_submenu(self.channel_remove_menu)
177 self.channel_remove_menu_item.set_sensitive(False)
179 def add_channel(self, name, stereo):
180 channel = input_channel(self.mixer, self.gui_factory, name, stereo)
181 self.add_channel_precreated(channel)
183 def add_channel_precreated(self, channel):
184 self.channels.append(channel)
185 frame = gtk.Frame()
186 frame.add(channel)
187 self.hbox_inputs.pack_start(frame, False)
188 channel.realize()
189 channel_remove_menu_item = gtk.MenuItem(channel.channel_name)
190 self.channel_remove_menu.append(channel_remove_menu_item)
191 channel_remove_menu_item.connect("activate", self.on_remove_channel, channel, channel_remove_menu_item)
192 self.channel_remove_menu_item.set_sensitive(True)
194 def read_meters(self):
195 for channel in self.channels:
196 channel.read_meter()
197 self.main_mix.read_meter()
198 return True
200 def lash_check_events(self):
201 while lash.lash_get_pending_event_count(self.lash_client):
202 event = lash.lash_get_event(self.lash_client)
204 #print repr(event)
206 event_type = lash.lash_event_get_type(event)
207 if event_type == lash.LASH_Quit:
208 print "jack_mixer: LASH ordered quit."
209 gtk.main_quit()
210 return False
211 elif event_type == lash.LASH_Save_File:
212 directory = lash.lash_event_get_string(event)
213 print "jack_mixer: LASH ordered to save data in directory %s" % directory
214 filename = directory + os.sep + "jack_mixer.xml"
215 f = file(filename, "w")
216 self.save_to_xml(f)
217 f.close()
218 lash.lash_send_event(self.lash_client, event) # we crash with double free
219 elif event_type == lash.LASH_Restore_File:
220 directory = lash.lash_event_get_string(event)
221 print "jack_mixer: LASH ordered to restore data from directory %s" % directory
222 filename = directory + os.sep + "jack_mixer.xml"
223 f = file(filename, "r")
224 self.load_from_xml(f)
225 f.close()
226 else:
227 print "jack_mixer: Got unhandled LASH event, type " + str(event_type)
228 return True
230 #lash.lash_event_destroy(event)
232 return True
234 def save_to_xml(self, file):
235 #print "Saving to XML..."
236 b = xml_serialization()
237 s = serializator()
238 s.serialize(self, b)
239 b.save(file)
241 def load_from_xml(self, file):
242 #print "Loading from XML..."
243 self.on_channels_clear(None)
244 self.unserialized_channels = []
245 b = xml_serialization()
246 b.load(file)
247 s = serializator()
248 s.unserialize(self, b)
249 for channel in self.unserialized_channels:
250 self.add_channel_precreated(channel)
251 del self.unserialized_channels
252 self.window.show_all()
254 def serialize(self, object_backend):
255 pass
257 def unserialize_property(self, name, value):
258 pass
260 def unserialize_child(self, name):
261 if name == main_mix_serialization_name():
262 return self.main_mix
264 if name == input_channel_serialization_name():
265 channel = input_channel(self.gui_factory, "", True)
266 self.unserialized_channels.append(channel)
267 return channel
269 def serialization_get_childs(self):
270 '''Get child objects tha required and support serialization'''
271 childs = self.channels[:]
272 childs.append(self.main_mix)
273 return childs
275 def serialization_name(self):
276 return "jack_mixer"
278 def main(self):
279 if not self.mixer:
280 return
282 self.window.show_all()
284 gtk.main()
286 #f = file("/dev/stdout", "w")
287 #self.save_to_xml(f)
288 #f.close
290 def help():
291 print "Usage: %s [mixer_name]" % sys.argv[0]
293 def main():
294 if lash: # If LASH python bindings are available
295 # sys.argv is modified by this call
296 lash_client = lash.init(sys.argv, "jack_mixer", lash.LASH_Config_File)
297 else:
298 lash_client = None
300 # check arguments
301 args = sys.argv[1:]
302 i = len(args)
303 for arg in reversed(args):
304 i -= 1
305 if len(arg) != 0 and arg[0] == '-':
306 if arg == "--help":
307 help()
308 return
309 else:
310 print 'Unknown option "%s"' % args[i]
311 help()
312 return
313 del args[i]
314 # else:
315 # print 'Non option argument "%s"' % args[i]
317 # Yeah , this sounds stupid, we connected earlier, but we dont want to show this if we got --help option
318 # This issue should be fixed in pylash, there is a reason for having two functions for initialization after all
319 if lash_client:
320 print "Successfully connected to LASH server at " + lash.lash_get_server_name(lash_client)
322 if len(args) == 1:
323 name = args[0]
324 else:
325 name = None
327 if not name:
328 name = "jack_mixer-%u" % os.getpid()
330 mixer = jack_mixer(name, lash_client)
332 mixer.main()
334 mixer.cleanup()
336 glade_dir = os.path.dirname(sys.argv[0])
338 # since ppl tend to run "python jack_mixer.py", lets assume that it is in current directory
339 # "python ./jack_mixer.py" and "./jack_mixer.py" will work anyway.
340 if not glade_dir:
341 glade_dir = "."
343 glade_file = glade_dir + os.sep + "jack_mixer.glade"
345 if not os.path.isfile(glade_file):
346 glade_file = glade_dir + os.sep + ".." + os.sep + "share"+ os.sep + "jack_mixer" + os.sep + "jack_mixer.glade"
348 #print 'Loading glade from "%s"' % glade_file
350 glade_xml = gtk.glade.XML(glade_file)
352 # scales suitable as meter scales
353 meter_scales = [scale.iec_268(), scale.linear_70dB(), scale.iec_268_minimalistic()]
355 # scales suitable as volume slider scales
356 slider_scales = [scale.linear_30dB(), scale.linear_70dB()]
358 if __name__ == "__main__":
359 main()