blob: e3c9db8b281e14124f4021c9b4416db0d7f18f95 [file] [log] [blame]
Guido van Rossumf06ee5f1996-11-27 19:52:01 +00001#! /usr/bin/env 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
Guido van Rossumef963591992-05-19 13:47:37 +000022# /usr/tmp/@j* until the user quits or changes the sampling rate via
23# the Rate menu.
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000024
25import commands
26import getopt
27import os
Guido van Rossumef963591992-05-19 13:47:37 +000028from stat import *
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000029import rand
30import stdwin
31from stdwinevents import *
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000032import sys
33import tempfile
Guido van Rossumef963591992-05-19 13:47:37 +000034import sndhdr
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000035
36from WindowParent import WindowParent
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000037from Buttons import PushButton
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000038
39# Pathnames
40
Guido van Rossum157e3f81992-05-18 14:49:07 +000041DEF_DB = '/usr/local/sounds' # Default directory of sounds
Guido van Rossum5ba61421992-12-14 14:11:15 +000042SOX = '/usr/local/bin/sox' # Sound format conversion program
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000043SFPLAY = '/usr/sbin/sfplay' # Sound playing program
44
45
46# Global variables
47
Guido van Rossumd55f4d11993-12-17 14:39:12 +000048class struct: pass # Class to define featureless structures
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000049
Guido van Rossum3dc44ab1994-10-08 19:24:02 +000050G = struct() # Holds writable global variables
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000051
52
53# Main program
54
55def 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()
63 #
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]'
Guido van Rossum157e3f81992-05-18 14:49:07 +000070 print ' -d debugging (-dd event debugging)'
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000071 print ' -s synchronous playing'
72 print ' -t type file type'
73 print ' -r rate sampling rate'
74 sys.exit(2)
75 #
76 for optname, optarg in optlist:
77 if optname == '-d':
Guido van Rossum157e3f81992-05-18 14:49:07 +000078 G.debug = G.debug + 1
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000079 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
85 #
Guido van Rossum3dc44ab1994-10-08 19:24:02 +000086 if G.debug:
87 for name in G.__dict__.keys():
88 print 'G.' + name, '=', `G.__dict__[name]`
89 #
Guido van Rossumf62e1dd1992-05-15 15:39:56 +000090 if not args:
91 args = [DEF_DB]
92 #
93 G.cw = opencontrolwindow()
94 for dirname in args:
95 G.windows.append(openlistwindow(dirname))
96 #
97 #
98 try:
99 maineventloop()
100 finally:
101 clearcache()
102 killchild()
103
Guido van Rossum157e3f81992-05-18 14:49:07 +0000104# Entries in Rate menu:
Guido van Rossumef963591992-05-19 13:47:37 +0000105rates = ['default', '7350', \
Guido van Rossum157e3f81992-05-18 14:49:07 +0000106 '8000', '11025', '16000', '22050', '32000', '41000', '48000']
107
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000108def maineventloop():
109 mouse_events = WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP
110 while G.windows:
Guido van Rossumef963591992-05-19 13:47:37 +0000111 try:
112 type, w, detail = event = stdwin.getevent()
113 except KeyboardInterrupt:
114 killchild()
115 continue
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000116 if w == G.cw.win:
117 if type == WE_CLOSE:
118 return
119 if type == WE_TIMER:
120 checkchild()
121 if G.busy:
122 G.cw.win.settimer(1)
Guido van Rossum157e3f81992-05-18 14:49:07 +0000123 elif type == WE_MENU:
124 menu, item = detail
125 if menu is G.ratemenu:
126 clearcache()
127 if item == 0:
128 G.rate = 0
129 else:
130 G.rate = eval(rates[item])
131 for i in range(len(rates)):
132 menu.check(i, (i == item))
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000133 else:
134 G.cw.dispatch(event)
135 else:
136 if type == WE_DRAW:
137 w.drawproc(w, detail)
138 elif type in mouse_events:
139 w.mouse(w, type, detail)
140 elif type == WE_CLOSE:
141 w.close(w)
142 del w, event
143 else:
Guido van Rossum157e3f81992-05-18 14:49:07 +0000144 if G.debug > 1: print type, w, detail
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000145
146def checkchild():
147 if G.busy:
148 waitchild(1)
149
150def killchild():
151 if G.busy:
152 os.kill(G.busy, 9)
153 waitchild(0)
154
155def waitchild(options):
Guido van Rossum3dc44ab1994-10-08 19:24:02 +0000156 pid, sts = os.waitpid(G.busy, options)
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000157 if pid == G.busy:
158 G.busy = 0
159 G.stop.enable(0)
160
161
162# Control window -- to set gain and cancel play operations in progress
163
164def opencontrolwindow():
165 stdwin.setdefscrollbars(0, 0)
166 cw = WindowParent().create('Jukebox', (0, 0))
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000167 #
Guido van Rossum157e3f81992-05-18 14:49:07 +0000168 stop = PushButton().definetext(cw, ' Stop ')
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000169 stop.hook = stop_hook
170 stop.enable(0)
171 G.stop = stop
172 #
173 cw.realize()
Guido van Rossum157e3f81992-05-18 14:49:07 +0000174 #
175 G.ratemenu = cw.win.menucreate('Rate')
176 for r in rates:
177 G.ratemenu.additem(r)
178 if G.rate == 0:
179 G.ratemenu.check(0, 1)
180 else:
181 for i in len(range(rates)):
182 if rates[i] == `G.rate`:
183 G.ratemenu.check(i, 1)
184 #
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000185 return cw
186
187def stop_hook(self):
188 killchild()
189
190
191# List windows -- to display list of files and subdirectories
192
193def openlistwindow(dirname):
194 list = os.listdir(dirname)
195 list.sort()
196 i = 0
197 while i < len(list):
Guido van Rossumef963591992-05-19 13:47:37 +0000198 if list[i][0] == '.':
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000199 del list[i]
200 else:
201 i = i+1
202 for i in range(len(list)):
Guido van Rossumef963591992-05-19 13:47:37 +0000203 fullname = os.path.join(dirname, list[i])
204 if os.path.isdir(fullname):
205 info = '/'
206 else:
207 try:
208 size = os.stat(fullname)[ST_SIZE]
209 info = `(size + 1023)/1024` + 'k'
210 except IOError:
211 info = '???'
212 info = '(' + info + ')'
213 list[i] = list[i], info
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000214 width = maxwidth(list)
215 # width = width + stdwin.textwidth(' ') # XXX X11 stdwin bug workaround
216 height = len(list) * stdwin.lineheight()
217 stdwin.setdefwinsize(width, min(height, 500))
218 stdwin.setdefscrollbars(0, 1)
219 w = stdwin.open(dirname)
220 stdwin.setdefwinsize(0, 0)
221 w.setdocsize(width, height)
222 w.drawproc = drawlistwindow
223 w.mouse = mouselistwindow
224 w.close = closelistwindow
225 w.dirname = dirname
226 w.list = list
227 w.selected = -1
228 return w
229
230def maxwidth(list):
231 width = 1
Guido van Rossumef963591992-05-19 13:47:37 +0000232 for name, info in list:
233 w = stdwin.textwidth(name + ' ' + info)
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000234 if w > width: width = w
235 return width
236
237def drawlistwindow(w, area):
238## (left, top), (right, bottom) = area
239 d = w.begindrawing()
240 d.erase((0, 0), (1000, 10000))
241 lh = d.lineheight()
242 h, v = 0, 0
Guido van Rossumef963591992-05-19 13:47:37 +0000243 for name, info in w.list:
244 if info == '/':
245 text = name + '/'
246 else:
247 text = name + ' ' + info
248 d.text((h, v), text)
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000249 v = v + lh
250 showselection(w, d)
251 d.close()
252
253def hideselection(w, d):
254 if w.selected >= 0:
255 invertselection(w, d)
256
257def showselection(w, d):
258 if w.selected >= 0:
259 invertselection(w, d)
260
261def invertselection(w, d):
262 lh = d.lineheight()
263 h1, v1 = p1 = 0, w.selected*lh
264 h2, v2 = p2 = 1000, v1 + lh
265 d.invert(p1, p2)
266
267def mouselistwindow(w, type, detail):
268 (h, v), clicks, button = detail[:3]
269 d = w.begindrawing()
270 lh = d.lineheight()
271 if 0 <= v < lh*len(w.list):
272 i = v / lh
273 else:
274 i = -1
275 if w.selected <> i:
276 hideselection(w, d)
277 w.selected = i
278 showselection(w, d)
279 d.close()
280 if type == WE_MOUSE_DOWN and clicks >= 2 and i >= 0:
281 setcursors('watch')
Guido van Rossumef963591992-05-19 13:47:37 +0000282 name, info = w.list[i]
283 fullname = os.path.join(w.dirname, name)
284 if info == '/':
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000285 if clicks == 2:
Guido van Rossumef963591992-05-19 13:47:37 +0000286 G.windows.append(openlistwindow(fullname))
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000287 else:
Guido van Rossumef963591992-05-19 13:47:37 +0000288 playfile(fullname)
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000289 setcursors('cross')
290
291def closelistwindow(w):
Guido van Rossumef963591992-05-19 13:47:37 +0000292 G.windows.remove(w)
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000293
294def setcursors(cursor):
295 for w in G.windows:
296 w.setwincursor(cursor)
297 G.cw.win.setwincursor(cursor)
298
299
300# Playing tools
301
302cache = {}
303
304def clearcache():
305 for x in cache.keys():
Guido van Rossum157e3f81992-05-18 14:49:07 +0000306 cmd = 'rm -f ' + cache[x]
307 if G.debug: print cmd
308 sts = os.system(cmd)
309 if sts:
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000310 print cmd
Guido van Rossum157e3f81992-05-18 14:49:07 +0000311 print 'Exit status', sts
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000312 del cache[x]
313
Guido van Rossum157e3f81992-05-18 14:49:07 +0000314validrates = (8000, 11025, 16000, 22050, 32000, 44100, 48000)
315
316def playfile(filename):
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000317 killchild()
Guido van Rossum5ba61421992-12-14 14:11:15 +0000318 try:
319 tuple = sndhdr.what(filename)
320 except IOError, msg:
321 print 'Can\'t open', filename, msg
322 stdwin.fleep()
323 return
Guido van Rossum157e3f81992-05-18 14:49:07 +0000324 raw = 0
325 if tuple:
326 mode, rate = tuple[:2]
327 if rate == 0:
328 rate = G.rate
329 if rate == 0:
330 rate = 8000
331 else:
332 mode = G.mode
333 rate = G.rate
334 if G.debug: print 'mode =', mode, 'rate =', rate
335 if mode in ('au', 'aiff', 'wav', 'aifc', 'ul', 'ub', 'sb') and \
336 rate in validrates:
337 tempname = filename
338 if mode in ('ul', 'ub', 'sb'):
339 raw = 1
340 elif cache.has_key(filename):
341 tempname = cache[filename]
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000342 else:
343 tempname = G.tempprefix + `rand.rand()` + '.aiff'
344 cmd = SOX
Guido van Rossum157e3f81992-05-18 14:49:07 +0000345 if G.debug:
346 cmd = cmd + ' -V'
347 if mode <> '':
348 cmd = cmd + ' -t ' + mode
349 cmd = cmd + ' ' + commands.mkarg(filename)
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000350 cmd = cmd + ' -t aiff'
Guido van Rossum157e3f81992-05-18 14:49:07 +0000351 if rate not in validrates:
352 rate = 32000
353 if rate:
354 cmd = cmd + ' -r ' + `rate`
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000355 cmd = cmd + ' ' + tempname
356 if G.debug: print cmd
357 sts = os.system(cmd)
358 if sts:
359 print cmd
360 print 'Exit status', sts
361 stdwin.fleep()
Guido van Rossumef963591992-05-19 13:47:37 +0000362 try:
363 os.unlink(tempname)
364 except:
365 pass
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000366 return
Guido van Rossum157e3f81992-05-18 14:49:07 +0000367 cache[filename] = tempname
368 if raw:
369 pid = sfplayraw(tempname, tuple)
370 else:
371 pid = sfplay(tempname, [])
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000372 if G.synchronous:
373 sts = os.wait(pid, 0)
374 else:
375 G.busy = pid
376 G.stop.enable(1)
377 G.cw.win.settimer(1)
378
Guido van Rossum157e3f81992-05-18 14:49:07 +0000379def sfplayraw(filename, tuple):
Guido van Rossum157e3f81992-05-18 14:49:07 +0000380 args = ['-i']
381 type, rate, channels, frames, bits = tuple
382 if type == 'ul':
383 args.append('mulaw')
384 elif type == 'ub':
385 args = args + ['integer', '8', 'unsigned']
386 elif type == 'sb':
387 args = args + ['integer', '8', '2scomp']
388 else:
389 print 'sfplayraw: warning: unknown type in', tuple
390 if channels > 1:
391 args = args + ['channels', `channels`]
392 if not rate:
393 rate = G.rate
394 if rate:
395 args = args + ['rate', `rate`]
396 args.append('end')
397 return sfplay(filename, args)
398
399def sfplay(filename, args):
400 if G.debug:
401 args = ['-p'] + args
402 args = [SFPLAY, '-r'] + args + [filename]
403 if G.debug: print 'sfplay:', args
404 pid = os.fork()
405 if pid == 0:
406 # Child
Guido van Rossum46005661994-01-12 14:05:27 +0000407 os.execv(SFPLAY, args)
Guido van Rossum157e3f81992-05-18 14:49:07 +0000408 # NOTREACHED
409 else:
410 # Parent
411 return pid
412
Guido van Rossumf62e1dd1992-05-15 15:39:56 +0000413main()