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, you 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_ushort(file):
166    try:
167        return struct.unpack('>H', file.read(2))[0]
168    except struct.error:
169        raise EOFError
170
171def _read_string(file):
172    length = ord(file.read(1))
173    if length == 0:
174        data = ''
175    else:
176        data = file.read(length)
177    if length & 1 == 0:
178        dummy = file.read(1)
179    return data
180
181_HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
182
183def _read_float(f): # 10 bytes
184    expon = _read_short(f) # 2 bytes
185    sign = 1
186    if expon < 0:
187        sign = -1
188        expon = expon + 0x8000
189    himant = _read_ulong(f) # 4 bytes
190    lomant = _read_ulong(f) # 4 bytes
191    if expon == himant == lomant == 0:
192        f = 0.0
193    elif expon == 0x7FFF:
194        f = _HUGE_VAL
195    else:
196        expon = expon - 16383
197        f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63)
198    return sign * f
199
200def _write_short(f, x):
201    f.write(struct.pack('>h', x))
202
203def _write_ushort(f, x):
204    f.write(struct.pack('>H', x))
205
206def _write_long(f, x):
207    f.write(struct.pack('>l', x))
208
209def _write_ulong(f, x):
210    f.write(struct.pack('>L', x))
211
212def _write_string(f, s):
213    if len(s) > 255:
214        raise ValueError("string exceeds maximum pstring length")
215    f.write(struct.pack('B', len(s)))
216    f.write(s)
217    if len(s) & 1 == 0:
218        f.write(chr(0))
219
220def _write_float(f, x):
221    import math
222    if x < 0:
223        sign = 0x8000
224        x = x * -1
225    else:
226        sign = 0
227    if x == 0:
228        expon = 0
229        himant = 0
230        lomant = 0
231    else:
232        fmant, expon = math.frexp(x)
233        if expon > 16384 or fmant >= 1 or fmant != fmant: # Infinity or NaN
234            expon = sign|0x7FFF
235            himant = 0
236            lomant = 0
237        else:                   # Finite
238            expon = expon + 16382
239            if expon < 0:           # denormalized
240                fmant = math.ldexp(fmant, expon)
241                expon = 0
242            expon = expon | sign
243            fmant = math.ldexp(fmant, 32)
244            fsmant = math.floor(fmant)
245            himant = long(fsmant)
246            fmant = math.ldexp(fmant - fsmant, 32)
247            fsmant = math.floor(fmant)
248            lomant = long(fsmant)
249    _write_ushort(f, expon)
250    _write_ulong(f, himant)
251    _write_ulong(f, lomant)
252
253from chunk import Chunk
254
255class Aifc_read:
256    # Variables used in this class:
257    #
258    # These variables are available to the user though appropriate
259    # methods of this class:
260    # _file -- the open file with methods read(), close(), and seek()
261    #       set through the __init__() method
262    # _nchannels -- the number of audio channels
263    #       available through the getnchannels() method
264    # _nframes -- the number of audio frames
265    #       available through the getnframes() method
266    # _sampwidth -- the number of bytes per audio sample
267    #       available through the getsampwidth() method
268    # _framerate -- the sampling frequency
269    #       available through the getframerate() method
270    # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
271    #       available through the getcomptype() method
272    # _compname -- the human-readable AIFF-C compression type
273    #       available through the getcomptype() method
274    # _markers -- the marks in the audio file
275    #       available through the getmarkers() and getmark()
276    #       methods
277    # _soundpos -- the position in the audio stream
278    #       available through the tell() method, set through the
279    #       setpos() method
280    #
281    # These variables are used internally only:
282    # _version -- the AIFF-C version number
283    # _decomp -- the decompressor from builtin module cl
284    # _comm_chunk_read -- 1 iff the COMM chunk has been read
285    # _aifc -- 1 iff reading an AIFF-C file
286    # _ssnd_seek_needed -- 1 iff positioned correctly in audio
287    #       file for readframes()
288    # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
289    # _framesize -- size of one frame in the file
290
291    _file = None  # Set here since __del__ checks it
292
293    def initfp(self, file):
294        self._version = 0
295        self._decomp = None
296        self._convert = None
297        self._markers = []
298        self._soundpos = 0
299        self._file = file
300        chunk = Chunk(file)
301        if chunk.getname() != 'FORM':
302            raise Error, 'file does not start with FORM id'
303        formdata = chunk.read(4)
304        if formdata == 'AIFF':
305            self._aifc = 0
306        elif formdata == 'AIFC':
307            self._aifc = 1
308        else:
309            raise Error, 'not an AIFF or AIFF-C file'
310        self._comm_chunk_read = 0
311        self._ssnd_chunk = None
312        while 1:
313            self._ssnd_seek_needed = 1
314            try:
315                chunk = Chunk(self._file)
316            except EOFError:
317                break
318            chunkname = chunk.getname()
319            if chunkname == 'COMM':
320                self._read_comm_chunk(chunk)
321                self._comm_chunk_read = 1
322            elif chunkname == 'SSND':
323                self._ssnd_chunk = chunk
324                dummy = chunk.read(8)
325                self._ssnd_seek_needed = 0
326            elif chunkname == 'FVER':
327                self._version = _read_ulong(chunk)
328            elif chunkname == 'MARK':
329                self._readmark(chunk)
330            chunk.skip()
331        if not self._comm_chunk_read or not self._ssnd_chunk:
332            raise Error, 'COMM chunk and/or SSND chunk missing'
333        if self._aifc and self._decomp:
334            import cl
335            params = [cl.ORIGINAL_FORMAT, 0,
336                  cl.BITS_PER_COMPONENT, self._sampwidth * 8,
337                  cl.FRAME_RATE, self._framerate]
338            if self._nchannels == 1:
339                params[1] = cl.MONO
340            elif self._nchannels == 2:
341                params[1] = cl.STEREO_INTERLEAVED
342            else:
343                raise Error, 'cannot compress more than 2 channels'
344            self._decomp.SetParams(params)
345
346    def __init__(self, f):
347        if isinstance(f, basestring):
348            f = __builtin__.open(f, 'rb')
349            try:
350                self.initfp(f)
351            except:
352                f.close()
353                raise
354        else:
355            # assume it is an open file object already
356            self.initfp(f)
357
358    #
359    # User visible methods.
360    #
361    def getfp(self):
362        return self._file
363
364    def rewind(self):
365        self._ssnd_seek_needed = 1
366        self._soundpos = 0
367
368    def close(self):
369        decomp = self._decomp
370        try:
371            if decomp:
372                self._decomp = None
373                decomp.CloseDecompressor()
374        finally:
375            self._file.close()
376
377    def tell(self):
378        return self._soundpos
379
380    def getnchannels(self):
381        return self._nchannels
382
383    def getnframes(self):
384        return self._nframes
385
386    def getsampwidth(self):
387        return self._sampwidth
388
389    def getframerate(self):
390        return self._framerate
391
392    def getcomptype(self):
393        return self._comptype
394
395    def getcompname(self):
396        return self._compname
397
398##  def getversion(self):
399##      return self._version
400
401    def getparams(self):
402        return self.getnchannels(), self.getsampwidth(), \
403              self.getframerate(), self.getnframes(), \
404              self.getcomptype(), self.getcompname()
405
406    def getmarkers(self):
407        if len(self._markers) == 0:
408            return None
409        return self._markers
410
411    def getmark(self, id):
412        for marker in self._markers:
413            if id == marker[0]:
414                return marker
415        raise Error, 'marker %r does not exist' % (id,)
416
417    def setpos(self, pos):
418        if pos < 0 or pos > self._nframes:
419            raise Error, 'position not in range'
420        self._soundpos = pos
421        self._ssnd_seek_needed = 1
422
423    def readframes(self, nframes):
424        if self._ssnd_seek_needed:
425            self._ssnd_chunk.seek(0)
426            dummy = self._ssnd_chunk.read(8)
427            pos = self._soundpos * self._framesize
428            if pos:
429                self._ssnd_chunk.seek(pos + 8)
430            self._ssnd_seek_needed = 0
431        if nframes == 0:
432            return ''
433        data = self._ssnd_chunk.read(nframes * self._framesize)
434        if self._convert and data:
435            data = self._convert(data)
436        self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth)
437        return data
438
439    #
440    # Internal methods.
441    #
442
443    def _decomp_data(self, data):
444        import cl
445        dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
446                          len(data) * 2)
447        return self._decomp.Decompress(len(data) // self._nchannels,
448                           data)
449
450    def _ulaw2lin(self, data):
451        import audioop
452        return audioop.ulaw2lin(data, 2)
453
454    def _adpcm2lin(self, data):
455        import audioop
456        if not hasattr(self, '_adpcmstate'):
457            # first time
458            self._adpcmstate = None
459        data, self._adpcmstate = audioop.adpcm2lin(data, 2,
460                               self._adpcmstate)
461        return data
462
463    def _read_comm_chunk(self, chunk):
464        self._nchannels = _read_short(chunk)
465        self._nframes = _read_long(chunk)
466        self._sampwidth = (_read_short(chunk) + 7) // 8
467        self._framerate = int(_read_float(chunk))
468        self._framesize = self._nchannels * self._sampwidth
469        if self._aifc:
470            #DEBUG: SGI's soundeditor produces a bad size :-(
471            kludge = 0
472            if chunk.chunksize == 18:
473                kludge = 1
474                print 'Warning: bad COMM chunk size'
475                chunk.chunksize = 23
476            #DEBUG end
477            self._comptype = chunk.read(4)
478            #DEBUG start
479            if kludge:
480                length = ord(chunk.file.read(1))
481                if length & 1 == 0:
482                    length = length + 1
483                chunk.chunksize = chunk.chunksize + length
484                chunk.file.seek(-1, 1)
485            #DEBUG end
486            self._compname = _read_string(chunk)
487            if self._comptype != 'NONE':
488                if self._comptype == 'G722':
489                    try:
490                        import audioop
491                    except ImportError:
492                        pass
493                    else:
494                        self._convert = self._adpcm2lin
495                        self._sampwidth = 2
496                        return
497                # for ULAW and ALAW try Compression Library
498                try:
499                    import cl
500                except ImportError:
501                    if self._comptype in ('ULAW', 'ulaw'):
502                        try:
503                            import audioop
504                            self._convert = self._ulaw2lin
505                            self._sampwidth = 2
506                            return
507                        except ImportError:
508                            pass
509                    raise Error, 'cannot read compressed AIFF-C files'
510                if self._comptype in ('ULAW', 'ulaw'):
511                    scheme = cl.G711_ULAW
512                elif self._comptype in ('ALAW', 'alaw'):
513                    scheme = cl.G711_ALAW
514                else:
515                    raise Error, 'unsupported compression type'
516                self._decomp = cl.OpenDecompressor(scheme)
517                self._convert = self._decomp_data
518                self._sampwidth = 2
519        else:
520            self._comptype = 'NONE'
521            self._compname = 'not compressed'
522
523    def _readmark(self, chunk):
524        nmarkers = _read_short(chunk)
525        # Some files appear to contain invalid counts.
526        # Cope with this by testing for EOF.
527        try:
528            for i in range(nmarkers):
529                id = _read_short(chunk)
530                pos = _read_long(chunk)
531                name = _read_string(chunk)
532                if pos or name:
533                    # some files appear to have
534                    # dummy markers consisting of
535                    # a position 0 and name ''
536                    self._markers.append((id, pos, name))
537        except EOFError:
538            print 'Warning: MARK chunk contains only',
539            print len(self._markers),
540            if len(self._markers) == 1: print 'marker',
541            else: print 'markers',
542            print 'instead of', nmarkers
543
544class Aifc_write:
545    # Variables used in this class:
546    #
547    # These variables are user settable through appropriate methods
548    # of this class:
549    # _file -- the open file with methods write(), close(), tell(), seek()
550    #       set through the __init__() method
551    # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
552    #       set through the setcomptype() or setparams() method
553    # _compname -- the human-readable AIFF-C compression type
554    #       set through the setcomptype() or setparams() method
555    # _nchannels -- the number of audio channels
556    #       set through the setnchannels() or setparams() method
557    # _sampwidth -- the number of bytes per audio sample
558    #       set through the setsampwidth() or setparams() method
559    # _framerate -- the sampling frequency
560    #       set through the setframerate() or setparams() method
561    # _nframes -- the number of audio frames written to the header
562    #       set through the setnframes() or setparams() method
563    # _aifc -- whether we're writing an AIFF-C file or an AIFF file
564    #       set through the aifc() method, reset through the
565    #       aiff() method
566    #
567    # These variables are used internally only:
568    # _version -- the AIFF-C version number
569    # _comp -- the compressor from builtin module cl
570    # _nframeswritten -- the number of audio frames actually written
571    # _datalength -- the size of the audio samples written to the header
572    # _datawritten -- the size of the audio samples actually written
573
574    _file = None  # Set here since __del__ checks it
575
576    def __init__(self, f):
577        if isinstance(f, basestring):
578            filename = f
579            f = __builtin__.open(f, 'wb')
580        else:
581            # else, assume it is an open file object already
582            filename = '???'
583        self.initfp(f)
584        if filename[-5:] == '.aiff':
585            self._aifc = 0
586        else:
587            self._aifc = 1
588
589    def initfp(self, file):
590        self._file = file
591        self._version = _AIFC_version
592        self._comptype = 'NONE'
593        self._compname = 'not compressed'
594        self._comp = None
595        self._convert = None
596        self._nchannels = 0
597        self._sampwidth = 0
598        self._framerate = 0
599        self._nframes = 0
600        self._nframeswritten = 0
601        self._datawritten = 0
602        self._datalength = 0
603        self._markers = []
604        self._marklength = 0
605        self._aifc = 1      # AIFF-C is default
606
607    def __del__(self):
608        if self._file:
609            self.close()
610
611    #
612    # User visible methods.
613    #
614    def aiff(self):
615        if self._nframeswritten:
616            raise Error, 'cannot change parameters after starting to write'
617        self._aifc = 0
618
619    def aifc(self):
620        if self._nframeswritten:
621            raise Error, 'cannot change parameters after starting to write'
622        self._aifc = 1
623
624    def setnchannels(self, nchannels):
625        if self._nframeswritten:
626            raise Error, 'cannot change parameters after starting to write'
627        if nchannels < 1:
628            raise Error, 'bad # of channels'
629        self._nchannels = nchannels
630
631    def getnchannels(self):
632        if not self._nchannels:
633            raise Error, 'number of channels not set'
634        return self._nchannels
635
636    def setsampwidth(self, sampwidth):
637        if self._nframeswritten:
638            raise Error, 'cannot change parameters after starting to write'
639        if sampwidth < 1 or sampwidth > 4:
640            raise Error, 'bad sample width'
641        self._sampwidth = sampwidth
642
643    def getsampwidth(self):
644        if not self._sampwidth:
645            raise Error, 'sample width not set'
646        return self._sampwidth
647
648    def setframerate(self, framerate):
649        if self._nframeswritten:
650            raise Error, 'cannot change parameters after starting to write'
651        if framerate <= 0:
652            raise Error, 'bad frame rate'
653        self._framerate = framerate
654
655    def getframerate(self):
656        if not self._framerate:
657            raise Error, 'frame rate not set'
658        return self._framerate
659
660    def setnframes(self, nframes):
661        if self._nframeswritten:
662            raise Error, 'cannot change parameters after starting to write'
663        self._nframes = nframes
664
665    def getnframes(self):
666        return self._nframeswritten
667
668    def setcomptype(self, comptype, compname):
669        if self._nframeswritten:
670            raise Error, 'cannot change parameters after starting to write'
671        if comptype not in ('NONE', 'ULAW', 'ulaw', 'ALAW', 'alaw', 'G722'):
672            raise Error, 'unsupported compression type'
673        self._comptype = comptype
674        self._compname = compname
675
676    def getcomptype(self):
677        return self._comptype
678
679    def getcompname(self):
680        return self._compname
681
682##  def setversion(self, version):
683##      if self._nframeswritten:
684##          raise Error, 'cannot change parameters after starting to write'
685##      self._version = version
686
687    def setparams(self, info):
688        nchannels, sampwidth, framerate, nframes, comptype, compname = info
689        if self._nframeswritten:
690            raise Error, 'cannot change parameters after starting to write'
691        if comptype not in ('NONE', 'ULAW', 'ulaw', 'ALAW', 'alaw', 'G722'):
692            raise Error, 'unsupported compression type'
693        self.setnchannels(nchannels)
694        self.setsampwidth(sampwidth)
695        self.setframerate(framerate)
696        self.setnframes(nframes)
697        self.setcomptype(comptype, compname)
698
699    def getparams(self):
700        if not self._nchannels or not self._sampwidth or not self._framerate:
701            raise Error, 'not all parameters set'
702        return self._nchannels, self._sampwidth, self._framerate, \
703              self._nframes, self._comptype, self._compname
704
705    def setmark(self, id, pos, name):
706        if id <= 0:
707            raise Error, 'marker ID must be > 0'
708        if pos < 0:
709            raise Error, 'marker position must be >= 0'
710        if type(name) != type(''):
711            raise Error, 'marker name must be a string'
712        for i in range(len(self._markers)):
713            if id == self._markers[i][0]:
714                self._markers[i] = id, pos, name
715                return
716        self._markers.append((id, pos, name))
717
718    def getmark(self, id):
719        for marker in self._markers:
720            if id == marker[0]:
721                return marker
722        raise Error, 'marker %r does not exist' % (id,)
723
724    def getmarkers(self):
725        if len(self._markers) == 0:
726            return None
727        return self._markers
728
729    def tell(self):
730        return self._nframeswritten
731
732    def writeframesraw(self, data):
733        self._ensure_header_written(len(data))
734        nframes = len(data) // (self._sampwidth * self._nchannels)
735        if self._convert:
736            data = self._convert(data)
737        self._file.write(data)
738        self._nframeswritten = self._nframeswritten + nframes
739        self._datawritten = self._datawritten + len(data)
740
741    def writeframes(self, data):
742        self.writeframesraw(data)
743        if self._nframeswritten != self._nframes or \
744              self._datalength != self._datawritten:
745            self._patchheader()
746
747    def close(self):
748        if self._file is None:
749            return
750        try:
751            self._ensure_header_written(0)
752            if self._datawritten & 1:
753                # quick pad to even size
754                self._file.write(chr(0))
755                self._datawritten = self._datawritten + 1
756            self._writemarkers()
757            if self._nframeswritten != self._nframes or \
758                  self._datalength != self._datawritten or \
759                  self._marklength:
760                self._patchheader()
761            if self._comp:
762                self._comp.CloseCompressor()
763                self._comp = None
764        finally:
765            # Prevent ref cycles
766            self._convert = None
767            f = self._file
768            self._file = None
769            f.close()
770
771    #
772    # Internal methods.
773    #
774
775    def _comp_data(self, data):
776        import cl
777        dummy = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data))
778        dummy = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data))
779        return self._comp.Compress(self._nframes, data)
780
781    def _lin2ulaw(self, data):
782        import audioop
783        return audioop.lin2ulaw(data, 2)
784
785    def _lin2adpcm(self, data):
786        import audioop
787        if not hasattr(self, '_adpcmstate'):
788            self._adpcmstate = None
789        data, self._adpcmstate = audioop.lin2adpcm(data, 2,
790                               self._adpcmstate)
791        return data
792
793    def _ensure_header_written(self, datasize):
794        if not self._nframeswritten:
795            if self._comptype in ('ULAW', 'ulaw', 'ALAW', 'alaw'):
796                if not self._sampwidth:
797                    self._sampwidth = 2
798                if self._sampwidth != 2:
799                    raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
800            if self._comptype == 'G722':
801                if not self._sampwidth:
802                    self._sampwidth = 2
803                if self._sampwidth != 2:
804                    raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
805            if not self._nchannels:
806                raise Error, '# channels not specified'
807            if not self._sampwidth:
808                raise Error, 'sample width not specified'
809            if not self._framerate:
810                raise Error, 'sampling rate not specified'
811            self._write_header(datasize)
812
813    def _init_compression(self):
814        if self._comptype == 'G722':
815            self._convert = self._lin2adpcm
816            return
817        try:
818            import cl
819        except ImportError:
820            if self._comptype in ('ULAW', 'ulaw'):
821                try:
822                    import audioop
823                    self._convert = self._lin2ulaw
824                    return
825                except ImportError:
826                    pass
827            raise Error, 'cannot write compressed AIFF-C files'
828        if self._comptype in ('ULAW', 'ulaw'):
829            scheme = cl.G711_ULAW
830        elif self._comptype in ('ALAW', 'alaw'):
831            scheme = cl.G711_ALAW
832        else:
833            raise Error, 'unsupported compression type'
834        self._comp = cl.OpenCompressor(scheme)
835        params = [cl.ORIGINAL_FORMAT, 0,
836              cl.BITS_PER_COMPONENT, self._sampwidth * 8,
837              cl.FRAME_RATE, self._framerate,
838              cl.FRAME_BUFFER_SIZE, 100,
839              cl.COMPRESSED_BUFFER_SIZE, 100]
840        if self._nchannels == 1:
841            params[1] = cl.MONO
842        elif self._nchannels == 2:
843            params[1] = cl.STEREO_INTERLEAVED
844        else:
845            raise Error, 'cannot compress more than 2 channels'
846        self._comp.SetParams(params)
847        # the compressor produces a header which we ignore
848        dummy = self._comp.Compress(0, '')
849        self._convert = self._comp_data
850
851    def _write_header(self, initlength):
852        if self._aifc and self._comptype != 'NONE':
853            self._init_compression()
854        self._file.write('FORM')
855        if not self._nframes:
856            self._nframes = initlength // (self._nchannels * self._sampwidth)
857        self._datalength = self._nframes * self._nchannels * self._sampwidth
858        if self._datalength & 1:
859            self._datalength = self._datalength + 1
860        if self._aifc:
861            if self._comptype in ('ULAW', 'ulaw', 'ALAW', 'alaw'):
862                self._datalength = self._datalength // 2
863                if self._datalength & 1:
864                    self._datalength = self._datalength + 1
865            elif self._comptype == 'G722':
866                self._datalength = (self._datalength + 3) // 4
867                if self._datalength & 1:
868                    self._datalength = self._datalength + 1
869        try:
870            self._form_length_pos = self._file.tell()
871        except (AttributeError, IOError):
872            self._form_length_pos = None
873        commlength = self._write_form_length(self._datalength)
874        if self._aifc:
875            self._file.write('AIFC')
876            self._file.write('FVER')
877            _write_ulong(self._file, 4)
878            _write_ulong(self._file, self._version)
879        else:
880            self._file.write('AIFF')
881        self._file.write('COMM')
882        _write_ulong(self._file, commlength)
883        _write_short(self._file, self._nchannels)
884        if self._form_length_pos is not None:
885            self._nframes_pos = self._file.tell()
886        _write_ulong(self._file, self._nframes)
887        if self._comptype in ('ULAW', 'ulaw', 'ALAW', 'alaw', 'G722'):
888            _write_short(self._file, 8)
889        else:
890            _write_short(self._file, self._sampwidth * 8)
891        _write_float(self._file, self._framerate)
892        if self._aifc:
893            self._file.write(self._comptype)
894            _write_string(self._file, self._compname)
895        self._file.write('SSND')
896        if self._form_length_pos is not None:
897            self._ssnd_length_pos = self._file.tell()
898        _write_ulong(self._file, self._datalength + 8)
899        _write_ulong(self._file, 0)
900        _write_ulong(self._file, 0)
901
902    def _write_form_length(self, datalength):
903        if self._aifc:
904            commlength = 18 + 5 + len(self._compname)
905            if commlength & 1:
906                commlength = commlength + 1
907            verslength = 12
908        else:
909            commlength = 18
910            verslength = 0
911        _write_ulong(self._file, 4 + verslength + self._marklength + \
912                     8 + commlength + 16 + datalength)
913        return commlength
914
915    def _patchheader(self):
916        curpos = self._file.tell()
917        if self._datawritten & 1:
918            datalength = self._datawritten + 1
919            self._file.write(chr(0))
920        else:
921            datalength = self._datawritten
922        if datalength == self._datalength and \
923              self._nframes == self._nframeswritten and \
924              self._marklength == 0:
925            self._file.seek(curpos, 0)
926            return
927        self._file.seek(self._form_length_pos, 0)
928        dummy = self._write_form_length(datalength)
929        self._file.seek(self._nframes_pos, 0)
930        _write_ulong(self._file, self._nframeswritten)
931        self._file.seek(self._ssnd_length_pos, 0)
932        _write_ulong(self._file, datalength + 8)
933        self._file.seek(curpos, 0)
934        self._nframes = self._nframeswritten
935        self._datalength = datalength
936
937    def _writemarkers(self):
938        if len(self._markers) == 0:
939            return
940        self._file.write('MARK')
941        length = 2
942        for marker in self._markers:
943            id, pos, name = marker
944            length = length + len(name) + 1 + 6
945            if len(name) & 1 == 0:
946                length = length + 1
947        _write_ulong(self._file, length)
948        self._marklength = length + 8
949        _write_short(self._file, len(self._markers))
950        for marker in self._markers:
951            id, pos, name = marker
952            _write_short(self._file, id)
953            _write_ulong(self._file, pos)
954            _write_string(self._file, name)
955
956def open(f, mode=None):
957    if mode is None:
958        if hasattr(f, 'mode'):
959            mode = f.mode
960        else:
961            mode = 'rb'
962    if mode in ('r', 'rb'):
963        return Aifc_read(f)
964    elif mode in ('w', 'wb'):
965        return Aifc_write(f)
966    else:
967        raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
968
969openfp = open # B/W compatibility
970
971if __name__ == '__main__':
972    import sys
973    if not sys.argv[1:]:
974        sys.argv.append('/usr/demos/data/audio/bach.aiff')
975    fn = sys.argv[1]
976    f = open(fn, 'r')
977    try:
978        print "Reading", fn
979        print "nchannels =", f.getnchannels()
980        print "nframes   =", f.getnframes()
981        print "sampwidth =", f.getsampwidth()
982        print "framerate =", f.getframerate()
983        print "comptype  =", f.getcomptype()
984        print "compname  =", f.getcompname()
985        if sys.argv[2:]:
986            gn = sys.argv[2]
987            print "Writing", gn
988            g = open(gn, 'w')
989            try:
990                g.setparams(f.getparams())
991                while 1:
992                    data = f.readframes(1024)
993                    if not data:
994                        break
995                    g.writeframes(data)
996            finally:
997                g.close()
998            print "Done."
999    finally:
1000        f.close()
1001