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