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
17 # The control window displays a "stop button" that cancel the current
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
31 from stdwinevents
import *
36 from WindowParent
import WindowParent
37 from Buttons
import PushButton
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
48 class struct
: pass # Class to define featureless structures
50 G
= struct() # oHlds writable global variables
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()
65 optlist
, args
= getopt
.getopt(sys
.argv
[1:], 'dr:st:')
66 except getopt
.error
, msg
:
67 sys
.stdout
= sys
.stderr
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'
76 for optname
, optarg
in optlist
:
80 G
.rate
= int(eval(optarg
))
89 G
.cw
= opencontrolwindow()
91 G
.windows
.append(openlistwindow(dirname
))
100 # Entries in Rate menu:
101 rates
= ['default', '7350', \
102 '8000', '11025', '16000', '22050', '32000', '41000', '48000']
105 mouse_events
= WE_MOUSE_DOWN
, WE_MOUSE_MOVE
, WE_MOUSE_UP
108 type, w
, detail
= event
= stdwin
.getevent()
109 except KeyboardInterrupt:
119 elif type == WE_MENU
:
121 if menu
is G
.ratemenu
:
126 G
.rate
= eval(rates
[item
])
127 for i
in range(len(rates
)):
128 menu
.check(i
, (i
== item
))
133 w
.drawproc(w
, detail
)
134 elif type in mouse_events
:
135 w
.mouse(w
, type, detail
)
136 elif type == WE_CLOSE
:
140 if G
.debug
> 1: print type, w
, detail
151 def waitchild(options
):
152 pid
, sts
= os
.wait(G
.busy
, options
)
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
171 G
.ratemenu
= cw
.win
.menucreate('Rate')
173 G
.ratemenu
.additem(r
)
175 G
.ratemenu
.check(0, 1)
177 for i
in len(range(rates
)):
178 if rates
[i
] == `G
.rate`
:
179 G
.ratemenu
.check(i
, 1)
187 # List windows -- to display list of files and subdirectories
189 def openlistwindow(dirname
):
190 list = os
.listdir(dirname
)
194 if list[i
][0] == '.':
198 for i
in range(len(list)):
199 fullname
= os
.path
.join(dirname
, list[i
])
200 if os
.path
.isdir(fullname
):
204 size
= os
.stat(fullname
)[ST_SIZE
]
205 info
= `
(size
+ 1023)/1024`
+ 'k'
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
228 for name
, info
in list:
229 w
= stdwin
.textwidth(name
+ ' ' + info
)
230 if w
> width
: width
= w
233 def drawlistwindow(w
, area
):
234 ## (left, top), (right, bottom) = area
236 d
.erase((0, 0), (1000, 10000))
239 for name
, info
in w
.list:
243 text
= name
+ ' ' + info
249 def hideselection(w
, d
):
251 invertselection(w
, d
)
253 def showselection(w
, d
):
255 invertselection(w
, d
)
257 def invertselection(w
, d
):
259 h1
, v1
= p1
= 0, w
.selected
*lh
260 h2
, v2
= p2
= 1000, v1
+ lh
263 def mouselistwindow(w
, type, detail
):
264 (h
, v
), clicks
, button
= detail
[:3]
267 if 0 <= v
< lh
*len(w
.list):
276 if type == WE_MOUSE_DOWN
and clicks
>= 2 and i
>= 0:
278 name
, info
= w
.list[i
]
279 fullname
= os
.path
.join(w
.dirname
, name
)
282 G
.windows
.append(openlistwindow(fullname
))
287 def closelistwindow(w
):
290 def setcursors(cursor
):
292 w
.setwincursor(cursor
)
293 G
.cw
.win
.setwincursor(cursor
)
301 for x
in cache
.keys():
302 cmd
= 'rm -f ' + cache
[x
]
303 if G
.debug
: print cmd
307 print 'Exit status', sts
310 validrates
= (8000, 11025, 16000, 22050, 32000, 44100, 48000)
312 def playfile(filename
):
315 tuple = sndhdr
.what(filename
)
317 print 'Can\'t open', filename
, msg
322 mode
, rate
= tuple[:2]
330 if G
.debug
: print 'mode =', mode
, 'rate =', rate
331 if mode
in ('au', 'aiff', 'wav', 'aifc', 'ul', 'ub', 'sb') and \
334 if mode
in ('ul', 'ub', 'sb'):
336 elif cache
.has_key(filename
):
337 tempname
= cache
[filename
]
339 tempname
= G
.tempprefix
+ `rand
.rand()`
+ '.aiff'
344 cmd
= cmd
+ ' -t ' + mode
345 cmd
= cmd
+ ' ' + commands
.mkarg(filename
)
346 cmd
= cmd
+ ' -t aiff'
347 if rate
not in validrates
:
350 cmd
= cmd
+ ' -r ' + `rate`
351 cmd
= cmd
+ ' ' + tempname
352 if G
.debug
: print cmd
356 print 'Exit status', sts
363 cache
[filename
] = tempname
365 pid
= sfplayraw(tempname
, tuple)
367 pid
= sfplay(tempname
, [])
369 sts
= os
.wait(pid
, 0)
375 def sfplayraw(filename
, tuple):
377 type, rate
, channels
, frames
, bits
= tuple
381 args
= args
+ ['integer', '8', 'unsigned']
383 args
= args
+ ['integer', '8', '2scomp']
385 print 'sfplayraw: warning: unknown type in', tuple
387 args
= args
+ ['channels', `channels`
]
391 args
= args
+ ['rate', `rate`
]
393 return sfplay(filename
, args
)
395 def sfplay(filename
, args
):
398 args
= [SFPLAY
, '-r'] + args
+ [filename
]
399 if G
.debug
: print 'sfplay:', args
403 os
.execv(SFPLAY
, args
)