#! /usr/bin/env python # 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 to events # while the sample is playing, so you can change the volume (gain) # during playing, cancel playing or start a new sample right away. # # The control window displays the current output gain and a primitive # "stop button" to cancel the current play request. # # Sound files must currently be in Dik Winter's compressed Mac format. # Since decompression is costly, decompressed samples are saved in # /usr/tmp/@j* until the application is left. The files are read # afresh each time, though. import audio import sunaudio import commands import getopt import path import posix import rand import stdwin from stdwinevents import * import string import sys from WindowParent import WindowParent from HVSplit import VSplit from Buttons import PushButton from Sliders import ComplexSlider # Pathnames HOME_BIN_SGI = '/ufs/guido/bin/sgi/' # Directory where macsound2sgi lives DEF_DB = '/ufs/dik/sounds/Mac/HCOM' # Default directory of sounds # 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.gain = 75 # Output gain G.rate = 3 # Sampling rate G.busy = 0 # Set while asynchronous playing is active G.windows = [] # List of open windows (except control) G.mode = 'mac' # Macintosh mode G.tempprefix = '/usr/tmp/@j' + `rand.rand()` + '-' # optlist, args = getopt.getopt(sys.argv[1:], 'dg:r:sSa') for optname, optarg in optlist: if optname == '-d': G.debug = 1 elif optname == '-g': G.gain = string.atoi(optarg) if not (0 < G.gain < 256): raise optarg.error, '-g gain out of range' elif optname == '-r': G.rate = string.atoi(optarg) if not (1 <= G.rate <= 3): raise optarg.error, '-r rate out of range' elif optname == '-s': G.synchronous = 1 elif optname == '-S': G.mode = 'sgi' elif optname == '-a': G.mode = 'sun' # if not args: args = [DEF_DB] # G.cw = opencontrolwindow() for dirname in args: G.windows.append(openlistwindow(dirname)) # # savegain = audio.getoutgain() try: # Initialize stdaudio audio.setoutgain(0) audio.start_playing('') dummy = audio.wait_playing() audio.setoutgain(0) maineventloop() finally: audio.setoutgain(savegain) audio.done() clearcache() def maineventloop(): mouse_events = WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP while G.windows: type, w, detail = event = stdwin.getevent() if w == G.cw.win: if type == WE_CLOSE: return 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: print type, w, detail # Control window -- to set gain and cancel play operations in progress def opencontrolwindow(): cw = WindowParent().create('Jukebox', (0, 0)) v = VSplit().create(cw) # gain = ComplexSlider().define(v) gain.setminvalmax(0, G.gain, 255) gain.settexts(' ', ' ') gain.sethook(gain_setval_hook) # stop = PushButton().definetext(v, 'Stop') stop.hook = stop_hook # cw.realize() return cw def gain_setval_hook(self): G.gain = self.val if G.busy: audio.setoutgain(G.gain) def stop_hook(self): if G.busy: audio.setoutgain(0) dummy = audio.stop_playing() G.busy = 0 # List windows -- to display list of files and subdirectories def openlistwindow(dirname): list = posix.listdir(dirname) list.sort() i = 0 while i < len(list): if list[i] == '.' or list[i] == '..': del list[i] else: i = i+1 for i in range(len(list)): name = list[i] if path.isdir(path.join(dirname, name)): list[i] = list[i] + '/' width = maxwidth(list) width = width + stdwin.textwidth(' ') # XXX X11 stdwin bug workaround height = len(list) * stdwin.lineheight() stdwin.setdefwinsize(width, min(height, 500)) 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 in list: w = stdwin.textwidth(name) if w > width: width = w return width def drawlistwindow(w, area): d = w.begindrawing() d.erase((0, 0), (1000, 10000)) lh = d.lineheight() h, v = 0, 0 for name in w.list: d.text((h, v), name) v = v + lh showselection(w, d) 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) if type == WE_MOUSE_DOWN and clicks >= 2 and i >= 0: name = path.join(w.dirname, w.list[i]) if name[-1:] == '/': if clicks == 2: G.windows.append(openlistwindow(name[:-1])) else: playfile(name) def closelistwindow(w): remove(G.windows, w) def remove(list, item): for i in range(len(list)): if list[i] == item: del list[i] break # Playing tools cache = {} def clearcache(): for x in cache.keys(): try: sts = posix.system('rm -f ' + cache[x]) if sts: print cmd print 'Exit status', sts except: print cmd print 'Exception?!' del cache[x] def playfile(name): if G.mode <> 'mac': tempname = name elif cache.has_key(name): tempname = cache[name] else: tempname = G.tempprefix + `rand.rand()` cmd = HOME_BIN_SGI + 'macsound2sgi' cmd = cmd + ' ' + commands.mkarg(name) cmd = cmd + ' >' + tempname if G.debug: print cmd sts = posix.system(cmd) if sts: print cmd print 'Exit status', sts stdwin.fleep() return cache[name] = tempname fp = open(tempname, 'r') try: hdr = sunaudio.gethdr(fp) except sunaudio.error, msg: hdr = () if hdr: data_size = hdr[0] data = fp.read(data_size) # XXX this doesn't work yet, need to convert from uLAW!!! del fp else: del fp data = readfile(tempname) if G.debug: print len(data), 'bytes read from', tempname if G.busy: G.busy = 0 dummy = audio.stop_playing() # # Completely reset the audio device audio.setrate(G.rate) audio.setduration(0) audio.setoutgain(G.gain) # if G.synchronous: audio.write(data) audio.setoutgain(0) else: try: audio.start_playing(data) G.busy = 1 except: stdwin.fleep() del data def readfile(filename): return readfp(open(filename, 'r')) def readfp(fp): data = '' while 1: buf = fp.read(102400) # Reads most samples in one fell swoop if not buf: return data data = data + buf main()