3 # JUKEBOX: browse directories full of sampled sound files.
5 # One or more "list windows" display the files and subdirectories of
6 # the arguments. Double-clicking on a subdirectory opens a new window
7 # displaying its contents (and so on recursively). Double clicking
8 # on a file plays it as a sound file (assuming it is one).
10 # Playing is asynchronous: the application keeps listening to events
11 # while the sample is playing, so you can change the volume (gain)
12 # during playing, cancel playing or start a new sample right away.
14 # The control window displays the current output gain and a primitive
15 # "stop button" to cancel the current play request.
17 # Sound files must currently be in Dik Winter's compressed Mac format.
18 # Since decompression is costly, decompressed samples are saved in
19 # /usr/tmp/@j* until the application is left. The files are read
20 # afresh each time, though.
30 from stdwinevents
import *
34 from WindowParent
import WindowParent
35 from HVSplit
import VSplit
36 from Buttons
import PushButton
37 from Sliders
import ComplexSlider
41 HOME_BIN_SGI
= '/ufs/guido/bin/sgi/' # Directory where macsound2sgi lives
42 DEF_DB
= '/ufs/dik/sounds/Mac/HCOM' # Default directory of sounds
47 class struct
: pass # Class to define featureless structures
49 G
= struct() # Holds writable global variables
55 G
.synchronous
= 0 # If set, use synchronous audio.write()
56 G
.debug
= 0 # If set, print debug messages
57 G
.gain
= 75 # Output gain
58 G
.rate
= 3 # Sampling rate
59 G
.busy
= 0 # Set while asynchronous playing is active
60 G
.windows
= [] # List of open windows (except control)
61 G
.mode
= 'mac' # Macintosh mode
62 G
.tempprefix
= '/usr/tmp/@j' + `rand
.rand()`
+ '-'
64 optlist
, args
= getopt
.getopt(sys
.argv
[1:], 'dg:r:sSa')
65 for optname
, optarg
in optlist
:
69 G
.gain
= string
.atoi(optarg
)
70 if not (0 < G
.gain
< 256):
71 raise optarg
.error
, '-g gain out of range'
73 G
.rate
= string
.atoi(optarg
)
74 if not (1 <= G
.rate
<= 3):
75 raise optarg
.error
, '-r rate out of range'
86 G
.cw
= opencontrolwindow()
88 G
.windows
.append(openlistwindow(dirname
))
91 savegain
= audio
.getoutgain()
95 audio
.start_playing('')
96 dummy
= audio
.wait_playing()
100 audio
.setoutgain(savegain
)
105 mouse_events
= WE_MOUSE_DOWN
, WE_MOUSE_MOVE
, WE_MOUSE_UP
107 type, w
, detail
= event
= stdwin
.getevent()
114 w
.drawproc(w
, detail
)
115 elif type in mouse_events
:
116 w
.mouse(w
, type, detail
)
117 elif type == WE_CLOSE
:
121 if G
.debug
: print type, w
, detail
123 # Control window -- to set gain and cancel play operations in progress
125 def opencontrolwindow():
126 cw
= WindowParent().create('Jukebox', (0, 0))
127 v
= VSplit().create(cw
)
129 gain
= ComplexSlider().define(v
)
130 gain
.setminvalmax(0, G
.gain
, 255)
131 gain
.settexts(' ', ' ')
132 gain
.sethook(gain_setval_hook
)
134 stop
= PushButton().definetext(v
, 'Stop')
135 stop
.hook
= stop_hook
140 def gain_setval_hook(self
):
142 if G
.busy
: audio
.setoutgain(G
.gain
)
147 dummy
= audio
.stop_playing()
151 # List windows -- to display list of files and subdirectories
153 def openlistwindow(dirname
):
154 list = posix
.listdir(dirname
)
158 if list[i
] == '.' or list[i
] == '..':
162 for i
in range(len(list)):
164 if path
.isdir(path
.join(dirname
, name
)):
165 list[i
] = list[i
] + '/'
166 width
= maxwidth(list)
167 width
= width
+ stdwin
.textwidth(' ') # XXX X11 stdwin bug workaround
168 height
= len(list) * stdwin
.lineheight()
169 stdwin
.setdefwinsize(width
, min(height
, 500))
170 w
= stdwin
.open(dirname
)
171 stdwin
.setdefwinsize(0, 0)
172 w
.setdocsize(width
, height
)
173 w
.drawproc
= drawlistwindow
174 w
.mouse
= mouselistwindow
175 w
.close
= closelistwindow
184 w
= stdwin
.textwidth(name
)
185 if w
> width
: width
= w
188 def drawlistwindow(w
, area
):
190 d
.erase((0, 0), (1000, 10000))
198 def hideselection(w
, d
):
200 invertselection(w
, d
)
202 def showselection(w
, d
):
204 invertselection(w
, d
)
206 def invertselection(w
, d
):
208 h1
, v1
= p1
= 0, w
.selected
*lh
209 h2
, v2
= p2
= 1000, v1
+ lh
212 def mouselistwindow(w
, type, detail
):
213 (h
, v
), clicks
, button
= detail
[:3]
216 if 0 <= v
< lh
*len(w
.list):
224 if type == WE_MOUSE_DOWN
and clicks
>= 2 and i
>= 0:
225 name
= path
.join(w
.dirname
, w
.list[i
])
228 G
.windows
.append(openlistwindow(name
[:-1]))
232 def closelistwindow(w
):
235 def remove(list, item
):
236 for i
in range(len(list)):
247 for x
in cache
.keys():
249 sts
= posix
.system('rm -f ' + cache
[x
])
252 print 'Exit status', sts
261 elif cache
.has_key(name
):
262 tempname
= cache
[name
]
264 tempname
= G
.tempprefix
+ `rand
.rand()`
265 cmd
= HOME_BIN_SGI
+ 'macsound2sgi'
266 cmd
= cmd
+ ' ' + commands
.mkarg(name
)
267 cmd
= cmd
+ ' >' + tempname
268 if G
.debug
: print cmd
269 sts
= posix
.system(cmd
)
272 print 'Exit status', sts
275 cache
[name
] = tempname
276 fp
= open(tempname
, 'r')
278 hdr
= sunaudio
.gethdr(fp
)
279 except sunaudio
.error
, msg
:
283 data
= fp
.read(data_size
)
284 # XXX this doesn't work yet, need to convert from uLAW!!!
288 data
= readfile(tempname
)
289 if G
.debug
: print len(data
), 'bytes read from', tempname
292 dummy
= audio
.stop_playing()
294 # Completely reset the audio device
295 audio
.setrate(G
.rate
)
297 audio
.setoutgain(G
.gain
)
304 audio
.start_playing(data
)
310 def readfile(filename
):
311 return readfp(open(filename
, 'r'))
316 buf
= fp
.read(102400) # Reads most samples in one fell swoop