#! /usr/bin/env python # XXX This only works on SGIs running IRIX 4.0 or higher # JUKEBOX: browse directories full of sampled sound files. # # One or more "list windows" display the files and subdirectories of # the arguments. Double-clicking on a subdirectory opens a new window # displaying its contents (and so on recursively). Double clicking # on a file plays it as a sound file (assuming it is one). # # Playing is asynchronous: the application keeps listening for events # while the sample is playing, so you can cancel playing or start a # new sample right away. Synchronous playing is available through the # -s option. # # The control window displays a "stop button" that cancel the current # play request. # # Most sound file formats recognized by SOX or SFPLAY are recognized. # Since conversion is costly, converted files are cached in # /usr/tmp/@j* until the user quits or changes the sampling rate via # the Rate menu. import commands import getopt import os from stat import * import rand import stdwin from stdwinevents import * import sys import tempfile import sndhdr from WindowParent import WindowParent from Buttons import PushButton # Pathnames DEF_DB = '/usr/local/sounds' # Default directory of sounds SOX = '/usr/local/bin/sox' # Sound format conversion program SFPLAY = '/usr/sbin/sfplay' # Sound playing program # Global variables class struct: pass # Class to define featureless structures G = struct() # Holds writable global variables # Main program def main(): G.synchronous = 0 # If set, use synchronous audio.write() G.debug = 0 # If set, print debug messages G.busy = 0 # Set while asynchronous playing is active G.windows = [] # List of open windows, except control G.mode = '' # File type (default any that sfplay knows) G.rate = 0 # Sampling rate (default " " " ") G.tempprefix = tempfile.mktemp() # try: optlist, args = getopt.getopt(sys.argv[1:], 'dr:st:') except getopt.error, msg: sys.stdout = sys.stderr print msg print 'usage: jukebox [-d] [-s] [-t type] [-r rate]' print ' -d debugging (-dd event debugging)' print ' -s synchronous playing' print ' -t type file type' print ' -r rate sampling rate' sys.exit(2) # for optname, optarg in optlist: if optname == '-d': G.debug = G.debug + 1 elif optname == '-r': G.rate = int(eval(optarg)) elif optname == '-s': G.synchronous = 1 elif optname == '-t': G.mode = optarg # if G.debug: for name in G.__dict__.keys(): print 'G.' + name, '=', `G.__dict__[name]` # if not args: args = [DEF_DB] # G.cw = opencontrolwindow() for dirname in args: G.windows.append(openlistwindow(dirname)) # # try: maineventloop() finally: clearcache() killchild() # Entries in Rate menu: rates = ['default', '7350', \ '8000', '11025', '16000', '22050', '32000', '41000', '48000'] def maineventloop(): mouse_events = WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP while G.windows: try: type, w, detail = event = stdwin.getevent() except KeyboardInterrupt: killchild() continue if w == G.cw.win: if type == WE_CLOSE: return if type == WE_TIMER: checkchild() if G.busy: G.cw.win.settimer(1) elif type == WE_MENU: menu, item = detail if menu is G.ratemenu: clearcache() if item == 0: G.rate = 0 else: G.rate = eval(rates[item]) for i in range(len(rates)): menu.check(i, (i == item)) else: G.cw.dispatch(event) else: if type == WE_DRAW: w.drawproc(w, detail) elif type in mouse_events: w.mouse(w, type, detail) elif type == WE_CLOSE: w.close(w) del w, event else: if G.debug > 1: print type, w, detail def checkchild(): if G.busy: waitchild(1) def killchild(): if G.busy: os.kill(G.busy, 9) waitchild(0) def waitchild(options): pid, sts = os.waitpid(G.busy, options) if pid == G.busy: G.busy = 0 G.stop.enable(0) # Control window -- to set gain and cancel play operations in progress def opencontrolwindow(): stdwin.setdefscrollbars(0, 0) cw = WindowParent().create('Jukebox', (0, 0)) # stop = PushButton().definetext(cw, ' Stop ') stop.hook = stop_hook stop.enable(0) G.stop = stop # cw.realize() # G.ratemenu = cw.win.menucreate('Rate') for r in rates: G.ratemenu.additem(r) if G.rate == 0: G.ratemenu.check(0, 1) else: for i in len(range(rates)): if rates[i] == `G.rate`: G.ratemenu.check(i, 1) # return cw def stop_hook(self): killchild() # List windows -- to display list of files and subdirectories def openlistwindow(dirname): list = os.listdir(dirname) list.sort() i = 0 while i < len(list): if list[i][0] == '.': del list[i] else: i = i+1 for i in range(len(list)): fullname = os.path.join(dirname, list[i]) if os.path.isdir(fullname): info = '/' else: try: size = os.stat(fullname)[ST_SIZE] info = `(size + 1023)/1024` + 'k' except IOError: info = '???' info = '(' + info + ')' list[i] = list[i], info width = maxwidth(list) # width = width + stdwin.textwidth(' ') # XXX X11 stdwin bug workaround height = len(list) * stdwin.lineheight() stdwin.setdefwinsize(width, min(height, 500)) stdwin.setdefscrollbars(0, 1) w = stdwin.open(dirname) stdwin.setdefwinsize(0, 0) w.setdocsize(width, height) w.drawproc = drawlistwindow w.mouse = mouselistwindow w.close = closelistwindow w.dirname = dirname w.list = list w.selected = -1 return w def maxwidth(list): width = 1 for name, info in list: w = stdwin.textwidth(name + ' ' + info) if w > width: width = w return width def drawlistwindow(w, area): ## (left, top), (right, bottom) = area d = w.begindrawing() d.erase((0, 0), (1000, 10000)) lh = d.lineheight() h, v = 0, 0 for name, info in w.list: if info == '/': text = name + '/' else: text = name + ' ' + info d.text((h, v), text) v = v + lh showselection(w, d) d.close() def hideselection(w, d): if w.selected >= 0: invertselection(w, d) def showselection(w, d): if w.selected >= 0: invertselection(w, d) def invertselection(w, d): lh = d.lineheight() h1, v1 = p1 = 0, w.selected*lh h2, v2 = p2 = 1000, v1 + lh d.invert(p1, p2) def mouselistwindow(w, type, detail): (h, v), clicks, button = detail[:3] d = w.begindrawing() lh = d.lineheight() if 0 <= v < lh*len(w.list): i = v / lh else: i = -1 if w.selected <> i: hideselection(w, d) w.selected = i showselection(w, d) d.close() if type == WE_MOUSE_DOWN and clicks >= 2 and i >= 0: setcursors('watch') name, info = w.list[i] fullname = os.path.join(w.dirname, name) if info == '/': if clicks == 2: G.windows.append(openlistwindow(fullname)) else: playfile(fullname) setcursors('cross') def closelistwindow(w): G.windows.remove(w) def setcursors(cursor): for w in G.windows: w.setwincursor(cursor) G.cw.win.setwincursor(cursor) # Playing tools cache = {} def clearcache(): for x in cache.keys(): cmd = 'rm -f ' + cache[x] if G.debug: print cmd sts = os.system(cmd) if sts: print cmd print 'Exit status', sts del cache[x] validrates = (8000, 11025, 16000, 22050, 32000, 44100, 48000) def playfile(filename): killchild() try: tuple = sndhdr.what(filename) except IOError, msg: print 'Can\'t open', filename, msg stdwin.fleep() return raw = 0 if tuple: mode, rate = tuple[:2] if rate == 0: rate = G.rate if rate == 0: rate = 8000 else: mode = G.mode rate = G.rate if G.debug: print 'mode =', mode, 'rate =', rate if mode in ('au', 'aiff', 'wav', 'aifc', 'ul', 'ub', 'sb') and \ rate in validrates: tempname = filename if mode in ('ul', 'ub', 'sb'): raw = 1 elif cache.has_key(filename): tempname = cache[filename] else: tempname = G.tempprefix + `rand.rand()` + '.aiff' cmd = SOX if G.debug: cmd = cmd + ' -V' if mode <> '': cmd = cmd + ' -t ' + mode cmd = cmd + ' ' + commands.mkarg(filename) cmd = cmd + ' -t aiff' if rate not in validrates: rate = 32000 if rate: cmd = cmd + ' -r ' + `rate` cmd = cmd + ' ' + tempname if G.debug: print cmd sts = os.system(cmd) if sts: print cmd print 'Exit status', sts stdwin.fleep() try: os.unlink(tempname) except: pass return cache[filename] = tempname if raw: pid = sfplayraw(tempname, tuple) else: pid = sfplay(tempname, []) if G.synchronous: sts = os.wait(pid, 0) else: G.busy = pid G.stop.enable(1) G.cw.win.settimer(1) def sfplayraw(filename, tuple): args = ['-i'] type, rate, channels, frames, bits = tuple if type == 'ul': args.append('mulaw') elif type == 'ub': args = args + ['integer', '8', 'unsigned'] elif type == 'sb': args = args + ['integer', '8', '2scomp'] else: print 'sfplayraw: warning: unknown type in', tuple if channels > 1: args = args + ['channels', `channels`] if not rate: rate = G.rate if rate: args = args + ['rate', `rate`] args.append('end') return sfplay(filename, args) def sfplay(filename, args): if G.debug: args = ['-p'] + args args = [SFPLAY, '-r'] + args + [filename] if G.debug: print 'sfplay:', args pid = os.fork() if pid == 0: # Child os.execv(SFPLAY, args) # NOTREACHED else: # Parent return pid main()