PageRenderTime 94ms CodeModel.GetById 41ms app.highlight 43ms RepoModel.GetById 1ms app.codeStats 0ms

/Lib/aifc.py

http://unladen-swallow.googlecode.com/
Python | 955 lines | 935 code | 0 blank | 20 comment | 1 complexity | 6142a02a9296089c789f667583f9f912 MD5 | raw file
  1"""Stuff to parse AIFF-C and AIFF files.
  2
  3Unless explicitly stated otherwise, the description below is true
  4both for AIFF-C files and AIFF files.
  5
  6An AIFF-C file has the following structure.
  7
  8  +-----------------+
  9  | FORM            |
 10  +-----------------+
 11  | <size>          |
 12  +----+------------+
 13  |    | AIFC       |
 14  |    +------------+
 15  |    | <chunks>   |
 16  |    |    .       |
 17  |    |    .       |
 18  |    |    .       |
 19  +----+------------+
 20
 21An AIFF file has the string "AIFF" instead of "AIFC".
 22
 23A chunk consists of an identifier (4 bytes) followed by a size (4 bytes,
 24big endian order), followed by the data.  The size field does not include
 25the size of the 8 byte header.
 26
 27The following chunk types are recognized.
 28
 29  FVER
 30      <version number of AIFF-C defining document> (AIFF-C only).
 31  MARK
 32      <# of markers> (2 bytes)
 33      list of markers:
 34          <marker ID> (2 bytes, must be > 0)
 35          <position> (4 bytes)
 36          <marker name> ("pstring")
 37  COMM
 38      <# of channels> (2 bytes)
 39      <# of sound frames> (4 bytes)
 40      <size of the samples> (2 bytes)
 41      <sampling frequency> (10 bytes, IEEE 80-bit extended
 42          floating point)
 43      in AIFF-C files only:
 44      <compression type> (4 bytes)
 45      <human-readable version of compression type> ("pstring")
 46  SSND
 47      <offset> (4 bytes, not used by this program)
 48      <blocksize> (4 bytes, not used by this program)
 49      <sound data>
 50
 51A pstring consists of 1 byte length, a string of characters, and 0 or 1
 52byte pad to make the total length even.
 53
 54Usage.
 55
 56Reading AIFF files:
 57  f = aifc.open(file, 'r')
 58where file is either the name of a file or an open file pointer.
 59The open file pointer must have methods read(), seek(), and close().
 60In some types of audio files, if the setpos() method is not used,
 61the seek() method is not necessary.
 62
 63This returns an instance of a class with the following public methods:
 64  getnchannels()  -- returns number of audio channels (1 for
 65             mono, 2 for stereo)
 66  getsampwidth()  -- returns sample width in bytes
 67  getframerate()  -- returns sampling frequency
 68  getnframes()    -- returns number of audio frames
 69  getcomptype()   -- returns compression type ('NONE' for AIFF files)
 70  getcompname()   -- returns human-readable version of
 71             compression type ('not compressed' for AIFF files)
 72  getparams() -- returns a tuple consisting of all of the
 73             above in the above order
 74  getmarkers()    -- get the list of marks in the audio file or None
 75             if there are no marks
 76  getmark(id) -- get mark with the specified id (raises an error
 77             if the mark does not exist)
 78  readframes(n)   -- returns at most n frames of audio
 79  rewind()    -- rewind to the beginning of the audio stream
 80  setpos(pos) -- seek to the specified position
 81  tell()      -- return the current position
 82  close()     -- close the instance (make it unusable)
 83The position returned by tell(), the position given to setpos() and
 84the position of marks are all compatible and have nothing to do with
 85the actual position in the file.
 86The close() method is called automatically when the class instance
 87is destroyed.
 88
 89Writing AIFF files:
 90  f = aifc.open(file, 'w')
 91where file is either the name of a file or an open file pointer.
 92The open file pointer must have methods write(), tell(), seek(), and
 93close().
 94
 95This returns an instance of a class with the following public methods:
 96  aiff()      -- create an AIFF file (AIFF-C default)
 97  aifc()      -- create an AIFF-C file
 98  setnchannels(n) -- set the number of channels
 99  setsampwidth(n) -- set the sample width
100  setframerate(n) -- set the frame rate
101  setnframes(n)   -- set the number of frames
102  setcomptype(type, name)
103          -- set the compression type and the
104             human-readable compression type
105  setparams(tuple)
106          -- set all parameters at once
107  setmark(id, pos, name)
108          -- add specified mark to the list of marks
109  tell()      -- return current position in output file (useful
110             in combination with setmark())
111  writeframesraw(data)
112          -- write audio frames without pathing up the
113             file header
114  writeframes(data)
115          -- write audio frames and patch up the file header
116  close()     -- patch up the file header and close the
117             output file
118You should set the parameters before the first writeframesraw or
119writeframes.  The total number of frames does not need to be set,
120but when it is set to the correct value, the header does not have to
121be patched up.
122It is best to first set all parameters, perhaps possibly the
123compression type, and then write audio frames using writeframesraw.
124When all frames have been written, either call writeframes('') or
125close() to patch up the sizes in the header.
126Marks can be added anytime.  If there are any marks, ypu must call
127close() after all frames have been written.
128The close() method is called automatically when the class instance
129is destroyed.
130
131When a file is opened with the extension '.aiff', an AIFF file is
132written, otherwise an AIFF-C file is written.  This default can be
133changed by calling aiff() or aifc() before the first writeframes or
134writeframesraw.
135"""
136
137import struct
138import __builtin__
139
140__all__ = ["Error","open","openfp"]
141
142class Error(Exception):
143    pass
144
145_AIFC_version = 0xA2805140L     # Version 1 of AIFF-C
146
147def _read_long(file):
148    try:
149        return struct.unpack('>l', file.read(4))[0]
150    except struct.error:
151        raise EOFError
152
153def _read_ulong(file):
154    try:
155        return struct.unpack('>L', file.read(4))[0]
156    except struct.error:
157        raise EOFError
158
159def _read_short(file):
160    try:
161        return struct.unpack('>h', file.read(2))[0]
162    except struct.error:
163        raise EOFError
164
165def _read_string(file):
166    length = ord(file.read(1))
167    if length == 0:
168        data = ''
169    else:
170        data = file.read(length)
171    if length & 1 == 0:
172        dummy = file.read(1)
173    return data
174
175_HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
176
177def _read_float(f): # 10 bytes
178    expon = _read_short(f) # 2 bytes
179    sign = 1
180    if expon < 0:
181        sign = -1
182        expon = expon + 0x8000
183    himant = _read_ulong(f) # 4 bytes
184    lomant = _read_ulong(f) # 4 bytes
185    if expon == himant == lomant == 0:
186        f = 0.0
187    elif expon == 0x7FFF:
188        f = _HUGE_VAL
189    else:
190        expon = expon - 16383
191        f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63)
192    return sign * f
193
194def _write_short(f, x):
195    f.write(struct.pack('>h', x))
196
197def _write_long(f, x):
198    f.write(struct.pack('>L', x))
199
200def _write_string(f, s):
201    if len(s) > 255:
202        raise ValueError("string exceeds maximum pstring length")
203    f.write(chr(len(s)))
204    f.write(s)
205    if len(s) & 1 == 0:
206        f.write(chr(0))
207
208def _write_float(f, x):
209    import math
210    if x < 0:
211        sign = 0x8000
212        x = x * -1
213    else:
214        sign = 0
215    if x == 0:
216        expon = 0
217        himant = 0
218        lomant = 0
219    else:
220        fmant, expon = math.frexp(x)
221        if expon > 16384 or fmant >= 1:     # Infinity or NaN
222            expon = sign|0x7FFF
223            himant = 0
224            lomant = 0
225        else:                   # Finite
226            expon = expon + 16382
227            if expon < 0:           # denormalized
228                fmant = math.ldexp(fmant, expon)
229                expon = 0
230            expon = expon | sign
231            fmant = math.ldexp(fmant, 32)
232            fsmant = math.floor(fmant)
233            himant = long(fsmant)
234            fmant = math.ldexp(fmant - fsmant, 32)
235            fsmant = math.floor(fmant)
236            lomant = long(fsmant)
237    _write_short(f, expon)
238    _write_long(f, himant)
239    _write_long(f, lomant)
240
241from chunk import Chunk
242
243class Aifc_read:
244    # Variables used in this class:
245    #
246    # These variables are available to the user though appropriate
247    # methods of this class:
248    # _file -- the open file with methods read(), close(), and seek()
249    #       set through the __init__() method
250    # _nchannels -- the number of audio channels
251    #       available through the getnchannels() method
252    # _nframes -- the number of audio frames
253    #       available through the getnframes() method
254    # _sampwidth -- the number of bytes per audio sample
255    #       available through the getsampwidth() method
256    # _framerate -- the sampling frequency
257    #       available through the getframerate() method
258    # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
259    #       available through the getcomptype() method
260    # _compname -- the human-readable AIFF-C compression type
261    #       available through the getcomptype() method
262    # _markers -- the marks in the audio file
263    #       available through the getmarkers() and getmark()
264    #       methods
265    # _soundpos -- the position in the audio stream
266    #       available through the tell() method, set through the
267    #       setpos() method
268    #
269    # These variables are used internally only:
270    # _version -- the AIFF-C version number
271    # _decomp -- the decompressor from builtin module cl
272    # _comm_chunk_read -- 1 iff the COMM chunk has been read
273    # _aifc -- 1 iff reading an AIFF-C file
274    # _ssnd_seek_needed -- 1 iff positioned correctly in audio
275    #       file for readframes()
276    # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
277    # _framesize -- size of one frame in the file
278
279    def initfp(self, file):
280        self._version = 0
281        self._decomp = None
282        self._convert = None
283        self._markers = []
284        self._soundpos = 0
285        self._file = file
286        chunk = Chunk(file)
287        if chunk.getname() != 'FORM':
288            raise Error, 'file does not start with FORM id'
289        formdata = chunk.read(4)
290        if formdata == 'AIFF':
291            self._aifc = 0
292        elif formdata == 'AIFC':
293            self._aifc = 1
294        else:
295            raise Error, 'not an AIFF or AIFF-C file'
296        self._comm_chunk_read = 0
297        while 1:
298            self._ssnd_seek_needed = 1
299            try:
300                chunk = Chunk(self._file)
301            except EOFError:
302                break
303            chunkname = chunk.getname()
304            if chunkname == 'COMM':
305                self._read_comm_chunk(chunk)
306                self._comm_chunk_read = 1
307            elif chunkname == 'SSND':
308                self._ssnd_chunk = chunk
309                dummy = chunk.read(8)
310                self._ssnd_seek_needed = 0
311            elif chunkname == 'FVER':
312                self._version = _read_ulong(chunk)
313            elif chunkname == 'MARK':
314                self._readmark(chunk)
315            chunk.skip()
316        if not self._comm_chunk_read or not self._ssnd_chunk:
317            raise Error, 'COMM chunk and/or SSND chunk missing'
318        if self._aifc and self._decomp:
319            import cl
320            params = [cl.ORIGINAL_FORMAT, 0,
321                  cl.BITS_PER_COMPONENT, self._sampwidth * 8,
322                  cl.FRAME_RATE, self._framerate]
323            if self._nchannels == 1:
324                params[1] = cl.MONO
325            elif self._nchannels == 2:
326                params[1] = cl.STEREO_INTERLEAVED
327            else:
328                raise Error, 'cannot compress more than 2 channels'
329            self._decomp.SetParams(params)
330
331    def __init__(self, f):
332        if type(f) == type(''):
333            f = __builtin__.open(f, 'rb')
334        # else, assume it is an open file object already
335        self.initfp(f)
336
337    #
338    # User visible methods.
339    #
340    def getfp(self):
341        return self._file
342
343    def rewind(self):
344        self._ssnd_seek_needed = 1
345        self._soundpos = 0
346
347    def close(self):
348        if self._decomp:
349            self._decomp.CloseDecompressor()
350            self._decomp = None
351        self._file.close()
352
353    def tell(self):
354        return self._soundpos
355
356    def getnchannels(self):
357        return self._nchannels
358
359    def getnframes(self):
360        return self._nframes
361
362    def getsampwidth(self):
363        return self._sampwidth
364
365    def getframerate(self):
366        return self._framerate
367
368    def getcomptype(self):
369        return self._comptype
370
371    def getcompname(self):
372        return self._compname
373
374##  def getversion(self):
375##      return self._version
376
377    def getparams(self):
378        return self.getnchannels(), self.getsampwidth(), \
379              self.getframerate(), self.getnframes(), \
380              self.getcomptype(), self.getcompname()
381
382    def getmarkers(self):
383        if len(self._markers) == 0:
384            return None
385        return self._markers
386
387    def getmark(self, id):
388        for marker in self._markers:
389            if id == marker[0]:
390                return marker
391        raise Error, 'marker %r does not exist' % (id,)
392
393    def setpos(self, pos):
394        if pos < 0 or pos > self._nframes:
395            raise Error, 'position not in range'
396        self._soundpos = pos
397        self._ssnd_seek_needed = 1
398
399    def readframes(self, nframes):
400        if self._ssnd_seek_needed:
401            self._ssnd_chunk.seek(0)
402            dummy = self._ssnd_chunk.read(8)
403            pos = self._soundpos * self._framesize
404            if pos:
405                self._ssnd_chunk.seek(pos + 8)
406            self._ssnd_seek_needed = 0
407        if nframes == 0:
408            return ''
409        data = self._ssnd_chunk.read(nframes * self._framesize)
410        if self._convert and data:
411            data = self._convert(data)
412        self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
413        return data
414
415    #
416    # Internal methods.
417    #
418
419    def _decomp_data(self, data):
420        import cl
421        dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
422                          len(data) * 2)
423        return self._decomp.Decompress(len(data) / self._nchannels,
424                           data)
425
426    def _ulaw2lin(self, data):
427        import audioop
428        return audioop.ulaw2lin(data, 2)
429
430    def _adpcm2lin(self, data):
431        import audioop
432        if not hasattr(self, '_adpcmstate'):
433            # first time
434            self._adpcmstate = None
435        data, self._adpcmstate = audioop.adpcm2lin(data, 2,
436                               self._adpcmstate)
437        return data
438
439    def _read_comm_chunk(self, chunk):
440        self._nchannels = _read_short(chunk)
441        self._nframes = _read_long(chunk)
442        self._sampwidth = (_read_short(chunk) + 7) / 8
443        self._framerate = int(_read_float(chunk))
444        self._framesize = self._nchannels * self._sampwidth
445        if self._aifc:
446            #DEBUG: SGI's soundeditor produces a bad size :-(
447            kludge = 0
448            if chunk.chunksize == 18:
449                kludge = 1
450                print 'Warning: bad COMM chunk size'
451                chunk.chunksize = 23
452            #DEBUG end
453            self._comptype = chunk.read(4)
454            #DEBUG start
455            if kludge:
456                length = ord(chunk.file.read(1))
457                if length & 1 == 0:
458                    length = length + 1
459                chunk.chunksize = chunk.chunksize + length
460                chunk.file.seek(-1, 1)
461            #DEBUG end
462            self._compname = _read_string(chunk)
463            if self._comptype != 'NONE':
464                if self._comptype == 'G722':
465                    try:
466                        import audioop
467                    except ImportError:
468                        pass
469                    else:
470                        self._convert = self._adpcm2lin
471                        self._framesize = self._framesize / 4
472                        return
473                # for ULAW and ALAW try Compression Library
474                try:
475                    import cl
476                except ImportError:
477                    if self._comptype == 'ULAW':
478                        try:
479                            import audioop
480                            self._convert = self._ulaw2lin
481                            self._framesize = self._framesize / 2
482                            return
483                        except ImportError:
484                            pass
485                    raise Error, 'cannot read compressed AIFF-C files'
486                if self._comptype == 'ULAW':
487                    scheme = cl.G711_ULAW
488                    self._framesize = self._framesize / 2
489                elif self._comptype == 'ALAW':
490                    scheme = cl.G711_ALAW
491                    self._framesize = self._framesize / 2
492                else:
493                    raise Error, 'unsupported compression type'
494                self._decomp = cl.OpenDecompressor(scheme)
495                self._convert = self._decomp_data
496        else:
497            self._comptype = 'NONE'
498            self._compname = 'not compressed'
499
500    def _readmark(self, chunk):
501        nmarkers = _read_short(chunk)
502        # Some files appear to contain invalid counts.
503        # Cope with this by testing for EOF.
504        try:
505            for i in range(nmarkers):
506                id = _read_short(chunk)
507                pos = _read_long(chunk)
508                name = _read_string(chunk)
509                if pos or name:
510                    # some files appear to have
511                    # dummy markers consisting of
512                    # a position 0 and name ''
513                    self._markers.append((id, pos, name))
514        except EOFError:
515            print 'Warning: MARK chunk contains only',
516            print len(self._markers),
517            if len(self._markers) == 1: print 'marker',
518            else: print 'markers',
519            print 'instead of', nmarkers
520
521class Aifc_write:
522    # Variables used in this class:
523    #
524    # These variables are user settable through appropriate methods
525    # of this class:
526    # _file -- the open file with methods write(), close(), tell(), seek()
527    #       set through the __init__() method
528    # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
529    #       set through the setcomptype() or setparams() method
530    # _compname -- the human-readable AIFF-C compression type
531    #       set through the setcomptype() or setparams() method
532    # _nchannels -- the number of audio channels
533    #       set through the setnchannels() or setparams() method
534    # _sampwidth -- the number of bytes per audio sample
535    #       set through the setsampwidth() or setparams() method
536    # _framerate -- the sampling frequency
537    #       set through the setframerate() or setparams() method
538    # _nframes -- the number of audio frames written to the header
539    #       set through the setnframes() or setparams() method
540    # _aifc -- whether we're writing an AIFF-C file or an AIFF file
541    #       set through the aifc() method, reset through the
542    #       aiff() method
543    #
544    # These variables are used internally only:
545    # _version -- the AIFF-C version number
546    # _comp -- the compressor from builtin module cl
547    # _nframeswritten -- the number of audio frames actually written
548    # _datalength -- the size of the audio samples written to the header
549    # _datawritten -- the size of the audio samples actually written
550
551    def __init__(self, f):
552        if type(f) == type(''):
553            filename = f
554            f = __builtin__.open(f, 'wb')
555        else:
556            # else, assume it is an open file object already
557            filename = '???'
558        self.initfp(f)
559        if filename[-5:] == '.aiff':
560            self._aifc = 0
561        else:
562            self._aifc = 1
563
564    def initfp(self, file):
565        self._file = file
566        self._version = _AIFC_version
567        self._comptype = 'NONE'
568        self._compname = 'not compressed'
569        self._comp = None
570        self._convert = None
571        self._nchannels = 0
572        self._sampwidth = 0
573        self._framerate = 0
574        self._nframes = 0
575        self._nframeswritten = 0
576        self._datawritten = 0
577        self._datalength = 0
578        self._markers = []
579        self._marklength = 0
580        self._aifc = 1      # AIFF-C is default
581
582    def __del__(self):
583        if self._file:
584            self.close()
585
586    #
587    # User visible methods.
588    #
589    def aiff(self):
590        if self._nframeswritten:
591            raise Error, 'cannot change parameters after starting to write'
592        self._aifc = 0
593
594    def aifc(self):
595        if self._nframeswritten:
596            raise Error, 'cannot change parameters after starting to write'
597        self._aifc = 1
598
599    def setnchannels(self, nchannels):
600        if self._nframeswritten:
601            raise Error, 'cannot change parameters after starting to write'
602        if nchannels < 1:
603            raise Error, 'bad # of channels'
604        self._nchannels = nchannels
605
606    def getnchannels(self):
607        if not self._nchannels:
608            raise Error, 'number of channels not set'
609        return self._nchannels
610
611    def setsampwidth(self, sampwidth):
612        if self._nframeswritten:
613            raise Error, 'cannot change parameters after starting to write'
614        if sampwidth < 1 or sampwidth > 4:
615            raise Error, 'bad sample width'
616        self._sampwidth = sampwidth
617
618    def getsampwidth(self):
619        if not self._sampwidth:
620            raise Error, 'sample width not set'
621        return self._sampwidth
622
623    def setframerate(self, framerate):
624        if self._nframeswritten:
625            raise Error, 'cannot change parameters after starting to write'
626        if framerate <= 0:
627            raise Error, 'bad frame rate'
628        self._framerate = framerate
629
630    def getframerate(self):
631        if not self._framerate:
632            raise Error, 'frame rate not set'
633        return self._framerate
634
635    def setnframes(self, nframes):
636        if self._nframeswritten:
637            raise Error, 'cannot change parameters after starting to write'
638        self._nframes = nframes
639
640    def getnframes(self):
641        return self._nframeswritten
642
643    def setcomptype(self, comptype, compname):
644        if self._nframeswritten:
645            raise Error, 'cannot change parameters after starting to write'
646        if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
647            raise Error, 'unsupported compression type'
648        self._comptype = comptype
649        self._compname = compname
650
651    def getcomptype(self):
652        return self._comptype
653
654    def getcompname(self):
655        return self._compname
656
657##  def setversion(self, version):
658##      if self._nframeswritten:
659##          raise Error, 'cannot change parameters after starting to write'
660##      self._version = version
661
662    def setparams(self, info):
663        nchannels, sampwidth, framerate, nframes, comptype, compname = info
664        if self._nframeswritten:
665            raise Error, 'cannot change parameters after starting to write'
666        if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
667            raise Error, 'unsupported compression type'
668        self.setnchannels(nchannels)
669        self.setsampwidth(sampwidth)
670        self.setframerate(framerate)
671        self.setnframes(nframes)
672        self.setcomptype(comptype, compname)
673
674    def getparams(self):
675        if not self._nchannels or not self._sampwidth or not self._framerate:
676            raise Error, 'not all parameters set'
677        return self._nchannels, self._sampwidth, self._framerate, \
678              self._nframes, self._comptype, self._compname
679
680    def setmark(self, id, pos, name):
681        if id <= 0:
682            raise Error, 'marker ID must be > 0'
683        if pos < 0:
684            raise Error, 'marker position must be >= 0'
685        if type(name) != type(''):
686            raise Error, 'marker name must be a string'
687        for i in range(len(self._markers)):
688            if id == self._markers[i][0]:
689                self._markers[i] = id, pos, name
690                return
691        self._markers.append((id, pos, name))
692
693    def getmark(self, id):
694        for marker in self._markers:
695            if id == marker[0]:
696                return marker
697        raise Error, 'marker %r does not exist' % (id,)
698
699    def getmarkers(self):
700        if len(self._markers) == 0:
701            return None
702        return self._markers
703
704    def tell(self):
705        return self._nframeswritten
706
707    def writeframesraw(self, data):
708        self._ensure_header_written(len(data))
709        nframes = len(data) / (self._sampwidth * self._nchannels)
710        if self._convert:
711            data = self._convert(data)
712        self._file.write(data)
713        self._nframeswritten = self._nframeswritten + nframes
714        self._datawritten = self._datawritten + len(data)
715
716    def writeframes(self, data):
717        self.writeframesraw(data)
718        if self._nframeswritten != self._nframes or \
719              self._datalength != self._datawritten:
720            self._patchheader()
721
722    def close(self):
723        self._ensure_header_written(0)
724        if self._datawritten & 1:
725            # quick pad to even size
726            self._file.write(chr(0))
727            self._datawritten = self._datawritten + 1
728        self._writemarkers()
729        if self._nframeswritten != self._nframes or \
730              self._datalength != self._datawritten or \
731              self._marklength:
732            self._patchheader()
733        if self._comp:
734            self._comp.CloseCompressor()
735            self._comp = None
736        self._file.close()
737
738    #
739    # Internal methods.
740    #
741
742    def _comp_data(self, data):
743        import cl
744        dummy = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data))
745        dummy = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data))
746        return self._comp.Compress(self._nframes, data)
747
748    def _lin2ulaw(self, data):
749        import audioop
750        return audioop.lin2ulaw(data, 2)
751
752    def _lin2adpcm(self, data):
753        import audioop
754        if not hasattr(self, '_adpcmstate'):
755            self._adpcmstate = None
756        data, self._adpcmstate = audioop.lin2adpcm(data, 2,
757                               self._adpcmstate)
758        return data
759
760    def _ensure_header_written(self, datasize):
761        if not self._nframeswritten:
762            if self._comptype in ('ULAW', 'ALAW'):
763                if not self._sampwidth:
764                    self._sampwidth = 2
765                if self._sampwidth != 2:
766                    raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
767            if self._comptype == 'G722':
768                if not self._sampwidth:
769                    self._sampwidth = 2
770                if self._sampwidth != 2:
771                    raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
772            if not self._nchannels:
773                raise Error, '# channels not specified'
774            if not self._sampwidth:
775                raise Error, 'sample width not specified'
776            if not self._framerate:
777                raise Error, 'sampling rate not specified'
778            self._write_header(datasize)
779
780    def _init_compression(self):
781        if self._comptype == 'G722':
782            self._convert = self._lin2adpcm
783            return
784        try:
785            import cl
786        except ImportError:
787            if self._comptype == 'ULAW':
788                try:
789                    import audioop
790                    self._convert = self._lin2ulaw
791                    return
792                except ImportError:
793                    pass
794            raise Error, 'cannot write compressed AIFF-C files'
795        if self._comptype == 'ULAW':
796            scheme = cl.G711_ULAW
797        elif self._comptype == 'ALAW':
798            scheme = cl.G711_ALAW
799        else:
800            raise Error, 'unsupported compression type'
801        self._comp = cl.OpenCompressor(scheme)
802        params = [cl.ORIGINAL_FORMAT, 0,
803              cl.BITS_PER_COMPONENT, self._sampwidth * 8,
804              cl.FRAME_RATE, self._framerate,
805              cl.FRAME_BUFFER_SIZE, 100,
806              cl.COMPRESSED_BUFFER_SIZE, 100]
807        if self._nchannels == 1:
808            params[1] = cl.MONO
809        elif self._nchannels == 2:
810            params[1] = cl.STEREO_INTERLEAVED
811        else:
812            raise Error, 'cannot compress more than 2 channels'
813        self._comp.SetParams(params)
814        # the compressor produces a header which we ignore
815        dummy = self._comp.Compress(0, '')
816        self._convert = self._comp_data
817
818    def _write_header(self, initlength):
819        if self._aifc and self._comptype != 'NONE':
820            self._init_compression()
821        self._file.write('FORM')
822        if not self._nframes:
823            self._nframes = initlength / (self._nchannels * self._sampwidth)
824        self._datalength = self._nframes * self._nchannels * self._sampwidth
825        if self._datalength & 1:
826            self._datalength = self._datalength + 1
827        if self._aifc:
828            if self._comptype in ('ULAW', 'ALAW'):
829                self._datalength = self._datalength / 2
830                if self._datalength & 1:
831                    self._datalength = self._datalength + 1
832            elif self._comptype == 'G722':
833                self._datalength = (self._datalength + 3) / 4
834                if self._datalength & 1:
835                    self._datalength = self._datalength + 1
836        self._form_length_pos = self._file.tell()
837        commlength = self._write_form_length(self._datalength)
838        if self._aifc:
839            self._file.write('AIFC')
840            self._file.write('FVER')
841            _write_long(self._file, 4)
842            _write_long(self._file, self._version)
843        else:
844            self._file.write('AIFF')
845        self._file.write('COMM')
846        _write_long(self._file, commlength)
847        _write_short(self._file, self._nchannels)
848        self._nframes_pos = self._file.tell()
849        _write_long(self._file, self._nframes)
850        _write_short(self._file, self._sampwidth * 8)
851        _write_float(self._file, self._framerate)
852        if self._aifc:
853            self._file.write(self._comptype)
854            _write_string(self._file, self._compname)
855        self._file.write('SSND')
856        self._ssnd_length_pos = self._file.tell()
857        _write_long(self._file, self._datalength + 8)
858        _write_long(self._file, 0)
859        _write_long(self._file, 0)
860
861    def _write_form_length(self, datalength):
862        if self._aifc:
863            commlength = 18 + 5 + len(self._compname)
864            if commlength & 1:
865                commlength = commlength + 1
866            verslength = 12
867        else:
868            commlength = 18
869            verslength = 0
870        _write_long(self._file, 4 + verslength + self._marklength + \
871                    8 + commlength + 16 + datalength)
872        return commlength
873
874    def _patchheader(self):
875        curpos = self._file.tell()
876        if self._datawritten & 1:
877            datalength = self._datawritten + 1
878            self._file.write(chr(0))
879        else:
880            datalength = self._datawritten
881        if datalength == self._datalength and \
882              self._nframes == self._nframeswritten and \
883              self._marklength == 0:
884            self._file.seek(curpos, 0)
885            return
886        self._file.seek(self._form_length_pos, 0)
887        dummy = self._write_form_length(datalength)
888        self._file.seek(self._nframes_pos, 0)
889        _write_long(self._file, self._nframeswritten)
890        self._file.seek(self._ssnd_length_pos, 0)
891        _write_long(self._file, datalength + 8)
892        self._file.seek(curpos, 0)
893        self._nframes = self._nframeswritten
894        self._datalength = datalength
895
896    def _writemarkers(self):
897        if len(self._markers) == 0:
898            return
899        self._file.write('MARK')
900        length = 2
901        for marker in self._markers:
902            id, pos, name = marker
903            length = length + len(name) + 1 + 6
904            if len(name) & 1 == 0:
905                length = length + 1
906        _write_long(self._file, length)
907        self._marklength = length + 8
908        _write_short(self._file, len(self._markers))
909        for marker in self._markers:
910            id, pos, name = marker
911            _write_short(self._file, id)
912            _write_long(self._file, pos)
913            _write_string(self._file, name)
914
915def open(f, mode=None):
916    if mode is None:
917        if hasattr(f, 'mode'):
918            mode = f.mode
919        else:
920            mode = 'rb'
921    if mode in ('r', 'rb'):
922        return Aifc_read(f)
923    elif mode in ('w', 'wb'):
924        return Aifc_write(f)
925    else:
926        raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
927
928openfp = open # B/W compatibility
929
930if __name__ == '__main__':
931    import sys
932    if not sys.argv[1:]:
933        sys.argv.append('/usr/demos/data/audio/bach.aiff')
934    fn = sys.argv[1]
935    f = open(fn, 'r')
936    print "Reading", fn
937    print "nchannels =", f.getnchannels()
938    print "nframes   =", f.getnframes()
939    print "sampwidth =", f.getsampwidth()
940    print "framerate =", f.getframerate()
941    print "comptype  =", f.getcomptype()
942    print "compname  =", f.getcompname()
943    if sys.argv[2:]:
944        gn = sys.argv[2]
945        print "Writing", gn
946        g = open(gn, 'w')
947        g.setparams(f.getparams())
948        while 1:
949            data = f.readframes(1024)
950            if not data:
951                break
952            g.writeframes(data)
953        g.close()
954        f.close()
955        print "Done."