]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
aifc.py: added missing tell() method in AIFC write class;
authorSjoerd Mullender <sjoerd@acm.org>
Mon, 13 Dec 1993 11:42:39 +0000 (11:42 +0000)
committerSjoerd Mullender <sjoerd@acm.org>
Mon, 13 Dec 1993 11:42:39 +0000 (11:42 +0000)
 use audioop module as backup for cl module when reading or
 writing u-law compressed files.
sunau.py: interface with the same methods as aifc for Sun and NeXT
  audio files

Lib/aifc.py
Lib/sunau.py [new file with mode: 0644]

index 2d897be80014b2b84509f24ddc0e9110c9d84e20..ee1f8f05c9683f0785bb9a94cda79ae4e1b6e462 100644 (file)
@@ -40,7 +40,7 @@
 #              <size of the samples> (2 bytes)
 #              <sampling frequency> (10 bytes, IEEE 80-bit extended
 #                      floating point)
-#              if AIFF-C files only:
+#              in AIFF-C files only:
 #              <compression type> (4 bytes)
 #              <human-readable version of compression type> ("pstring")
 #      SSND
@@ -58,9 +58,9 @@
 # or
 #      f = aifc.openfp(filep, 'r')
 # where file is the name of a file and filep is an open file pointer.
-# The open file pointer must have methods  read(), seek(), and
-# close().  In some types of audio files, if the setpos() method is
-# not used, the seek() method is not necessary.
+# The open file pointer must have methods read(), seek(), and close().
+# In some types of audio files, if the setpos() method is not used,
+# the seek() method is not necessary.
 #
 # This returns an instance of a class with the following public methods:
 #      getnchannels()  -- returns number of audio channels (1 for
@@ -85,6 +85,8 @@
 # The position returned by tell(), the position given to setpos() and
 # the position of marks are all compatible and have nothing to do with
 # the actual postion in the file.
+# The close() method is called automatically when the class instance
+# is destroyed.
 #
 # Writing AIFF files:
 #      f = aifc.open(file, 'w')
 # but when it is set to the correct value, the header does not have to
 # be patched up.
 # It is best to first set all parameters, perhaps possibly the
-# compression type, and the write audio frames using writeframesraw.
+# compression type, and then write audio frames using writeframesraw.
 # When all frames have been written, either call writeframes('') or
 # close() to patch up the sizes in the header.
 # Marks can be added anytime.  If there are any marks, ypu must call
 # close() after all frames have been written.
+# The close() method is called automatically when the class instance
+# is destroyed.
 #
 # When a file is opened with the extension '.aiff', an AIFF file is
 # written, otherwise an AIFF-C file is written.  This default can be
@@ -347,7 +351,7 @@ class Aifc_read:
        #               methods
        # _soundpos -- the position in the audio stream
        #               available through the tell() method, set through the
-       #               tell() method
+       #               setpos() method
        #
        # These variables are used internally only:
        # _version -- the AIFF-C version number
@@ -362,6 +366,7 @@ class Aifc_read:
                self._file = file
                self._version = 0
                self._decomp = None
+               self._convert = None
                self._markers = []
                self._soundpos = 0
                form = self._file.read(4)
@@ -433,6 +438,10 @@ class Aifc_read:
        def init(self, filename):
                return self.initfp(builtin.open(filename, 'r'))
 
+       def __del__(self):
+               if self._file:
+                       self.close()
+
        #
        # User visible methods.
        #
@@ -475,8 +484,9 @@ class Aifc_read:
 ##             return self._version
 
        def getparams(self):
-               return self._nchannels, self._sampwidth, self._framerate, \
-                         self._nframes, self._comptype, self._compname
+               return self.getnchannels(), self.getsampwidth(), \
+                         self.getframerate(), self.getnframes(), \
+                         self.getcomptype(), self.getcompname()
 
        def getmarkers(self):
                if len(self._markers) == 0:
@@ -506,16 +516,24 @@ class Aifc_read:
                if nframes == 0:
                        return ''
                data = self._ssnd_chunk.read(nframes * self._framesize)
-               if self._decomp and data:
-                       dummy = self._decomp.SetParam(CL.FRAME_BUFFER_SIZE, \
-                                 len(data) * 2)
-                       data = self._decomp.Decompress(len(data) / self._nchannels, data)
+               if self._convert and data:
+                       data = self._convert(data)
                self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
                return data
 
        #
        # Internal methods.
        #
+       def _decomp_data(self, data):
+               dummy = self._decomp.SetParam(CL.FRAME_BUFFER_SIZE,
+                                             len(data) * 2)
+               return self._decomp.Decompress(len(data) / self._nchannels,
+                                              data)
+
+       def _ulaw2lin(self, data):
+               import audioop
+               return audioop.ulaw2lin(data, 2)
+
        def _read_comm_chunk(self, chunk):
                nchannels = _read_short(chunk)
                self._nchannels = _convert1(nchannels, _nchannelslist)
@@ -547,6 +565,14 @@ class Aifc_read:
                                try:
                                        import cl, CL
                                except ImportError:
+                                       if self._comptype == 'ULAW':
+                                               try:
+                                                       import audioop
+                                                       self._convert = self._ulaw2lin
+                                                       self._framesize = self._framesize / 2
+                                                       return
+                                               except ImportError:
+                                                       pass
                                        raise Error, 'cannot read compressed AIFF-C files'
                                if self._comptype == 'ULAW':
                                        scheme = CL.G711_ULAW
@@ -557,6 +583,7 @@ class Aifc_read:
                                else:
                                        raise Error, 'unsupported compression type'
                                self._decomp = cl.OpenDecompressor(scheme)
+                               self._convert = self._decomp_data
                else:
                        self._comptype = 'NONE'
                        self._compname = 'not compressed'
@@ -622,6 +649,7 @@ class Aifc_write:
                self._comptype = 'NONE'
                self._compname = 'not compressed'
                self._comp = None
+               self._convert = None
                self._nchannels = 0
                self._sampwidth = 0
                self._framerate = 0
@@ -634,6 +662,10 @@ class Aifc_write:
                self._aifc = 1          # AIFF-C is default
                return self
 
+       def __del__(self):
+               if self._file:
+                       self.close()
+
        #
        # User visible methods.
        #
@@ -752,15 +784,14 @@ class Aifc_write:
                        return None
                return self._markers
                                
+       def tell(self):
+               return self._nframeswritten
+
        def writeframesraw(self, data):
                self._ensure_header_written(len(data))
                nframes = len(data) / (self._sampwidth * self._nchannels)
-               if self._comp:
-                       dummy = self._comp.SetParam(CL.FRAME_BUFFER_SIZE, \
-                                 len(data))
-                       dummy = self._comp.SetParam(CL.COMPRESSED_BUFFER_SIZE,\
-                                 len(data))
-                       data = self._comp.Compress(nframes, data)
+               if self._convert:
+                       data = self._convert(data)
                self._file.write(data)
                self._nframeswritten = self._nframeswritten + nframes
                self._datawritten = self._datawritten + len(data)
@@ -791,6 +822,15 @@ class Aifc_write:
        #
        # Internal methods.
        #
+       def _comp_data(self, data):
+               dum = self._comp.SetParam(CL.FRAME_BUFFER_SIZE, len(data))
+               dum = self._comp.SetParam(CL.COMPRESSED_BUFFER_SIZE, len(data))
+               return self._comp.Compress(nframes, data)
+
+       def _lin2ulaw(self, data):
+               import audioop
+               return audioop.lin2ulaw(data, 2)
+
        def _ensure_header_written(self, datasize):
                if not self._nframeswritten:
                        if self._comptype in ('ULAW', 'ALAW'):
@@ -806,37 +846,48 @@ class Aifc_write:
                                raise Error, 'sampling rate not specified'
                        self._write_header(datasize)
 
+       def _init_compression(self):
+               try:
+                       import cl, CL
+               except ImportError:
+                       if self._comptype == 'ULAW':
+                               try:
+                                       import audioop
+                                       self._convert = self._lin2ulaw
+                                       return
+                               except ImportError:
+                                       pass
+                       raise Error, 'cannot write compressed AIFF-C files'
+               if self._comptype == 'ULAW':
+                       scheme = CL.G711_ULAW
+               elif self._comptype == 'ALAW':
+                       scheme = CL.G711_ALAW
+               else:
+                       raise Error, 'unsupported compression type'
+               self._comp = cl.OpenCompressor(scheme)
+               params = [CL.ORIGINAL_FORMAT, 0, \
+                         CL.BITS_PER_COMPONENT, 0, \
+                         CL.FRAME_RATE, self._framerate, \
+                         CL.FRAME_BUFFER_SIZE, 100, \
+                         CL.COMPRESSED_BUFFER_SIZE, 100]
+               if self._nchannels == AL.MONO:
+                       params[1] = CL.MONO
+               else:
+                       params[1] = CL.STEREO_INTERLEAVED
+               if self._sampwidth == AL.SAMPLE_8:
+                       params[3] = 8
+               elif self._sampwidth == AL.SAMPLE_16:
+                       params[3] = 16
+               else:
+                       params[3] = 24
+               self._comp.SetParams(params)
+               # the compressor produces a header which we ignore
+               dummy = self._comp.Compress(0, '')
+               self._convert = self._comp_data
+
        def _write_header(self, initlength):
                if self._aifc and self._comptype != 'NONE':
-                       try:
-                               import cl, CL
-                       except ImportError:
-                               raise Error, 'cannot write compressed AIFF-C files'
-                       if self._comptype == 'ULAW':
-                               scheme = CL.G711_ULAW
-                       elif self._comptype == 'ALAW':
-                               scheme = CL.G711_ALAW
-                       else:
-                               raise Error, 'unsupported compression type'
-                       self._comp = cl.OpenCompressor(scheme)
-                       params = [CL.ORIGINAL_FORMAT, 0, \
-                                 CL.BITS_PER_COMPONENT, 0, \
-                                 CL.FRAME_RATE, self._framerate, \
-                                 CL.FRAME_BUFFER_SIZE, 100, \
-                                 CL.COMPRESSED_BUFFER_SIZE, 100]
-                       if self._nchannels == AL.MONO:
-                               params[1] = CL.MONO
-                       else:
-                               params[1] = CL.STEREO_INTERLEAVED
-                       if self._sampwidth == AL.SAMPLE_8:
-                               params[3] = 8
-                       elif self._sampwidth == AL.SAMPLE_16:
-                               params[3] = 16
-                       else:
-                               params[3] = 24
-                       self._comp.SetParams(params)
-                       # the compressor produces a header which we ignore
-                       dummy = self._comp.Compress(0, '')
+                       self._init_compression()
                self._file.write('FORM')
                if not self._nframes:
                        self._nframes = initlength / (self._nchannels * self._sampwidth)
diff --git a/Lib/sunau.py b/Lib/sunau.py
new file mode 100644 (file)
index 0000000..1acebd0
--- /dev/null
@@ -0,0 +1,471 @@
+# Stuff to parse Sun and NeXT audio files.
+#
+# An audio consists of a header followed by the data.  The structure
+# of the header is as follows.
+#
+#      +---------------+
+#      | magic word    |
+#      +---------------+
+#      | header size   |
+#      +---------------+
+#      | data size     |
+#      +---------------+
+#      | encoding      |
+#      +---------------+
+#      | sample rate   |
+#      +---------------+
+#      | # of channels |
+#      +---------------+
+#      | info          |
+#      |               |
+#      +---------------+
+#
+# The magic word consists of the 4 characters '.snd'.  Apart from the
+# info field, all header fields are 4 bytes in size.  They are all
+# 32-bit unsigned integers encoded in big-endian byte order.
+#
+# The header size really gives the start of the data.
+# The data size is the physical size of the data.  From the other
+# parameter the number of frames can be calculated.
+# The encoding gives the way in which audio samples are encoded.
+# Possible values are listed below.
+# The info field currently consists of an ASCII string giving a
+# human-readable description of the audio file.  The info field is
+# padded with NUL bytes to the header size.
+#
+# Usage.
+#
+# Reading audio files:
+#      f = au.open(file, 'r')
+# or
+#      f = au.openfp(filep, 'r')
+# where file is the name of a file and filep is an open file pointer.
+# The open file pointer must have methods read(), seek(), and close().
+# When the setpos() and rewind() methods are not used, the seek()
+# method is not  necessary.
+#
+# This returns an instance of a class with the following public methods:
+#      getnchannels()  -- returns number of audio channels (1 for
+#                         mono, 2 for stereo)
+#      getsampwidth()  -- returns sample width in bytes
+#      getframerate()  -- returns sampling frequency
+#      getnframes()    -- returns number of audio frames
+#      getcomptype()   -- returns compression type ('NONE' for AIFF files)
+#      getcompname()   -- returns human-readable version of
+#                         compression type ('not compressed' for AIFF files)
+#      getparams()     -- returns a tuple consisting of all of the
+#                         above in the above order
+#      getmarkers()    -- returns None (for compatibility with the
+#                         aifc module)
+#      getmark(id)     -- raises an error since the mark does not
+#                         exist (for compatibility with the aifc module)
+#      readframes(n)   -- returns at most n frames of audio
+#      rewind()        -- rewind to the beginning of the audio stream
+#      setpos(pos)     -- seek to the specified position
+#      tell()          -- return the current position
+#      close()         -- close the instance (make it unusable)
+# The position returned by tell() and the position given to setpos()
+# are compatible and have nothing to do with the actual postion in the
+# file.
+# The close() method is called automatically when the class instance
+# is destroyed.
+#
+# Writing audio files:
+#      f = au.open(file, 'w')
+# or
+#      f = au.openfp(filep, 'w')
+# where file is the name of a file and filep is an open file pointer.
+# The open file pointer must have methods write(), tell(), seek(), and
+# close().
+#
+# This returns an instance of a class with the following public methods:
+#      setnchannels(n) -- set the number of channels
+#      setsampwidth(n) -- set the sample width
+#      setframerate(n) -- set the frame rate
+#      setnframes(n)   -- set the number of frames
+#      setcomptype(type, name)
+#                      -- set the compression type and the
+#                         human-readable compression type
+#      setparams(nchannels, sampwidth, framerate, nframes, comptype, compname)
+#                      -- set all parameters at once
+#      tell()          -- return current position in output file
+#      writeframesraw(data)
+#                      -- write audio frames without pathing up the
+#                         file header
+#      writeframes(data)
+#                      -- write audio frames and patch up the file header
+#      close()         -- patch up the file header and close the
+#                         output file
+# You should set the parameters before the first writeframesraw or
+# writeframes.  The total number of frames does not need to be set,
+# but when it is set to the correct value, the header does not have to
+# be patched up.
+# It is best to first set all parameters, perhaps possibly the
+# compression type, and then write audio frames using writeframesraw.
+# When all frames have been written, either call writeframes('') or
+# close() to patch up the sizes in the header.
+# The close() method is called automatically when the class instance
+# is destroyed.
+
+# from <multimedia/audio_filehdr.h>
+AUDIO_FILE_MAGIC = 0x2e736e64
+AUDIO_FILE_ENCODING_MULAW_8 = 1
+AUDIO_FILE_ENCODING_LINEAR_8 = 2
+AUDIO_FILE_ENCODING_LINEAR_16 = 3
+AUDIO_FILE_ENCODING_LINEAR_24 = 4
+AUDIO_FILE_ENCODING_LINEAR_32 = 5
+AUDIO_FILE_ENCODING_FLOAT = 6
+AUDIO_FILE_ENCODING_DOUBLE = 7
+AUDIO_FILE_ENCODING_ADPCM_G721 = 23
+AUDIO_FILE_ENCODING_ADPCM_G722 = 24
+AUDIO_FILE_ENCODING_ADPCM_G723_3 = 25
+AUDIO_FILE_ENCODING_ADPCM_G723_5 = 26
+AUDIO_FILE_ENCODING_ALAW_8 = 27
+
+# from <multimedia/audio_hdr.h>
+AUDIO_UNKNOWN_SIZE = 0xFFFFFFFFL       # ((unsigned)(~0))
+
+_simple_encodings = [AUDIO_FILE_ENCODING_MULAW_8,
+                    AUDIO_FILE_ENCODING_LINEAR_8,
+                    AUDIO_FILE_ENCODING_LINEAR_16,
+                    AUDIO_FILE_ENCODING_LINEAR_24,
+                    AUDIO_FILE_ENCODING_LINEAR_32,
+                    AUDIO_FILE_ENCODING_ALAW_8]
+
+def _read_u32(file):
+       x = 0L
+       for i in range(4):
+               byte = file.read(1)
+               if byte == '':
+                       raise EOFError
+               x = x*256 + ord(byte)
+       return x
+
+def _write_u32(file, x):
+       data = []
+       for i in range(4):
+               d, m = divmod(x, 256)
+               data.insert(0, m)
+               x = d
+       for i in range(4):
+               file.write(chr(int(data[i])))
+
+class Au_read:
+       def initfp(self, file):
+               self._file = file
+               self._soundpos = 0
+               magic = int(_read_u32(file))
+               if magic != AUDIO_FILE_MAGIC:
+                       raise Error, 'bad magic number'
+               self._hdr_size = int(_read_u32(file))
+               if self._hdr_size < 24:
+                       raise Error, 'header size too small'
+               if self._hdr_size > 100:
+                       raise Error, 'header size rediculously large'
+               self._data_size = _read_u32(file)
+               if self._data_size != AUDIO_UNKNOWN_SIZE:
+                       self._data_size = int(self._data_size)
+               self._encoding = int(_read_u32(file))
+               if self._encoding not in _simple_encodings:
+                       raise Error, 'encoding not (yet) supported'
+               if self._encoding in (AUDIO_FILE_ENCODING_MULAW_8,
+                         AUDIO_FILE_ENCODING_LINEAR_8,
+                         AUDIO_FILE_ENCODING_ALAW_8):
+                       self._sampwidth = 2
+                       self._framesize = 1
+               elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_16:
+                       self._framesize = self._sampwidth = 2
+               elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_24:
+                       self._framesize = self._sampwidth = 3
+               elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_32:
+                       self._framesize = self._sampwidth = 4
+               else:
+                       raise Error, 'unknown encoding'
+               self._framerate = int(_read_u32(file))
+               self._nchannels = int(_read_u32(file))
+               self._framesize = self._framesize * self._nchannels
+               if self._hdr_size > 24:
+                       self._info = file.read(self._hdr_size - 24)
+                       for i in range(len(self._info)):
+                               if self._info[i] == '\0':
+                                       self._info = self._info[:i]
+                                       break
+               else:
+                       self._info = ''
+               return self
+
+       def init(self, filename):
+               import builtin
+               return self.initfp(builtin.open(filename, 'r'))
+
+       def __del__(self):
+               if self._file:
+                       self.close()
+
+       def getfp(self):
+               return self._file
+
+       def getnchannels(self):
+               return self._nchannels
+
+       def getsampwidth(self):
+               return self._sampwidth
+
+       def getframerate(self):
+               return self._framerate
+
+       def getnframes(self):
+               if self._data_size == AUDIO_UNKNOWN_SIZE:
+                       return AUDIO_UNKNOWN_SIZE
+               if self._encoding in _simple_encodings:
+                       return self._data_size / self._framesize
+               return 0                # XXX--must do some arithmetic here
+
+       def getcomptype(self):
+               if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
+                       return 'ULAW'
+               elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
+                       return 'ALAW'
+               else:
+                       return 'NONE'
+
+       def getcompname(self):
+               if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
+                       return 'CCITT G.711 u-law'
+               elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
+                       return 'CCITT G.711 A-law'
+               else:
+                       return 'not compressed'
+
+       def getparams(self):
+               return self.getnchannels(), self.getsampwidth(), \
+                         self.getframerate(), self.getnframes(), \
+                         self.getcomptype(), self.getcompname()
+
+       def getmarkers(self):
+               return None
+
+       def getmark(self, id):
+               raise Error, 'no marks'
+
+       def readframes(self, nframes):
+               if self._encoding in _simple_encodings:
+                       if nframes == AUDIO_UNKNOWN_SIZE:
+                               data = self._file.read()
+                       else:
+                               data = self._file.read(nframes * self._sampwidth * self._nchannels)
+                       if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
+                               import audioop
+                               data = audioop.ulaw2lin(data, self._sampwidth)
+                       return data
+               return None             # XXX--not implemented yet
+
+       def rewind(self):
+               self._soundpos = 0
+               self._file.seek(self._hdr_size)
+
+       def tell(self):
+               return self._soundpos
+
+       def setpos(self, pos):
+               if pos < 0 or pos > self.getnframes():
+                       raise Error, 'position not in range'
+               self._file.seek(pos * self._framesize + self._hdr_size)
+               self._soundpos = pos
+
+       def close(self):
+               self._file.close()
+               self._file = None
+
+class Au_write:
+       def init(self, filename):
+               import builtin
+               return self.initfp(builtin.open(filename, 'w'))
+
+       def initfp(self, file):
+               self._file = file
+               self._framerate = 0
+               self._nchannels = 0
+               self._sampwidth = 0
+               self._framesize = 0
+               self._nframes = AUDIO_UNKNOWN_SIZE
+               self._nframeswritten = 0
+               self._datawritten = 0
+               self._datalength = 0
+               self._info = ''
+               self._comptype = 'ULAW' # default is U-law
+               return self
+
+       def __del__(self):
+               if self._file:
+                       self.close()
+
+       def setnchannels(self, nchannels):
+               if self._nframeswritten:
+                       raise Error, 'cannot change parameters after starting to write'
+               if nchannels not in (1, 2, 4):
+                       raise Error, 'only 1, 2, or 4 channels supported'
+               self._nchannels = nchannels
+
+       def getnchannels(self):
+               if not self._nchannels:
+                       raise Error, 'number of channels not set'
+               return self._nchannels
+
+       def setsampwidth(self, sampwidth):
+               if self._nframeswritten:
+                       raise Error, 'cannot change parameters after starting to write'
+               if sampwidth not in (1, 2, 4):
+                       raise Error, 'bad sample width'
+               self._sampwidth = sampwidth
+
+       def getsampwidth(self):
+               if not self._framerate:
+                       raise Error, 'sample width not specified'
+               return self._sampwidth
+
+       def setframerate(self, framerate):
+               if self._nframeswritten:
+                       raise Error, 'cannot change parameters after starting to write'
+               self._framerate = framerate
+
+       def getframerate(self):
+               if not self._framerate:
+                       raise Error, 'frame rate not set'
+               return self._framerate
+
+       def setnframes(self, nframes):
+               if self._nframeswritten:
+                       raise Error, 'cannot change parameters after starting to write'
+               if nframes < 0:
+                       raise Error, '# of frames cannot be negative'
+               self._nframes = nframes
+
+       def getnframes(self):
+               return self._nframeswritten
+
+       def setcomptype(self, type, name):
+               if type in ('NONE', 'ULAW'):
+                       self._comptype = type
+               else:
+                       raise Error, 'unknown compression type'
+
+       def getcomptype(self):
+               return self._comptype
+
+       def getcompname(self):
+               if self._comptype == 'ULAW':
+                       return 'CCITT G.711 u-law'
+               elif self._comptype == 'ALAW':
+                       return 'CCITT G.711 A-law'
+               else:
+                       return 'not compressed'
+
+       def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
+               self.setnchannels(nchannels)
+               self.setsampwidth(sampwidth)
+               self.setframerate(framerate)
+               self.setnframes(nframes)
+               self.setcomptype(comptype, compname)
+
+       def getparams(self):
+               return self.getnchannels(), self.getsampwidth(), \
+                         self.getframerate(), self.getnframes(), \
+                         self.getcomptype(), self.getcompname()
+
+       def tell(self):
+               return self._nframeswritten
+
+       def writeframesraw(self, data):
+               self._ensure_header_written()
+               nframes = len(data) / self._framesize
+               if self._comptype == 'ULAW':
+                       import audioop
+                       data = audioop.lin2ulaw(data, self._sampwidth)
+               self._file.write(data)
+               self._nframeswritten = self._nframeswritten + nframes
+               self._datawritten = self._datawritten + len(data)
+
+       def writeframes(self, data):
+               self.writeframesraw(data)
+               if self._nframeswritten != self._nframes or \
+                         self._datalength != self._datawritten:
+                       self._patchheader()
+
+       def close(self):
+               self._ensure_header_written()
+               if self._nframeswritten != self._nframes or \
+                         self._datalength != self._datawritten:
+                       self._patchheader()
+               self._file.close()
+               self._file = None
+
+       #
+       # private methods
+       #
+       def _ensure_header_written(self):
+               if not self._nframeswritten:
+                       if not self._nchannels:
+                               raise Error, '# of channels not specified'
+                       if not self._sampwidth:
+                               raise Error, 'sample width not specified'
+                       if not self._framerate:
+                               raise Error, 'frame rate not specified'
+                       self._write_header()
+
+       def _write_header(self):
+               if self._comptype == 'NONE':
+                       if self._sampwidth == 1:
+                               encoding = AUDIO_FILE_ENCODING_LINEAR_8
+                               self._framesize = 1
+                       elif self._sampwidth == 2:
+                               encoding = AUDIO_FILE_ENCODING_LINEAR_16
+                               self._framesize = 2
+                       elif self._sampwidth == 4:
+                               encoding = AUDIO_FILE_ENCODING_LINEAR_32
+                               self._framesize = 4
+                       else:
+                               raise Error, 'internal error'
+               elif self._comptype == 'ULAW':
+                       encoding = AUDIO_FILE_ENCODING_MULAW_8
+                       self._framesize = 1
+               else:
+                       raise Error, 'internal error'
+               self._framesize = self._framesize * self._nchannels
+               _write_u32(self._file, AUDIO_FILE_MAGIC)
+               header_size = 25 + len(self._info)
+               header_size = (header_size + 7) & ~7
+               _write_u32(self._file, header_size)
+               if self._nframes == AUDIO_UNKNOWN_SIZE:
+                       length = AUDIO_UNKNOWN_SIZE
+               else:
+                       length = self._nframes * self._framesize
+               _write_u32(self._file, length)
+               self._datalength = length
+               _write_u32(self._file, encoding)
+               _write_u32(self._file, self._framerate)
+               _write_u32(self._file, self._nchannels)
+               self._file.write(self._info)
+               self._file.write('\0'*(header_size - len(self._info) - 24))
+
+       def _patchheader(self):
+               self._file.seek(8)
+               _write_u32(self._file, self._datawritten)
+               self._datalength = self._datawritten
+               self._file.seek(0, 2)
+
+def open(filename, mode):
+       if mode == 'r':
+               return Au_read().init(filename)
+       elif mode == 'w':
+               return Au_write().init(filename)
+       else:
+               raise Error, "mode must be 'r' or 'w'"
+
+def openfp(filep, mode):
+       if mode == 'r':
+               return Au_read().initfp(filep)
+       elif mode == 'w':
+               return Au_write().initfp(filep)
+       else:
+               raise Error, "mode must be 'r' or 'w'"