At the release of 1.0.1.
[python/dscho.git] / Demo / stdwin / jukebox.py
blob377078cfea0f27fd55444bb1d58df62f7d976b46
1 #! /usr/local/bin/python
3 # XXX This only works on SGIs running IRIX 4.0 or higher
5 # JUKEBOX: browse directories full of sampled sound files.
7 # One or more "list windows" display the files and subdirectories of
8 # the arguments. Double-clicking on a subdirectory opens a new window
9 # displaying its contents (and so on recursively). Double clicking
10 # on a file plays it as a sound file (assuming it is one).
12 # Playing is asynchronous: the application keeps listening for events
13 # while the sample is playing, so you can cancel playing or start a
14 # new sample right away. Synchronous playing is available through the
15 # -s option.
17 # The control window displays a "stop button" that cancel the current
18 # play request.
20 # Most sound file formats recognized by SOX or SFPLAY are recognized.
21 # Since conversion is costly, converted files are cached in
22 # /usr/tmp/@j* until the user quits or changes the sampling rate via
23 # the Rate menu.
25 import commands
26 import getopt
27 import os
28 from stat import *
29 import rand
30 import stdwin
31 from stdwinevents import *
32 import sys
33 import tempfile
34 import sndhdr
36 from WindowParent import WindowParent
37 from Buttons import PushButton
39 # Pathnames
41 DEF_DB = '/usr/local/sounds' # Default directory of sounds
42 SOX = '/usr/local/bin/sox' # Sound format conversion program
43 SFPLAY = '/usr/sbin/sfplay' # Sound playing program
46 # Global variables
48 class struct: pass # Class to define featureless structures
50 G = struct() # oHlds writable global variables
53 # Main program
55 def main():
56 G.synchronous = 0 # If set, use synchronous audio.write()
57 G.debug = 0 # If set, print debug messages
58 G.busy = 0 # Set while asynchronous playing is active
59 G.windows = [] # List of open windows, except control
60 G.mode = '' # File type (default any that sfplay knows)
61 G.rate = 0 # Sampling rate (default " " " ")
62 G.tempprefix = tempfile.mktemp()
64 try:
65 optlist, args = getopt.getopt(sys.argv[1:], 'dr:st:')
66 except getopt.error, msg:
67 sys.stdout = sys.stderr
68 print msg
69 print 'usage: jukebox [-d] [-s] [-t type] [-r rate]'
70 print ' -d debugging (-dd event debugging)'
71 print ' -s synchronous playing'
72 print ' -t type file type'
73 print ' -r rate sampling rate'
74 sys.exit(2)
76 for optname, optarg in optlist:
77 if optname == '-d':
78 G.debug = G.debug + 1
79 elif optname == '-r':
80 G.rate = int(eval(optarg))
81 elif optname == '-s':
82 G.synchronous = 1
83 elif optname == '-t':
84 G.mode = optarg
86 if not args:
87 args = [DEF_DB]
89 G.cw = opencontrolwindow()
90 for dirname in args:
91 G.windows.append(openlistwindow(dirname))
94 try:
95 maineventloop()
96 finally:
97 clearcache()
98 killchild()
100 # Entries in Rate menu:
101 rates = ['default', '7350', \
102 '8000', '11025', '16000', '22050', '32000', '41000', '48000']
104 def maineventloop():
105 mouse_events = WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP
106 while G.windows:
107 try:
108 type, w, detail = event = stdwin.getevent()
109 except KeyboardInterrupt:
110 killchild()
111 continue
112 if w == G.cw.win:
113 if type == WE_CLOSE:
114 return
115 if type == WE_TIMER:
116 checkchild()
117 if G.busy:
118 G.cw.win.settimer(1)
119 elif type == WE_MENU:
120 menu, item = detail
121 if menu is G.ratemenu:
122 clearcache()
123 if item == 0:
124 G.rate = 0
125 else:
126 G.rate = eval(rates[item])
127 for i in range(len(rates)):
128 menu.check(i, (i == item))
129 else:
130 G.cw.dispatch(event)
131 else:
132 if type == WE_DRAW:
133 w.drawproc(w, detail)
134 elif type in mouse_events:
135 w.mouse(w, type, detail)
136 elif type == WE_CLOSE:
137 w.close(w)
138 del w, event
139 else:
140 if G.debug > 1: print type, w, detail
142 def checkchild():
143 if G.busy:
144 waitchild(1)
146 def killchild():
147 if G.busy:
148 os.kill(G.busy, 9)
149 waitchild(0)
151 def waitchild(options):
152 pid, sts = os.wait(G.busy, options)
153 if pid == G.busy:
154 G.busy = 0
155 G.stop.enable(0)
158 # Control window -- to set gain and cancel play operations in progress
160 def opencontrolwindow():
161 stdwin.setdefscrollbars(0, 0)
162 cw = WindowParent().create('Jukebox', (0, 0))
164 stop = PushButton().definetext(cw, ' Stop ')
165 stop.hook = stop_hook
166 stop.enable(0)
167 G.stop = stop
169 cw.realize()
171 G.ratemenu = cw.win.menucreate('Rate')
172 for r in rates:
173 G.ratemenu.additem(r)
174 if G.rate == 0:
175 G.ratemenu.check(0, 1)
176 else:
177 for i in len(range(rates)):
178 if rates[i] == `G.rate`:
179 G.ratemenu.check(i, 1)
181 return cw
183 def stop_hook(self):
184 killchild()
187 # List windows -- to display list of files and subdirectories
189 def openlistwindow(dirname):
190 list = os.listdir(dirname)
191 list.sort()
192 i = 0
193 while i < len(list):
194 if list[i][0] == '.':
195 del list[i]
196 else:
197 i = i+1
198 for i in range(len(list)):
199 fullname = os.path.join(dirname, list[i])
200 if os.path.isdir(fullname):
201 info = '/'
202 else:
203 try:
204 size = os.stat(fullname)[ST_SIZE]
205 info = `(size + 1023)/1024` + 'k'
206 except IOError:
207 info = '???'
208 info = '(' + info + ')'
209 list[i] = list[i], info
210 width = maxwidth(list)
211 # width = width + stdwin.textwidth(' ') # XXX X11 stdwin bug workaround
212 height = len(list) * stdwin.lineheight()
213 stdwin.setdefwinsize(width, min(height, 500))
214 stdwin.setdefscrollbars(0, 1)
215 w = stdwin.open(dirname)
216 stdwin.setdefwinsize(0, 0)
217 w.setdocsize(width, height)
218 w.drawproc = drawlistwindow
219 w.mouse = mouselistwindow
220 w.close = closelistwindow
221 w.dirname = dirname
222 w.list = list
223 w.selected = -1
224 return w
226 def maxwidth(list):
227 width = 1
228 for name, info in list:
229 w = stdwin.textwidth(name + ' ' + info)
230 if w > width: width = w
231 return width
233 def drawlistwindow(w, area):
234 ## (left, top), (right, bottom) = area
235 d = w.begindrawing()
236 d.erase((0, 0), (1000, 10000))
237 lh = d.lineheight()
238 h, v = 0, 0
239 for name, info in w.list:
240 if info == '/':
241 text = name + '/'
242 else:
243 text = name + ' ' + info
244 d.text((h, v), text)
245 v = v + lh
246 showselection(w, d)
247 d.close()
249 def hideselection(w, d):
250 if w.selected >= 0:
251 invertselection(w, d)
253 def showselection(w, d):
254 if w.selected >= 0:
255 invertselection(w, d)
257 def invertselection(w, d):
258 lh = d.lineheight()
259 h1, v1 = p1 = 0, w.selected*lh
260 h2, v2 = p2 = 1000, v1 + lh
261 d.invert(p1, p2)
263 def mouselistwindow(w, type, detail):
264 (h, v), clicks, button = detail[:3]
265 d = w.begindrawing()
266 lh = d.lineheight()
267 if 0 <= v < lh*len(w.list):
268 i = v / lh
269 else:
270 i = -1
271 if w.selected <> i:
272 hideselection(w, d)
273 w.selected = i
274 showselection(w, d)
275 d.close()
276 if type == WE_MOUSE_DOWN and clicks >= 2 and i >= 0:
277 setcursors('watch')
278 name, info = w.list[i]
279 fullname = os.path.join(w.dirname, name)
280 if info == '/':
281 if clicks == 2:
282 G.windows.append(openlistwindow(fullname))
283 else:
284 playfile(fullname)
285 setcursors('cross')
287 def closelistwindow(w):
288 G.windows.remove(w)
290 def setcursors(cursor):
291 for w in G.windows:
292 w.setwincursor(cursor)
293 G.cw.win.setwincursor(cursor)
296 # Playing tools
298 cache = {}
300 def clearcache():
301 for x in cache.keys():
302 cmd = 'rm -f ' + cache[x]
303 if G.debug: print cmd
304 sts = os.system(cmd)
305 if sts:
306 print cmd
307 print 'Exit status', sts
308 del cache[x]
310 validrates = (8000, 11025, 16000, 22050, 32000, 44100, 48000)
312 def playfile(filename):
313 killchild()
314 try:
315 tuple = sndhdr.what(filename)
316 except IOError, msg:
317 print 'Can\'t open', filename, msg
318 stdwin.fleep()
319 return
320 raw = 0
321 if tuple:
322 mode, rate = tuple[:2]
323 if rate == 0:
324 rate = G.rate
325 if rate == 0:
326 rate = 8000
327 else:
328 mode = G.mode
329 rate = G.rate
330 if G.debug: print 'mode =', mode, 'rate =', rate
331 if mode in ('au', 'aiff', 'wav', 'aifc', 'ul', 'ub', 'sb') and \
332 rate in validrates:
333 tempname = filename
334 if mode in ('ul', 'ub', 'sb'):
335 raw = 1
336 elif cache.has_key(filename):
337 tempname = cache[filename]
338 else:
339 tempname = G.tempprefix + `rand.rand()` + '.aiff'
340 cmd = SOX
341 if G.debug:
342 cmd = cmd + ' -V'
343 if mode <> '':
344 cmd = cmd + ' -t ' + mode
345 cmd = cmd + ' ' + commands.mkarg(filename)
346 cmd = cmd + ' -t aiff'
347 if rate not in validrates:
348 rate = 32000
349 if rate:
350 cmd = cmd + ' -r ' + `rate`
351 cmd = cmd + ' ' + tempname
352 if G.debug: print cmd
353 sts = os.system(cmd)
354 if sts:
355 print cmd
356 print 'Exit status', sts
357 stdwin.fleep()
358 try:
359 os.unlink(tempname)
360 except:
361 pass
362 return
363 cache[filename] = tempname
364 if raw:
365 pid = sfplayraw(tempname, tuple)
366 else:
367 pid = sfplay(tempname, [])
368 if G.synchronous:
369 sts = os.wait(pid, 0)
370 else:
371 G.busy = pid
372 G.stop.enable(1)
373 G.cw.win.settimer(1)
375 def sfplayraw(filename, tuple):
376 args = ['-i']
377 type, rate, channels, frames, bits = tuple
378 if type == 'ul':
379 args.append('mulaw')
380 elif type == 'ub':
381 args = args + ['integer', '8', 'unsigned']
382 elif type == 'sb':
383 args = args + ['integer', '8', '2scomp']
384 else:
385 print 'sfplayraw: warning: unknown type in', tuple
386 if channels > 1:
387 args = args + ['channels', `channels`]
388 if not rate:
389 rate = G.rate
390 if rate:
391 args = args + ['rate', `rate`]
392 args.append('end')
393 return sfplay(filename, args)
395 def sfplay(filename, args):
396 if G.debug:
397 args = ['-p'] + args
398 args = [SFPLAY, '-r'] + args + [filename]
399 if G.debug: print 'sfplay:', args
400 pid = os.fork()
401 if pid == 0:
402 # Child
403 os.execv(SFPLAY, args)
404 # NOTREACHED
405 else:
406 # Parent
407 return pid
409 main()