blob: fba5d71045004f71c0a3d6a83d457594dde9758f [file] [log] [blame]
Guido van Rossumf62e1dd1992-05-15 15:39:56 +00001#! /ufs/guido/bin/sgi/python
2
3# XXX This file is being hacked -- some functionality has been taken out!
4
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#
12# Playing is asynchronous: the application keeps listening to events
13# while the sample is playing, so you can change the volume (gain)
14# during playing, cancel playing or start a new sample right away.
15#
16# The control window displays the current output gain and a primitive
17# "stop button" to cancel the current play request.
18#
19# Sound files must currently be in Dik Winter's compressed Mac format.
20# Since decompression is costly, decompressed samples are saved in
21# /usr/tmp/@j* until the application is left. The files are read
22# afresh each time, though.
23
24import commands
25import getopt
26import os
27import rand
28import stdwin
29from stdwinevents import *
30import string
31import sys
32import tempfile
33
34from WindowParent import WindowParent
35from HVSplit import VSplit
36from Buttons import PushButton
37from Sliders import ComplexSlider
38
39# Pathnames
40
41DEF_DB = '/usr/local/sounds/aiff' # Default directory of sounds
42SOX = '/usr/local/sox' # Sound format conversion program
43SFPLAY = '/usr/sbin/sfplay' # Sound playing program
44
45
46# Global variables
47
48class struct(): pass # Class to define featureless structures
49
50G = struct() # Holds writable global variables
51
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]'
70 print ' -d debugging'
71 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':
78 G.debug = 1
79 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 #
86 if not args:
87 args = [DEF_DB]
88 #
89 G.cw = opencontrolwindow()
90 for dirname in args:
91 G.windows.append(openlistwindow(dirname))
92 #
93 #
94 try:
95 maineventloop()
96 finally:
97 clearcache()
98 killchild()
99
100def maineventloop():
101 mouse_events = WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP
102 while G.windows:
103 type, w, detail = event = stdwin.getevent()
104 if w == G.cw.win:
105 if type == WE_CLOSE:
106 return
107 if type == WE_TIMER:
108 checkchild()
109 if G.busy:
110 G.cw.win.settimer(1)
111 else:
112 G.cw.dispatch(event)
113 else:
114 if type == WE_DRAW:
115 w.drawproc(w, detail)
116 elif type in mouse_events:
117 w.mouse(w, type, detail)
118 elif type == WE_CLOSE:
119 w.close(w)
120 del w, event
121 else:
122 if G.debug: print type, w, detail
123
124def checkchild():
125 if G.busy:
126 waitchild(1)
127
128def killchild():
129 if G.busy:
130 os.kill(G.busy, 9)
131 waitchild(0)
132
133def waitchild(options):
134 pid, sts = os.wait(G.busy, options)
135 if pid == G.busy:
136 G.busy = 0
137 G.stop.enable(0)
138
139
140# Control window -- to set gain and cancel play operations in progress
141
142def opencontrolwindow():
143 stdwin.setdefscrollbars(0, 0)
144 cw = WindowParent().create('Jukebox', (0, 0))
145 v = VSplit().create(cw)
146 #
147 stop = PushButton().definetext(v, 'Stop')
148 stop.hook = stop_hook
149 stop.enable(0)
150 G.stop = stop
151 #
152 cw.realize()
153 return cw
154
155def stop_hook(self):
156 killchild()
157
158
159# List windows -- to display list of files and subdirectories
160
161def openlistwindow(dirname):
162 list = os.listdir(dirname)
163 list.sort()
164 i = 0
165 while i < len(list):
166 if list[i] == '.' or list[i] == '..':
167 del list[i]
168 else:
169 i = i+1
170 for i in range(len(list)):
171 name = list[i]
172 if os.path.isdir(os.path.join(dirname, name)):
173 list[i] = list[i] + '/'
174 width = maxwidth(list)
175 # width = width + stdwin.textwidth(' ') # XXX X11 stdwin bug workaround
176 height = len(list) * stdwin.lineheight()
177 stdwin.setdefwinsize(width, min(height, 500))
178 stdwin.setdefscrollbars(0, 1)
179 w = stdwin.open(dirname)
180 stdwin.setdefwinsize(0, 0)
181 w.setdocsize(width, height)
182 w.drawproc = drawlistwindow
183 w.mouse = mouselistwindow
184 w.close = closelistwindow
185 w.dirname = dirname
186 w.list = list
187 w.selected = -1
188 return w
189
190def maxwidth(list):
191 width = 1
192 for name in list:
193 w = stdwin.textwidth(name)
194 if w > width: width = w
195 return width
196
197def drawlistwindow(w, area):
198## (left, top), (right, bottom) = area
199 d = w.begindrawing()
200 d.erase((0, 0), (1000, 10000))
201 lh = d.lineheight()
202 h, v = 0, 0
203 for name in w.list:
204 d.text((h, v), name)
205 v = v + lh
206 showselection(w, d)
207 d.close()
208
209def hideselection(w, d):
210 if w.selected >= 0:
211 invertselection(w, d)
212
213def showselection(w, d):
214 if w.selected >= 0:
215 invertselection(w, d)
216
217def invertselection(w, d):
218 lh = d.lineheight()
219 h1, v1 = p1 = 0, w.selected*lh
220 h2, v2 = p2 = 1000, v1 + lh
221 d.invert(p1, p2)
222
223def mouselistwindow(w, type, detail):
224 (h, v), clicks, button = detail[:3]
225 d = w.begindrawing()
226 lh = d.lineheight()
227 if 0 <= v < lh*len(w.list):
228 i = v / lh
229 else:
230 i = -1
231 if w.selected <> i:
232 hideselection(w, d)
233 w.selected = i
234 showselection(w, d)
235 d.close()
236 if type == WE_MOUSE_DOWN and clicks >= 2 and i >= 0:
237 setcursors('watch')
238 name = os.path.join(w.dirname, w.list[i])
239 if name[-1:] == '/':
240 if clicks == 2:
241 G.windows.append(openlistwindow(name[:-1]))
242 else:
243 playfile(name)
244 setcursors('cross')
245
246def closelistwindow(w):
247 remove(G.windows, w)
248
249def remove(list, item):
250 for i in range(len(list)):
251 if list[i] == item:
252 del list[i]
253 break
254
255def setcursors(cursor):
256 for w in G.windows:
257 w.setwincursor(cursor)
258 G.cw.win.setwincursor(cursor)
259
260
261# Playing tools
262
263cache = {}
264
265def clearcache():
266 for x in cache.keys():
267 try:
268 sts = os.system('rm -f ' + cache[x])
269 if sts:
270 print cmd
271 print 'Exit status', sts
272 except:
273 print cmd
274 print 'Exception?!'
275 del cache[x]
276
277def playfile(name):
278 killchild()
279 if G.mode in ('', 'au', 'aiff'):
280 tempname = name
281 elif cache.has_key(name):
282 tempname = cache[name]
283 else:
284 tempname = G.tempprefix + `rand.rand()` + '.aiff'
285 cmd = SOX
286 if G.mode <> '' and G.mode <> 'sox':
287 cmd = cmd + ' -t ' + G.mode
288 cmd = cmd + ' ' + commands.mkarg(name)
289 cmd = cmd + ' -t aiff'
290 if G.rate:
291 cmd = cmd + ' -r ' + `G.rate`
292 cmd = cmd + ' ' + tempname
293 if G.debug: print cmd
294 sts = os.system(cmd)
295 if sts:
296 print cmd
297 print 'Exit status', sts
298 stdwin.fleep()
299 return
300 cache[name] = tempname
301 pid = os.fork()
302 if pid == 0:
303 # Child
304 os.exec(SFPLAY, [SFPLAY, '-r', tempname])
305 # NOTREACHED
306 # Parent
307 if G.synchronous:
308 sts = os.wait(pid, 0)
309 else:
310 G.busy = pid
311 G.stop.enable(1)
312 G.cw.win.settimer(1)
313
314main()