home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pyth_os2.zip / python-1.0.2 / Demo / stdwin / jukebox.py < prev    next >
Text File  |  1994-01-12  |  9KB  |  414 lines

  1. #! /usr/local/bin/python
  2.  
  3. # XXX This only works on SGIs running IRIX 4.0 or higher
  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 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
  15. # -s option.
  16. #
  17. # The control window displays a "stop button" that cancel the current
  18. # play request.
  19. #
  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
  23. # the Rate menu.
  24.  
  25. import commands
  26. import getopt
  27. import os
  28. from stat import *
  29. import rand
  30. import stdwin
  31. from stdwinevents import *
  32. import sys
  33. import tempfile
  34. import sndhdr
  35.  
  36. from WindowParent import WindowParent
  37. from Buttons import PushButton
  38.  
  39. # Pathnames
  40.  
  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
  44.  
  45.  
  46. # Global variables
  47.  
  48. class struct: pass        # Class to define featureless structures
  49.  
  50. G = struct()            # Holds writable global variables
  51.  
  52.  
  53. # Main program
  54.  
  55. def 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 (-dd event 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 = 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 G.debug:
  87.         for name in G.__dict__.keys():
  88.             print 'G.' + name, '=', `G.__dict__[name]`
  89.     #
  90.     if not args:
  91.         args = [DEF_DB]
  92.     #
  93.     G.cw = opencontrolwindow()
  94.     for dirname in args:
  95.         G.windows.append(openlistwindow(dirname))
  96.     #
  97.     #
  98.     try:
  99.         maineventloop()
  100.     finally:
  101.         clearcache()
  102.         killchild()
  103.  
  104. # Entries in Rate menu:
  105. rates = ['default', '7350', \
  106.     '8000', '11025', '16000', '22050', '32000', '41000', '48000']
  107.  
  108. def maineventloop():
  109.     mouse_events = WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP
  110.     while G.windows:
  111.         try:
  112.             type, w, detail = event = stdwin.getevent()
  113.         except KeyboardInterrupt:
  114.             killchild()
  115.             continue
  116.         if w == G.cw.win:
  117.             if type == WE_CLOSE:
  118.                 return
  119.             if type == WE_TIMER:
  120.                 checkchild()
  121.                 if G.busy:
  122.                     G.cw.win.settimer(1)
  123.             elif type == WE_MENU:
  124.                 menu, item = detail
  125.                 if menu is G.ratemenu:
  126.                     clearcache()
  127.                     if item == 0:
  128.                         G.rate = 0
  129.                     else:
  130.                         G.rate = eval(rates[item])
  131.                     for i in range(len(rates)):
  132.                         menu.check(i, (i == item))
  133.             else:
  134.                 G.cw.dispatch(event)
  135.         else:
  136.             if type == WE_DRAW:
  137.                 w.drawproc(w, detail)
  138.             elif type in mouse_events:
  139.                 w.mouse(w, type, detail)
  140.             elif type == WE_CLOSE:
  141.                 w.close(w)
  142.                 del w, event
  143.             else:
  144.                 if G.debug > 1: print type, w, detail
  145.  
  146. def checkchild():
  147.     if G.busy:
  148.         waitchild(1)
  149.  
  150. def killchild():
  151.     if G.busy:
  152.         os.kill(G.busy, 9)
  153.         waitchild(0)
  154.  
  155. def waitchild(options):
  156.     pid, sts = os.waitpid(G.busy, options)
  157.     if pid == G.busy:
  158.         G.busy = 0
  159.         G.stop.enable(0)
  160.  
  161.  
  162. # Control window -- to set gain and cancel play operations in progress
  163.  
  164. def opencontrolwindow():
  165.     stdwin.setdefscrollbars(0, 0)
  166.     cw = WindowParent().create('Jukebox', (0, 0))
  167.     #
  168.     stop = PushButton().definetext(cw, '        Stop        ')
  169.     stop.hook = stop_hook
  170.     stop.enable(0)
  171.     G.stop = stop
  172.     #
  173.     cw.realize()
  174.     #
  175.     G.ratemenu = cw.win.menucreate('Rate')
  176.     for r in rates:
  177.         G.ratemenu.additem(r)
  178.     if G.rate == 0:
  179.         G.ratemenu.check(0, 1)
  180.     else:
  181.         for i in len(range(rates)):
  182.             if rates[i] == `G.rate`:
  183.                 G.ratemenu.check(i, 1)
  184.     #
  185.     return cw
  186.  
  187. def stop_hook(self):
  188.     killchild()
  189.  
  190.  
  191. # List windows -- to display list of files and subdirectories
  192.  
  193. def openlistwindow(dirname):
  194.     list = os.listdir(dirname)
  195.     list.sort()
  196.     i = 0
  197.     while i < len(list):
  198.         if list[i][0] == '.':
  199.             del list[i]
  200.         else:
  201.             i = i+1
  202.     for i in range(len(list)):
  203.         fullname = os.path.join(dirname, list[i])
  204.         if os.path.isdir(fullname):
  205.             info = '/'
  206.         else:
  207.             try:
  208.                 size = os.stat(fullname)[ST_SIZE]
  209.                 info = `(size + 1023)/1024` + 'k'
  210.             except IOError:
  211.                 info = '???'
  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
  225.     w.dirname = dirname
  226.     w.list = list
  227.     w.selected = -1
  228.     return w
  229.  
  230. def maxwidth(list):
  231.     width = 1
  232.     for name, info in list:
  233.         w = stdwin.textwidth(name + '  ' + info)
  234.         if w > width: width = w
  235.     return width
  236.  
  237. def drawlistwindow(w, area):
  238. ##    (left, top), (right, bottom) = area
  239.     d = w.begindrawing()
  240.     d.erase((0, 0), (1000, 10000))
  241.     lh = d.lineheight()
  242.     h, v = 0, 0
  243.     for name, info in w.list:
  244.         if info == '/':
  245.             text = name + '/'
  246.         else:
  247.             text = name + '  ' + info
  248.         d.text((h, v), text)
  249.         v = v + lh
  250.     showselection(w, d)
  251.     d.close()
  252.  
  253. def hideselection(w, d):
  254.     if w.selected >= 0:
  255.         invertselection(w, d)
  256.  
  257. def showselection(w, d):
  258.     if w.selected >= 0:
  259.         invertselection(w, d)
  260.  
  261. def invertselection(w, d):
  262.     lh = d.lineheight()
  263.     h1, v1 = p1 = 0, w.selected*lh
  264.     h2, v2 = p2 = 1000, v1 + lh
  265.     d.invert(p1, p2)
  266.  
  267. def mouselistwindow(w, type, detail):
  268.     (h, v), clicks, button = detail[:3]
  269.     d = w.begindrawing()
  270.     lh = d.lineheight()
  271.     if 0 <= v < lh*len(w.list):
  272.         i = v / lh
  273.     else:
  274.         i = -1
  275.     if w.selected <> i:
  276.         hideselection(w, d)
  277.         w.selected = i
  278.         showselection(w, d)
  279.     d.close()
  280.     if type == WE_MOUSE_DOWN and clicks >= 2 and i >= 0:
  281.         setcursors('watch')
  282.         name, info = w.list[i]
  283.         fullname = os.path.join(w.dirname, name)
  284.         if info == '/':
  285.             if clicks == 2:
  286.                 G.windows.append(openlistwindow(fullname))
  287.         else:
  288.             playfile(fullname)
  289.         setcursors('cross')
  290.  
  291. def closelistwindow(w):
  292.     G.windows.remove(w)
  293.  
  294. def setcursors(cursor):
  295.     for w in G.windows:
  296.         w.setwincursor(cursor)
  297.     G.cw.win.setwincursor(cursor)
  298.  
  299.  
  300. # Playing tools
  301.  
  302. cache = {}
  303.  
  304. def clearcache():
  305.     for x in cache.keys():
  306.         cmd = 'rm -f ' + cache[x]
  307.         if G.debug: print cmd
  308.         sts = os.system(cmd)
  309.         if sts:
  310.             print cmd
  311.             print 'Exit status', sts
  312.         del cache[x]
  313.  
  314. validrates = (8000, 11025, 16000, 22050, 32000, 44100, 48000)
  315.  
  316. def playfile(filename):
  317.     killchild()
  318.     try:
  319.         tuple = sndhdr.what(filename)
  320.     except IOError, msg:
  321.         print 'Can\'t open', filename, msg
  322.         stdwin.fleep()
  323.         return
  324.     raw = 0
  325.     if tuple:
  326.         mode, rate = tuple[:2]
  327.         if rate == 0:
  328.             rate = G.rate
  329.             if rate == 0:
  330.                 rate = 8000
  331.     else:
  332.         mode = G.mode
  333.         rate = G.rate
  334.     if G.debug: print 'mode =', mode, 'rate =', rate
  335.     if mode in ('au', 'aiff', 'wav', 'aifc', 'ul', 'ub', 'sb') and \
  336.           rate in validrates:
  337.         tempname = filename
  338.         if mode in ('ul', 'ub', 'sb'):
  339.             raw = 1
  340.     elif cache.has_key(filename):
  341.         tempname = cache[filename]
  342.     else:
  343.         tempname = G.tempprefix + `rand.rand()` + '.aiff'
  344.         cmd = SOX
  345.         if G.debug:
  346.             cmd = cmd + ' -V'
  347.         if mode <> '':
  348.             cmd = cmd + ' -t ' + mode
  349.         cmd = cmd + ' ' + commands.mkarg(filename)
  350.         cmd = cmd + ' -t aiff'
  351.         if rate not in validrates:
  352.             rate = 32000
  353.         if rate:
  354.             cmd = cmd + ' -r ' + `rate`
  355.         cmd = cmd + ' ' + tempname
  356.         if G.debug: print cmd
  357.         sts = os.system(cmd)
  358.         if sts:
  359.             print cmd
  360.             print 'Exit status', sts
  361.             stdwin.fleep()
  362.             try:
  363.                 os.unlink(tempname)
  364.             except:
  365.                 pass
  366.             return
  367.         cache[filename] = tempname
  368.     if raw:
  369.         pid = sfplayraw(tempname, tuple)
  370.     else:
  371.         pid = sfplay(tempname, [])
  372.     if G.synchronous:
  373.         sts = os.wait(pid, 0)
  374.     else:
  375.         G.busy = pid
  376.         G.stop.enable(1)
  377.         G.cw.win.settimer(1)
  378.  
  379. def sfplayraw(filename, tuple):
  380.     args = ['-i']
  381.     type, rate, channels, frames, bits = tuple
  382.     if type == 'ul':
  383.         args.append('mulaw')
  384.     elif type == 'ub':
  385.         args = args + ['integer', '8', 'unsigned']
  386.     elif type == 'sb':
  387.         args = args + ['integer', '8', '2scomp']
  388.     else:
  389.         print 'sfplayraw: warning: unknown type in', tuple
  390.     if channels > 1:
  391.         args = args + ['channels', `channels`]
  392.     if not rate:
  393.         rate = G.rate
  394.     if rate:
  395.         args = args + ['rate', `rate`]
  396.     args.append('end')
  397.     return sfplay(filename, args)
  398.  
  399. def sfplay(filename, args):
  400.     if G.debug:
  401.         args = ['-p'] + args
  402.     args = [SFPLAY, '-r'] + args + [filename]
  403.     if G.debug: print 'sfplay:', args
  404.     pid = os.fork()
  405.     if pid == 0:
  406.         # Child
  407.         os.execv(SFPLAY, args)
  408.         # NOTREACHED
  409.     else:
  410.         # Parent
  411.         return pid
  412.  
  413. main()
  414.