home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pyos2bin.zip / Lib / wave.py < prev    next >
Text File  |  1996-08-20  |  17KB  |  560 lines

  1. # Stuff to parse WAVE files.
  2. #
  3. # Usage.
  4. #
  5. # Reading WAVE files:
  6. #    f = wave.open(file, 'r')
  7. # where file is either the name of a file or an open file pointer.
  8. # The open file pointer must have methods read(), seek(), and close().
  9. # When the setpos() and rewind() methods are not used, the seek()
  10. # method is not  necessary.
  11. #
  12. # This returns an instance of a class with the following public methods:
  13. #    getnchannels()    -- returns number of audio channels (1 for
  14. #               mono, 2 for stereo)
  15. #    getsampwidth()    -- returns sample width in bytes
  16. #    getframerate()    -- returns sampling frequency
  17. #    getnframes()    -- returns number of audio frames
  18. #    getcomptype()    -- returns compression type ('NONE' for AIFF files)
  19. #    getcompname()    -- returns human-readable version of
  20. #               compression type ('not compressed' for AIFF files)
  21. #    getparams()    -- returns a tuple consisting of all of the
  22. #               above in the above order
  23. #    getmarkers()    -- returns None (for compatibility with the
  24. #               aifc module)
  25. #    getmark(id)    -- raises an error since the mark does not
  26. #               exist (for compatibility with the aifc module)
  27. #    readframes(n)    -- returns at most n frames of audio
  28. #    rewind()    -- rewind to the beginning of the audio stream
  29. #    setpos(pos)    -- seek to the specified position
  30. #    tell()        -- return the current position
  31. #    close()        -- close the instance (make it unusable)
  32. # The position returned by tell() and the position given to setpos()
  33. # are compatible and have nothing to do with the actual postion in the
  34. # file.
  35. # The close() method is called automatically when the class instance
  36. # is destroyed.
  37. #
  38. # Writing WAVE files:
  39. #    f = wave.open(file, 'w')
  40. # where file is either the name of a file or an open file pointer.
  41. # The open file pointer must have methods write(), tell(), seek(), and
  42. # close().
  43. #
  44. # This returns an instance of a class with the following public methods:
  45. #    setnchannels(n)    -- set the number of channels
  46. #    setsampwidth(n)    -- set the sample width
  47. #    setframerate(n)    -- set the frame rate
  48. #    setnframes(n)    -- set the number of frames
  49. #    setcomptype(type, name)
  50. #            -- set the compression type and the
  51. #               human-readable compression type
  52. #    setparams(nchannels, sampwidth, framerate, nframes, comptype, compname)
  53. #            -- set all parameters at once
  54. #    tell()        -- return current position in output file
  55. #    writeframesraw(data)
  56. #            -- write audio frames without pathing up the
  57. #               file header
  58. #    writeframes(data)
  59. #            -- write audio frames and patch up the file header
  60. #    close()        -- patch up the file header and close the
  61. #               output file
  62. # You should set the parameters before the first writeframesraw or
  63. # writeframes.  The total number of frames does not need to be set,
  64. # but when it is set to the correct value, the header does not have to
  65. # be patched up.
  66. # It is best to first set all parameters, perhaps possibly the
  67. # compression type, and then write audio frames using writeframesraw.
  68. # When all frames have been written, either call writeframes('') or
  69. # close() to patch up the sizes in the header.
  70. # The close() method is called automatically when the class instance
  71. # is destroyed.
  72.  
  73. import __builtin__
  74.  
  75. Error = 'wave.Error'
  76.  
  77. WAVE_FORMAT_PCM = 0x0001
  78.  
  79. _array_fmts = None, 'b', 'h', None, 'l'
  80.  
  81. def _read_long(file):
  82.     x = 0L
  83.     for i in range(4):
  84.         byte = file.read(1)
  85.         if byte == '':
  86.             raise EOFError
  87.         x = x + (ord(byte) << (8 * i))
  88.     if x >= 0x80000000L:
  89.         x = x - 0x100000000L
  90.     return int(x)
  91.  
  92. def _read_ulong(file):
  93.     x = 0L
  94.     for i in range(4):
  95.         byte = file.read(1)
  96.         if byte == '':
  97.             raise EOFError
  98.         x = x + (ord(byte) << (8 * i))
  99.     return x
  100.  
  101. def _read_short(file):
  102.     x = 0
  103.     for i in range(2):
  104.         byte = file.read(1)
  105.         if byte == '':
  106.             raise EOFError
  107.         x = x + (ord(byte) << (8 * i))
  108.     if x >= 0x8000:
  109.         x = x - 0x10000
  110.     return x
  111.  
  112. def _write_short(f, x):
  113.     d, m = divmod(x, 256)
  114.     f.write(chr(m))
  115.     f.write(chr(d))
  116.  
  117. def _write_long(f, x):
  118.     if x < 0:
  119.         x = x + 0x100000000L
  120.     for i in range(4):
  121.         d, m = divmod(x, 256)
  122.         f.write(chr(int(m)))
  123.         x = d
  124.  
  125. class Chunk:
  126.     def __init__(self, file):
  127.         self.file = file
  128.         self.chunkname = self.file.read(4)
  129.         if len(self.chunkname) < 4:
  130.             raise EOFError
  131.         self.chunksize = _read_long(self.file)
  132.         self.size_read = 0
  133.         self.offset = self.file.tell()
  134.  
  135.     def rewind(self):
  136.         self.file.seek(self.offset, 0)
  137.         self.size_read = 0
  138.  
  139.     def setpos(self, pos):
  140.         if pos < 0 or pos > self.chunksize:
  141.             raise RuntimeError
  142.         self.file.seek(self.offset + pos, 0)
  143.         self.size_read = pos
  144.         
  145.     def read(self, length):
  146.         if self.size_read >= self.chunksize:
  147.             return ''
  148.         if length > self.chunksize - self.size_read:
  149.              length = self.chunksize - self.size_read
  150.         data = self.file.read(length)
  151.         self.size_read = self.size_read + len(data)
  152.         return data
  153.  
  154.     def skip(self):
  155.         try:
  156.             self.file.seek(self.chunksize - self.size_read, 1)
  157.         except RuntimeError:
  158.             while self.size_read < self.chunksize:
  159.                 dummy = self.read(8192)
  160.                 if not dummy:
  161.                     raise EOFError
  162.  
  163. class Wave_read:
  164.     # Variables used in this class:
  165.     #
  166.     # These variables are available to the user though appropriate
  167.     # methods of this class:
  168.     # _file -- the open file with methods read(), close(), and seek()
  169.     #        set through the __init__() method
  170.     # _nchannels -- the number of audio channels
  171.     #        available through the getnchannels() method
  172.     # _nframes -- the number of audio frames
  173.     #        available through the getnframes() method
  174.     # _sampwidth -- the number of bytes per audio sample
  175.     #        available through the getsampwidth() method
  176.     # _framerate -- the sampling frequency
  177.     #        available through the getframerate() method
  178.     # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
  179.     #        available through the getcomptype() method
  180.     # _compname -- the human-readable AIFF-C compression type
  181.     #        available through the getcomptype() method
  182.     # _soundpos -- the position in the audio stream
  183.     #        available through the tell() method, set through the
  184.     #        setpos() method
  185.     #
  186.     # These variables are used internally only:
  187.     # _fmt_chunk_read -- 1 iff the FMT chunk has been read
  188.     # _data_seek_needed -- 1 iff positioned correctly in audio
  189.     #        file for readframes()
  190.     # _data_chunk -- instantiation of a chunk class for the DATA chunk
  191.     # _framesize -- size of one frame in the file
  192.  
  193. ##     access _file, _nchannels, _nframes, _sampwidth, _framerate, \
  194. ##           _comptype, _compname, _soundpos, \
  195. ##           _fmt_chunk_read, _data_seek_needed, \
  196. ##           _data_chunk, _framesize: private
  197.  
  198.     def initfp(self, file):
  199.         self._file = file
  200.         self._convert = None
  201.         self._soundpos = 0
  202.         form = self._file.read(4)
  203.         if form != 'RIFF':
  204.             raise Error, 'file does not start with RIFF id'
  205.         formlength = _read_long(self._file)
  206.         if formlength <= 0:
  207.             raise Error, 'invalid FORM chunk data size'
  208.         formdata = self._file.read(4)
  209.         formlength = formlength - 4
  210.         if formdata != 'WAVE':
  211.             raise Error, 'not a WAVE file'
  212.         self._fmt_chunk_read = 0
  213.         while formlength > 0:
  214.             self._data_seek_needed = 1
  215.             chunk = Chunk(self._file)
  216.             if chunk.chunkname == 'fmt ':
  217.                 self._read_fmt_chunk(chunk)
  218.                 self._fmt_chunk_read = 1
  219.             elif chunk.chunkname == 'data':
  220.                 if not self._fmt_chunk_read:
  221.                     raise Error, 'data chunk before fmt chunk'
  222.                 self._data_chunk = chunk
  223.                 self._nframes = chunk.chunksize / self._framesize
  224.                 self._data_seek_needed = 0
  225.             formlength = formlength - 8 - chunk.chunksize
  226.             if formlength > 0:
  227.                 chunk.skip()
  228.         if not self._fmt_chunk_read or not self._data_chunk:
  229.             raise Error, 'fmt chunk and/or data chunk missing'
  230.  
  231.     def __init__(self, f):
  232.         if type(f) == type(''):
  233.             f = __builtin__.open(f, 'r')
  234.         # else, assume it is an open file object already
  235.         self.initfp(f)
  236.  
  237.     def __del__(self):
  238.         if self._file:
  239.             self.close()
  240.  
  241.     #
  242.     # User visible methods.
  243.     #
  244.     def getfp(self):
  245.         return self._file
  246.  
  247.     def rewind(self):
  248.         self._data_seek_needed = 1
  249.         self._soundpos = 0
  250.  
  251.     def close(self):
  252.         self._file = None
  253.  
  254.     def tell(self):
  255.         return self._soundpos
  256.  
  257.     def getnchannels(self):
  258.         return self._nchannels
  259.  
  260.     def getnframes(self):
  261.         return self._nframes
  262.  
  263.     def getsampwidth(self):
  264.         return self._sampwidth
  265.  
  266.     def getframerate(self):
  267.         return self._framerate
  268.  
  269.     def getcomptype(self):
  270.         return self._comptype
  271.  
  272.     def getcompname(self):
  273.         return self._compname
  274.  
  275.     def getparams(self):
  276.         return self.getnchannels(), self.getsampwidth(), \
  277.               self.getframerate(), self.getnframes(), \
  278.               self.getcomptype(), self.getcompname()
  279.  
  280.     def getmarkers(self):
  281.         return None
  282.  
  283.     def getmark(self, id):
  284.         raise Error, 'no marks'
  285.  
  286.     def setpos(self, pos):
  287.         if pos < 0 or pos > self._nframes:
  288.             raise Error, 'position not in range'
  289.         self._soundpos = pos
  290.         self._data_seek_needed = 1
  291.  
  292.     def readframes(self, nframes):
  293.         if self._data_seek_needed:
  294.             self._data_chunk.rewind()
  295.             pos = self._soundpos * self._framesize
  296.             if pos:
  297.                 self._data_chunk.setpos(pos)
  298.             self._data_seek_needed = 0
  299.         if nframes == 0:
  300.             return ''
  301.         if self._sampwidth > 1:
  302.             # unfortunately the fromfile() method does not take
  303.             # something that only looks like a file object, so
  304.             # we have to reach into the innards of the chunk object
  305.             import array
  306.             data = array.array(_array_fmts[self._sampwidth])
  307.             nitems = nframes * self._nchannels
  308.             if nitems * self._sampwidth > self._data_chunk.chunksize - self._data_chunk.size_read:
  309.                 nitems = (self._data_chunk.chunksize - self._data_chunk.size_read) / self._sampwidth
  310.             data.fromfile(self._data_chunk.file, nitems)
  311.             self._data_chunk.size_read = self._data_chunk.size_read + nitems * self._sampwidth
  312.             data.byteswap()
  313.             data = data.tostring()
  314.         else:
  315.             data = self._data_chunk.read(nframes * self._framesize)
  316.         if self._convert and data:
  317.             data = self._convert(data)
  318.         self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
  319.         return data
  320.  
  321.     #
  322.     # Internal methods.
  323.     #
  324. ##     access *: private
  325.  
  326.     def _read_fmt_chunk(self, chunk):
  327.         wFormatTag = _read_short(chunk)
  328.         self._nchannels = _read_short(chunk)
  329.         self._framerate = _read_long(chunk)
  330.         dwAvgBytesPerSec = _read_long(chunk)
  331.         wBlockAlign = _read_short(chunk)
  332.         if wFormatTag == WAVE_FORMAT_PCM:
  333.             self._sampwidth = (_read_short(chunk) + 7) / 8
  334.         else:
  335.             raise Error, 'unknown format'
  336.         self._framesize = self._nchannels * self._sampwidth
  337.         self._comptype = 'NONE'
  338.         self._compname = 'not compressed'
  339.  
  340. class Wave_write:
  341.     # Variables used in this class:
  342.     #
  343.     # These variables are user settable through appropriate methods
  344.     # of this class:
  345.     # _file -- the open file with methods write(), close(), tell(), seek()
  346.     #        set through the __init__() method
  347.     # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
  348.     #        set through the setcomptype() or setparams() method
  349.     # _compname -- the human-readable AIFF-C compression type
  350.     #        set through the setcomptype() or setparams() method
  351.     # _nchannels -- the number of audio channels
  352.     #        set through the setnchannels() or setparams() method
  353.     # _sampwidth -- the number of bytes per audio sample
  354.     #        set through the setsampwidth() or setparams() method
  355.     # _framerate -- the sampling frequency
  356.     #        set through the setframerate() or setparams() method
  357.     # _nframes -- the number of audio frames written to the header
  358.     #        set through the setnframes() or setparams() method
  359.     #
  360.     # These variables are used internally only:
  361.     # _datalength -- the size of the audio samples written to the header
  362.     # _nframeswritten -- the number of frames actually written
  363.     # _datawritten -- the size of the audio samples actually written
  364.  
  365. ##     access _file, _comptype, _compname, _nchannels, _sampwidth, \
  366. ##           _framerate, _nframes, _nframeswritten, \
  367. ##           _datalength, _datawritten: private
  368.  
  369.     def __init__(self, f):
  370.         if type(f) == type(''):
  371.             f = __builtin__.open(f, 'w')
  372.         self.initfp(f)
  373.  
  374.     def initfp(self, file):
  375.         self._file = file
  376.         self._convert = None
  377.         self._nchannels = 0
  378.         self._sampwidth = 0
  379.         self._framerate = 0
  380.         self._nframes = 0
  381.         self._nframeswritten = 0
  382.         self._datawritten = 0
  383.         self._datalength = 0
  384.  
  385.     def __del__(self):
  386.         if self._file:
  387.             self.close()
  388.  
  389.     #
  390.     # User visible methods.
  391.     #
  392.     def setnchannels(self, nchannels):
  393.         if self._datawritten:
  394.             raise Error, 'cannot change parameters after starting to write'
  395.         if nchannels < 1:
  396.             raise Error, 'bad # of channels'
  397.         self._nchannels = nchannels
  398.  
  399.     def getnchannels(self):
  400.         if not self._nchannels:
  401.             raise Error, 'number of channels not set'
  402.         return self._nchannels
  403.  
  404.     def setsampwidth(self, sampwidth):
  405.         if self._datawritten:
  406.             raise Error, 'cannot change parameters after starting to write'
  407.         if sampwidth < 1 or sampwidth > 4:
  408.             raise Error, 'bad sample width'
  409.         self._sampwidth = sampwidth
  410.  
  411.     def getsampwidth(self):
  412.         if not self._sampwidth:
  413.             raise Error, 'sample width not set'
  414.         return self._sampwidth
  415.  
  416.     def setframerate(self, framerate):
  417.         if self._datawritten:
  418.             raise Error, 'cannot change parameters after starting to write'
  419.         if framerate <= 0:
  420.             raise Error, 'bad frame rate'
  421.         self._framerate = framerate
  422.  
  423.     def getframerate(self):
  424.         if not self._framerate:
  425.             raise Error, 'frame rate not set'
  426.         return self._framerate
  427.  
  428.     def setnframes(self, nframes):
  429.         if self._datawritten:
  430.             raise Error, 'cannot change parameters after starting to write'
  431.         self._nframes = nframes
  432.  
  433.     def getnframes(self):
  434.         return self._nframeswritten
  435.  
  436.     def setcomptype(self, comptype, compname):
  437.         if self._datawritten:
  438.             raise Error, 'cannot change parameters after starting to write'
  439.         if comptype not in ('NONE',):
  440.             raise Error, 'unsupported compression type'
  441.         self._comptype = comptype
  442.         self._compname = compname
  443.  
  444.     def getcomptype(self):
  445.         return self._comptype
  446.  
  447.     def getcompname(self):
  448.         return self._compname
  449.  
  450.     def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
  451.         if self._datawritten:
  452.             raise Error, 'cannot change parameters after starting to write'
  453.         self.setnchannels(nchannels)
  454.         self.setsampwidth(sampwidth)
  455.         self.setframerate(framerate)
  456.         self.setnframes(nframes)
  457.         self.setcomptype(comptype, compname)
  458.  
  459.     def getparams(self):
  460.         if not self._nchannels or not self._sampwidth or not self._framerate:
  461.             raise Error, 'not all parameters set'
  462.         return self._nchannels, self._sampwidth, self._framerate, \
  463.               self._nframes, self._comptype, self._compname
  464.  
  465.     def setmark(self, id, pos, name):
  466.         raise Error, 'setmark() not supported'
  467.  
  468.     def getmark(self, id):
  469.         raise Error, 'no marks'
  470.  
  471.     def getmarkers(self):
  472.         return None
  473.                 
  474.     def tell(self):
  475.         return self._nframeswritten
  476.  
  477.     def writeframesraw(self, data):
  478.         self._ensure_header_written(len(data))
  479.         nframes = len(data) / (self._sampwidth * self._nchannels)
  480.         if self._convert:
  481.             data = self._convert(data)
  482.         if self._sampwidth > 1:
  483.             import array
  484.             data = array.array(_array_fmts[self._sampwidth], data)
  485.             data.byteswap()
  486.             data.tofile(self._file)
  487.             self._datawritten = self._datawritten + len(data) * self._sampwidth
  488.         else:
  489.             self._file.write(data)
  490.             self._datawritten = self._datawritten + len(data)
  491.         self._nframeswritten = self._nframeswritten + nframes
  492.  
  493.     def writeframes(self, data):
  494.         self.writeframesraw(data)
  495.         if self._datalength != self._datawritten:
  496.             self._patchheader()
  497.  
  498.     def close(self):
  499.         self._ensure_header_written(0)
  500.         if self._datalength != self._datawritten:
  501.             self._patchheader()
  502.         self._file.flush()
  503.         self._file = None
  504.  
  505.     #
  506.     # Internal methods.
  507.     #
  508. ##     access *: private
  509.  
  510.     def _ensure_header_written(self, datasize):
  511.         if not self._datawritten:
  512.             if not self._nchannels:
  513.                 raise Error, '# channels not specified'
  514.             if not self._sampwidth:
  515.                 raise Error, 'sample width not specified'
  516.             if not self._framerate:
  517.                 raise Error, 'sampling rate not specified'
  518.             self._write_header(datasize)
  519.  
  520.     def _write_header(self, initlength):
  521.         self._file.write('RIFF')
  522.         if not self._nframes:
  523.             self._nframes = initlength / (self._nchannels * self._sampwidth)
  524.         self._datalength = self._nframes * self._nchannels * self._sampwidth
  525.         self._form_length_pos = self._file.tell()
  526.         _write_long(self._file, 36 + self._datalength)
  527.         self._file.write('WAVE')
  528.         self._file.write('fmt ')
  529.         _write_long(self._file, 16)
  530.         _write_short(self._file, WAVE_FORMAT_PCM)
  531.         _write_short(self._file, self._nchannels)
  532.         _write_long(self._file, self._framerate)
  533.         _write_long(self._file, self._nchannels * self._framerate * self._sampwidth)
  534.         _write_short(self._file, self._nchannels * self._sampwidth)
  535.         _write_short(self._file, self._sampwidth * 8)
  536.         self._file.write('data')
  537.         self._data_length_pos = self._file.tell()
  538.         _write_long(self._file, self._datalength)
  539.  
  540.     def _patchheader(self):
  541.         if self._datawritten == self._datalength:
  542.             return
  543.         curpos = self._file.tell()
  544.         self._file.seek(self._form_length_pos, 0)
  545.         _write_long(36 + self._datawritten)
  546.         self._file.seek(self._data_length_pos, 0)
  547.         _write_long(self._file, self._datawritten)
  548.         self._file.seek(curpos, 0)
  549.         self._datalength = self._datawritten
  550.  
  551. def open(f, mode):
  552.     if mode == 'r':
  553.         return Wave_read(f)
  554.     elif mode == 'w':
  555.         return Wave_write(f)
  556.     else:
  557.         raise Error, "mode must be 'r' or 'w'"
  558.  
  559. openfp = open # B/W compatibility
  560.