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