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() # Holds 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
))
87 for name
in G
.__dict
__.keys():
88 print 'G.' + name
, '=', `G
.__dict
__[name
]`
93 G
.cw
= opencontrolwindow()
95 G
.windows
.append(openlistwindow(dirname
))
104 # Entries in Rate menu:
105 rates
= ['default', '7350', \
106 '8000', '11025', '16000', '22050', '32000', '41000', '48000']
109 mouse_events
= WE_MOUSE_DOWN
, WE_MOUSE_MOVE
, WE_MOUSE_UP
112 type, w
, detail
= event
= stdwin
.getevent()
113 except KeyboardInterrupt:
123 elif type == WE_MENU
:
125 if menu
is G
.ratemenu
:
130 G
.rate
= eval(rates
[item
])
131 for i
in range(len(rates
)):
132 menu
.check(i
, (i
== item
))
137 w
.drawproc(w
, detail
)
138 elif type in mouse_events
:
139 w
.mouse(w
, type, detail
)
140 elif type == WE_CLOSE
:
144 if G
.debug
> 1: print type, w
, detail
155 def waitchild(options
):
156 pid
, sts
= os
.waitpid(G
.busy
, options
)
162 # Control window -- to set gain and cancel play operations in progress
164 def opencontrolwindow():
165 stdwin
.setdefscrollbars(0, 0)
166 cw
= WindowParent().create('Jukebox', (0, 0))
168 stop
= PushButton().definetext(cw
, ' Stop ')
169 stop
.hook
= stop_hook
175 G
.ratemenu
= cw
.win
.menucreate('Rate')
177 G
.ratemenu
.additem(r
)
179 G
.ratemenu
.check(0, 1)
181 for i
in len(range(rates
)):
182 if rates
[i
] == `G
.rate`
:
183 G
.ratemenu
.check(i
, 1)
191 # List windows -- to display list of files and subdirectories
193 def openlistwindow(dirname
):
194 list = os
.listdir(dirname
)
198 if list[i
][0] == '.':
202 for i
in range(len(list)):
203 fullname
= os
.path
.join(dirname
, list[i
])
204 if os
.path
.isdir(fullname
):
208 size
= os
.stat(fullname
)[ST_SIZE
]
209 info
= `
(size
+ 1023)/1024`
+ 'k'
212 info
= '(' + info
+ ')'
213 list[i
] = list[i
], info
214 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
232 for name
, info
in list:
233 w
= stdwin
.textwidth(name
+ ' ' + info
)
234 if w
> width
: width
= w
237 def drawlistwindow(w
, area
):
238 ## (left, top), (right, bottom) = area
240 d
.erase((0, 0), (1000, 10000))
243 for name
, info
in w
.list:
247 text
= name
+ ' ' + info
253 def hideselection(w
, d
):
255 invertselection(w
, d
)
257 def showselection(w
, d
):
259 invertselection(w
, d
)
261 def invertselection(w
, d
):
263 h1
, v1
= p1
= 0, w
.selected
*lh
264 h2
, v2
= p2
= 1000, v1
+ lh
267 def mouselistwindow(w
, type, detail
):
268 (h
, v
), clicks
, button
= detail
[:3]
271 if 0 <= v
< lh
*len(w
.list):
280 if type == WE_MOUSE_DOWN
and clicks
>= 2 and i
>= 0:
282 name
, info
= w
.list[i
]
283 fullname
= os
.path
.join(w
.dirname
, name
)
286 G
.windows
.append(openlistwindow(fullname
))
291 def closelistwindow(w
):
294 def setcursors(cursor
):
296 w
.setwincursor(cursor
)
297 G
.cw
.win
.setwincursor(cursor
)
305 for x
in cache
.keys():
306 cmd
= 'rm -f ' + cache
[x
]
307 if G
.debug
: print cmd
311 print 'Exit status', sts
314 validrates
= (8000, 11025, 16000, 22050, 32000, 44100, 48000)
316 def playfile(filename
):
319 tuple = sndhdr
.what(filename
)
321 print 'Can\'t open', filename
, msg
326 mode
, rate
= tuple[:2]
334 if G
.debug
: print 'mode =', mode
, 'rate =', rate
335 if mode
in ('au', 'aiff', 'wav', 'aifc', 'ul', 'ub', 'sb') and \
338 if mode
in ('ul', 'ub', 'sb'):
340 elif cache
.has_key(filename
):
341 tempname
= cache
[filename
]
343 tempname
= G
.tempprefix
+ `rand
.rand()`
+ '.aiff'
348 cmd
= cmd
+ ' -t ' + mode
349 cmd
= cmd
+ ' ' + commands
.mkarg(filename
)
350 cmd
= cmd
+ ' -t aiff'
351 if rate
not in validrates
:
354 cmd
= cmd
+ ' -r ' + `rate`
355 cmd
= cmd
+ ' ' + tempname
356 if G
.debug
: print cmd
360 print 'Exit status', sts
367 cache
[filename
] = tempname
369 pid
= sfplayraw(tempname
, tuple)
371 pid
= sfplay(tempname
, [])
373 sts
= os
.wait(pid
, 0)
379 def sfplayraw(filename
, tuple):
381 type, rate
, channels
, frames
, bits
= tuple
385 args
= args
+ ['integer', '8', 'unsigned']
387 args
= args
+ ['integer', '8', '2scomp']
389 print 'sfplayraw: warning: unknown type in', tuple
391 args
= args
+ ['channels', `channels`
]
395 args
= args
+ ['rate', `rate`
]
397 return sfplay(filename
, args
)
399 def sfplay(filename
, args
):
402 args
= [SFPLAY
, '-r'] + args
+ [filename
]
403 if G
.debug
: print 'sfplay:', args
407 os
.execv(SFPLAY
, args
)