blob: 61e7560769451ef0a2182ae7d8626de3463dc487 [file] [log] [blame]
Guido van Rossum157e3f81992-05-18 14:49:07 +00001#! /usr/local/python
Guido van Rossumf62e1dd1992-05-15 15:39:56 +00002
Guido van Rossum157e3f81992-05-18 14:49:07 +00003# XXX This only works on SGIs running IRIX 4.0 or higher
Guido van Rossumf62e1dd1992-05-15 15:39:56 +00004
5# JUKEBOX: browse directories full of sampled sound files.
6#
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).
11#
Guido van Rossum157e3f81992-05-18 14:49:07 +000012# 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.
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000016#
Guido van Rossum157e3f81992-05-18 14:49:07 +000017# The control window displays a "stop button" that cancel the current
18# play request.
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000019#
Guido van Rossum157e3f81992-05-18 14:49:07 +000020# 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.
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000023
24import commands
25import getopt
26import os
27import rand
28import stdwin
29from stdwinevents import *
30import string
31import sys
32import tempfile
33
34from WindowParent import WindowParent
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000035from Buttons import PushButton
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000036
37# Pathnames
38
Guido van Rossum157e3f81992-05-18 14:49:07 +000039DEF_DB = '/usr/local/sounds' # Default directory of sounds
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000040SOX = '/usr/local/sox' # Sound format conversion program
41SFPLAY = '/usr/sbin/sfplay' # Sound playing program
42
43
44# Global variables
45
46class struct(): pass # Class to define featureless structures
47
Guido van Rossum157e3f81992-05-18 14:49:07 +000048G = struct() # oHlds writable global variables
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000049
50
51# Main program
52
53def main():
54 G.synchronous = 0 # If set, use synchronous audio.write()
55 G.debug = 0 # If set, print debug messages
56 G.busy = 0 # Set while asynchronous playing is active
57 G.windows = [] # List of open windows, except control
58 G.mode = '' # File type (default any that sfplay knows)
59 G.rate = 0 # Sampling rate (default " " " ")
60 G.tempprefix = tempfile.mktemp()
61 #
62 try:
63 optlist, args = getopt.getopt(sys.argv[1:], 'dr:st:')
64 except getopt.error, msg:
65 sys.stdout = sys.stderr
66 print msg
67 print 'usage: jukebox [-d] [-s] [-t type] [-r rate]'
Guido van Rossum157e3f81992-05-18 14:49:07 +000068 print ' -d debugging (-dd event debugging)'
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000069 print ' -s synchronous playing'
70 print ' -t type file type'
71 print ' -r rate sampling rate'
72 sys.exit(2)
73 #
74 for optname, optarg in optlist:
75 if optname == '-d':
Guido van Rossum157e3f81992-05-18 14:49:07 +000076 G.debug = G.debug + 1
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000077 elif optname == '-r':
78 G.rate = int(eval(optarg))
79 elif optname == '-s':
80 G.synchronous = 1
81 elif optname == '-t':
82 G.mode = optarg
83 #
84 if not args:
85 args = [DEF_DB]
86 #
87 G.cw = opencontrolwindow()
88 for dirname in args:
89 G.windows.append(openlistwindow(dirname))
90 #
91 #
92 try:
93 maineventloop()
94 finally:
95 clearcache()
96 killchild()
97
Guido van Rossum157e3f81992-05-18 14:49:07 +000098# Entries in Rate menu:
99rates = ['default', \
100 '8000', '11025', '16000', '22050', '32000', '41000', '48000']
101
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000102def maineventloop():
103 mouse_events = WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP
104 while G.windows:
105 type, w, detail = event = stdwin.getevent()
106 if w == G.cw.win:
107 if type == WE_CLOSE:
108 return
109 if type == WE_TIMER:
110 checkchild()
111 if G.busy:
112 G.cw.win.settimer(1)
Guido van Rossum157e3f81992-05-18 14:49:07 +0000113 elif type == WE_MENU:
114 menu, item = detail
115 if menu is G.ratemenu:
116 clearcache()
117 if item == 0:
118 G.rate = 0
119 else:
120 G.rate = eval(rates[item])
121 for i in range(len(rates)):
122 menu.check(i, (i == item))
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000123 else:
124 G.cw.dispatch(event)
125 else:
126 if type == WE_DRAW:
127 w.drawproc(w, detail)
128 elif type in mouse_events:
129 w.mouse(w, type, detail)
130 elif type == WE_CLOSE:
131 w.close(w)
132 del w, event
133 else:
Guido van Rossum157e3f81992-05-18 14:49:07 +0000134 if G.debug > 1: print type, w, detail
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000135
136def checkchild():
137 if G.busy:
138 waitchild(1)
139
140def killchild():
141 if G.busy:
142 os.kill(G.busy, 9)
143 waitchild(0)
144
145def waitchild(options):
146 pid, sts = os.wait(G.busy, options)
147 if pid == G.busy:
148 G.busy = 0
149 G.stop.enable(0)
150
151
152# Control window -- to set gain and cancel play operations in progress
153
154def opencontrolwindow():
155 stdwin.setdefscrollbars(0, 0)
156 cw = WindowParent().create('Jukebox', (0, 0))
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000157 #
Guido van Rossum157e3f81992-05-18 14:49:07 +0000158 stop = PushButton().definetext(cw, ' Stop ')
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000159 stop.hook = stop_hook
160 stop.enable(0)
161 G.stop = stop
162 #
163 cw.realize()
Guido van Rossum157e3f81992-05-18 14:49:07 +0000164 #
165 G.ratemenu = cw.win.menucreate('Rate')
166 for r in rates:
167 G.ratemenu.additem(r)
168 if G.rate == 0:
169 G.ratemenu.check(0, 1)
170 else:
171 for i in len(range(rates)):
172 if rates[i] == `G.rate`:
173 G.ratemenu.check(i, 1)
174 #
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000175 return cw
176
177def stop_hook(self):
178 killchild()
179
180
181# List windows -- to display list of files and subdirectories
182
183def openlistwindow(dirname):
184 list = os.listdir(dirname)
185 list.sort()
186 i = 0
187 while i < len(list):
188 if list[i] == '.' or list[i] == '..':
189 del list[i]
190 else:
191 i = i+1
192 for i in range(len(list)):
193 name = list[i]
194 if os.path.isdir(os.path.join(dirname, name)):
195 list[i] = list[i] + '/'
196 width = maxwidth(list)
197 # width = width + stdwin.textwidth(' ') # XXX X11 stdwin bug workaround
198 height = len(list) * stdwin.lineheight()
199 stdwin.setdefwinsize(width, min(height, 500))
200 stdwin.setdefscrollbars(0, 1)
201 w = stdwin.open(dirname)
202 stdwin.setdefwinsize(0, 0)
203 w.setdocsize(width, height)
204 w.drawproc = drawlistwindow
205 w.mouse = mouselistwindow
206 w.close = closelistwindow
207 w.dirname = dirname
208 w.list = list
209 w.selected = -1
210 return w
211
212def maxwidth(list):
213 width = 1
214 for name in list:
215 w = stdwin.textwidth(name)
216 if w > width: width = w
217 return width
218
219def drawlistwindow(w, area):
220## (left, top), (right, bottom) = area
221 d = w.begindrawing()
222 d.erase((0, 0), (1000, 10000))
223 lh = d.lineheight()
224 h, v = 0, 0
225 for name in w.list:
226 d.text((h, v), name)
227 v = v + lh
228 showselection(w, d)
229 d.close()
230
231def hideselection(w, d):
232 if w.selected >= 0:
233 invertselection(w, d)
234
235def showselection(w, d):
236 if w.selected >= 0:
237 invertselection(w, d)
238
239def invertselection(w, d):
240 lh = d.lineheight()
241 h1, v1 = p1 = 0, w.selected*lh
242 h2, v2 = p2 = 1000, v1 + lh
243 d.invert(p1, p2)
244
245def mouselistwindow(w, type, detail):
246 (h, v), clicks, button = detail[:3]
247 d = w.begindrawing()
248 lh = d.lineheight()
249 if 0 <= v < lh*len(w.list):
250 i = v / lh
251 else:
252 i = -1
253 if w.selected <> i:
254 hideselection(w, d)
255 w.selected = i
256 showselection(w, d)
257 d.close()
258 if type == WE_MOUSE_DOWN and clicks >= 2 and i >= 0:
259 setcursors('watch')
260 name = os.path.join(w.dirname, w.list[i])
261 if name[-1:] == '/':
262 if clicks == 2:
263 G.windows.append(openlistwindow(name[:-1]))
264 else:
265 playfile(name)
266 setcursors('cross')
267
268def closelistwindow(w):
269 remove(G.windows, w)
270
271def remove(list, item):
272 for i in range(len(list)):
273 if list[i] == item:
274 del list[i]
275 break
276
277def setcursors(cursor):
278 for w in G.windows:
279 w.setwincursor(cursor)
280 G.cw.win.setwincursor(cursor)
281
282
283# Playing tools
284
285cache = {}
286
287def clearcache():
288 for x in cache.keys():
Guido van Rossum157e3f81992-05-18 14:49:07 +0000289 cmd = 'rm -f ' + cache[x]
290 if G.debug: print cmd
291 sts = os.system(cmd)
292 if sts:
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000293 print cmd
Guido van Rossum157e3f81992-05-18 14:49:07 +0000294 print 'Exit status', sts
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000295 del cache[x]
296
Guido van Rossum157e3f81992-05-18 14:49:07 +0000297validrates = (8000, 11025, 16000, 22050, 32000, 44100, 48000)
298
299def playfile(filename):
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000300 killchild()
Guido van Rossum157e3f81992-05-18 14:49:07 +0000301 import sndhdr
302 tuple = sndhdr.what(filename)
303 raw = 0
304 if tuple:
305 mode, rate = tuple[:2]
306 if rate == 0:
307 rate = G.rate
308 if rate == 0:
309 rate = 8000
310 else:
311 mode = G.mode
312 rate = G.rate
313 if G.debug: print 'mode =', mode, 'rate =', rate
314 if mode in ('au', 'aiff', 'wav', 'aifc', 'ul', 'ub', 'sb') and \
315 rate in validrates:
316 tempname = filename
317 if mode in ('ul', 'ub', 'sb'):
318 raw = 1
319 elif cache.has_key(filename):
320 tempname = cache[filename]
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000321 else:
322 tempname = G.tempprefix + `rand.rand()` + '.aiff'
323 cmd = SOX
Guido van Rossum157e3f81992-05-18 14:49:07 +0000324 if G.debug:
325 cmd = cmd + ' -V'
326 if mode <> '':
327 cmd = cmd + ' -t ' + mode
328 cmd = cmd + ' ' + commands.mkarg(filename)
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000329 cmd = cmd + ' -t aiff'
Guido van Rossum157e3f81992-05-18 14:49:07 +0000330 if rate not in validrates:
331 rate = 32000
332 if rate:
333 cmd = cmd + ' -r ' + `rate`
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000334 cmd = cmd + ' ' + tempname
335 if G.debug: print cmd
336 sts = os.system(cmd)
337 if sts:
338 print cmd
339 print 'Exit status', sts
340 stdwin.fleep()
341 return
Guido van Rossum157e3f81992-05-18 14:49:07 +0000342 cache[filename] = tempname
343 if raw:
344 pid = sfplayraw(tempname, tuple)
345 else:
346 pid = sfplay(tempname, [])
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000347 if G.synchronous:
348 sts = os.wait(pid, 0)
349 else:
350 G.busy = pid
351 G.stop.enable(1)
352 G.cw.win.settimer(1)
353
Guido van Rossum157e3f81992-05-18 14:49:07 +0000354def sfplayraw(filename, tuple):
355 import sndhdr
356 args = ['-i']
357 type, rate, channels, frames, bits = tuple
358 if type == 'ul':
359 args.append('mulaw')
360 elif type == 'ub':
361 args = args + ['integer', '8', 'unsigned']
362 elif type == 'sb':
363 args = args + ['integer', '8', '2scomp']
364 else:
365 print 'sfplayraw: warning: unknown type in', tuple
366 if channels > 1:
367 args = args + ['channels', `channels`]
368 if not rate:
369 rate = G.rate
370 if rate:
371 args = args + ['rate', `rate`]
372 args.append('end')
373 return sfplay(filename, args)
374
375def sfplay(filename, args):
376 if G.debug:
377 args = ['-p'] + args
378 args = [SFPLAY, '-r'] + args + [filename]
379 if G.debug: print 'sfplay:', args
380 pid = os.fork()
381 if pid == 0:
382 # Child
383 os.exec(SFPLAY, args)
384 # NOTREACHED
385 else:
386 # Parent
387 return pid
388
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000389main()