blob: d6eef782412faa7cc7228ab052339e468103ed58 [file] [log] [blame]
Guido van Rossume7b146f2000-02-04 15:28:42 +00001"""Stuff to parse WAVE files.
2
3Usage.
4
5Reading WAVE files:
6 f = wave.open(file, 'r')
7where file is either the name of a file or an open file pointer.
8The open file pointer must have methods read(), seek(), and close().
9When the setpos() and rewind() methods are not used, the seek()
10method is not necessary.
11
12This returns an instance of a class with the following public methods:
13 getnchannels() -- returns number of audio channels (1 for
14 mono, 2 for stereo)
15 getsampwidth() -- returns sample width in bytes
16 getframerate() -- returns sampling frequency
17 getnframes() -- returns number of audio frames
18 getcomptype() -- returns compression type ('NONE' for linear samples)
19 getcompname() -- returns human-readable version of
20 compression type ('not compressed' linear samples)
21 getparams() -- returns a tuple consisting of all of the
22 above in the above order
23 getmarkers() -- returns None (for compatibility with the
24 aifc module)
25 getmark(id) -- raises an error since the mark does not
26 exist (for compatibility with the aifc module)
27 readframes(n) -- returns at most n frames of audio
28 rewind() -- rewind to the beginning of the audio stream
29 setpos(pos) -- seek to the specified position
30 tell() -- return the current position
31 close() -- close the instance (make it unusable)
32The position returned by tell() and the position given to setpos()
Thomas Wouters7e474022000-07-16 12:04:32 +000033are compatible and have nothing to do with the actual position in the
Guido van Rossume7b146f2000-02-04 15:28:42 +000034file.
35The close() method is called automatically when the class instance
36is destroyed.
37
38Writing WAVE files:
39 f = wave.open(file, 'w')
40where file is either the name of a file or an open file pointer.
41The open file pointer must have methods write(), tell(), seek(), and
42close().
43
44This returns an instance of a class with the following public methods:
45 setnchannels(n) -- set the number of channels
46 setsampwidth(n) -- set the sample width
47 setframerate(n) -- set the frame rate
48 setnframes(n) -- set the number of frames
49 setcomptype(type, name)
50 -- set the compression type and the
51 human-readable compression type
52 setparams(tuple)
53 -- set all parameters at once
54 tell() -- return current position in output file
55 writeframesraw(data)
56 -- write audio frames without pathing up the
57 file header
58 writeframes(data)
59 -- write audio frames and patch up the file header
60 close() -- patch up the file header and close the
61 output file
62You should set the parameters before the first writeframesraw or
63writeframes. The total number of frames does not need to be set,
64but when it is set to the correct value, the header does not have to
65be patched up.
66It is best to first set all parameters, perhaps possibly the
67compression type, and then write audio frames using writeframesraw.
68When all frames have been written, either call writeframes('') or
69close() to patch up the sizes in the header.
70The close() method is called automatically when the class instance
71is destroyed.
72"""
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000073
74import __builtin__
75
Skip Montanaro40fc1602001-03-01 04:27:19 +000076__all__ = ["open", "openfp", "Error"]
77
Fred Drake9b8d8012000-08-17 04:45:13 +000078class Error(Exception):
79 pass
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000080
81WAVE_FORMAT_PCM = 0x0001
82
Serhiy Storchaka71378032013-10-17 23:03:48 +030083_array_fmts = None, 'b', 'h', None, 'i'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000084
Guido van Rossumebb9c921999-02-05 22:28:17 +000085# Determine endian-ness
86import struct
87if struct.pack("h", 1) == "\000\001":
Guido van Rossume7b146f2000-02-04 15:28:42 +000088 big_endian = 1
Guido van Rossumebb9c921999-02-05 22:28:17 +000089else:
Guido van Rossume7b146f2000-02-04 15:28:42 +000090 big_endian = 0
Guido van Rossumebb9c921999-02-05 22:28:17 +000091
Guido van Rossum3601e881999-08-26 15:50:43 +000092from chunk import Chunk
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000093
94class Wave_read:
Guido van Rossume7b146f2000-02-04 15:28:42 +000095 """Variables used in this class:
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000096
Guido van Rossume7b146f2000-02-04 15:28:42 +000097 These variables are available to the user though appropriate
98 methods of this class:
99 _file -- the open file with methods read(), close(), and seek()
100 set through the __init__() method
101 _nchannels -- the number of audio channels
102 available through the getnchannels() method
103 _nframes -- the number of audio frames
104 available through the getnframes() method
105 _sampwidth -- the number of bytes per audio sample
106 available through the getsampwidth() method
107 _framerate -- the sampling frequency
108 available through the getframerate() method
109 _comptype -- the AIFF-C compression type ('NONE' if AIFF)
110 available through the getcomptype() method
111 _compname -- the human-readable AIFF-C compression type
112 available through the getcomptype() method
113 _soundpos -- the position in the audio stream
114 available through the tell() method, set through the
115 setpos() method
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000116
Guido van Rossume7b146f2000-02-04 15:28:42 +0000117 These variables are used internally only:
118 _fmt_chunk_read -- 1 iff the FMT chunk has been read
119 _data_seek_needed -- 1 iff positioned correctly in audio
120 file for readframes()
121 _data_chunk -- instantiation of a chunk class for the DATA chunk
122 _framesize -- size of one frame in the file
123 """
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000124
Guido van Rossume7b146f2000-02-04 15:28:42 +0000125 def initfp(self, file):
126 self._convert = None
127 self._soundpos = 0
128 self._file = Chunk(file, bigendian = 0)
129 if self._file.getname() != 'RIFF':
130 raise Error, 'file does not start with RIFF id'
131 if self._file.read(4) != 'WAVE':
132 raise Error, 'not a WAVE file'
133 self._fmt_chunk_read = 0
134 self._data_chunk = None
135 while 1:
136 self._data_seek_needed = 1
137 try:
138 chunk = Chunk(self._file, bigendian = 0)
139 except EOFError:
140 break
141 chunkname = chunk.getname()
142 if chunkname == 'fmt ':
143 self._read_fmt_chunk(chunk)
144 self._fmt_chunk_read = 1
145 elif chunkname == 'data':
146 if not self._fmt_chunk_read:
147 raise Error, 'data chunk before fmt chunk'
148 self._data_chunk = chunk
Guido van Rossum54e54c62001-09-04 19:14:14 +0000149 self._nframes = chunk.chunksize // self._framesize
Guido van Rossume7b146f2000-02-04 15:28:42 +0000150 self._data_seek_needed = 0
151 break
152 chunk.skip()
153 if not self._fmt_chunk_read or not self._data_chunk:
154 raise Error, 'fmt chunk and/or data chunk missing'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000155
Guido van Rossume7b146f2000-02-04 15:28:42 +0000156 def __init__(self, f):
Tim Peterscfc41782000-10-09 23:43:55 +0000157 self._i_opened_the_file = None
Neal Norwitz0e67fd42004-08-01 22:48:06 +0000158 if isinstance(f, basestring):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000159 f = __builtin__.open(f, 'rb')
Tim Peterscfc41782000-10-09 23:43:55 +0000160 self._i_opened_the_file = f
Guido van Rossume7b146f2000-02-04 15:28:42 +0000161 # else, assume it is an open file object already
Georg Brandlab1f4672007-03-15 07:41:30 +0000162 try:
163 self.initfp(f)
164 except:
165 if self._i_opened_the_file:
166 f.close()
167 raise
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000168
Tim Peterscfc41782000-10-09 23:43:55 +0000169 def __del__(self):
170 self.close()
Guido van Rossume7b146f2000-02-04 15:28:42 +0000171 #
172 # User visible methods.
173 #
174 def getfp(self):
175 return self._file
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000176
Guido van Rossume7b146f2000-02-04 15:28:42 +0000177 def rewind(self):
178 self._data_seek_needed = 1
179 self._soundpos = 0
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000180
Guido van Rossume7b146f2000-02-04 15:28:42 +0000181 def close(self):
Tim Peterscfc41782000-10-09 23:43:55 +0000182 if self._i_opened_the_file:
183 self._i_opened_the_file.close()
184 self._i_opened_the_file = None
Guido van Rossume7b146f2000-02-04 15:28:42 +0000185 self._file = None
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000186
Guido van Rossume7b146f2000-02-04 15:28:42 +0000187 def tell(self):
188 return self._soundpos
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000189
Guido van Rossume7b146f2000-02-04 15:28:42 +0000190 def getnchannels(self):
191 return self._nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000192
Guido van Rossume7b146f2000-02-04 15:28:42 +0000193 def getnframes(self):
194 return self._nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000195
Guido van Rossume7b146f2000-02-04 15:28:42 +0000196 def getsampwidth(self):
197 return self._sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000198
Guido van Rossume7b146f2000-02-04 15:28:42 +0000199 def getframerate(self):
200 return self._framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000201
Guido van Rossume7b146f2000-02-04 15:28:42 +0000202 def getcomptype(self):
203 return self._comptype
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000204
Guido van Rossume7b146f2000-02-04 15:28:42 +0000205 def getcompname(self):
206 return self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000207
Guido van Rossume7b146f2000-02-04 15:28:42 +0000208 def getparams(self):
209 return self.getnchannels(), self.getsampwidth(), \
210 self.getframerate(), self.getnframes(), \
211 self.getcomptype(), self.getcompname()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000212
Guido van Rossume7b146f2000-02-04 15:28:42 +0000213 def getmarkers(self):
214 return None
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000215
Guido van Rossume7b146f2000-02-04 15:28:42 +0000216 def getmark(self, id):
217 raise Error, 'no marks'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000218
Guido van Rossume7b146f2000-02-04 15:28:42 +0000219 def setpos(self, pos):
220 if pos < 0 or pos > self._nframes:
221 raise Error, 'position not in range'
222 self._soundpos = pos
223 self._data_seek_needed = 1
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000224
Guido van Rossume7b146f2000-02-04 15:28:42 +0000225 def readframes(self, nframes):
226 if self._data_seek_needed:
227 self._data_chunk.seek(0, 0)
228 pos = self._soundpos * self._framesize
229 if pos:
230 self._data_chunk.seek(pos, 0)
231 self._data_seek_needed = 0
232 if nframes == 0:
233 return ''
234 if self._sampwidth > 1 and big_endian:
235 # unfortunately the fromfile() method does not take
236 # something that only looks like a file object, so
237 # we have to reach into the innards of the chunk object
238 import array
239 chunk = self._data_chunk
240 data = array.array(_array_fmts[self._sampwidth])
Serhiy Storchaka71378032013-10-17 23:03:48 +0300241 assert data.itemsize == self._sampwidth
Guido van Rossume7b146f2000-02-04 15:28:42 +0000242 nitems = nframes * self._nchannels
243 if nitems * self._sampwidth > chunk.chunksize - chunk.size_read:
244 nitems = (chunk.chunksize - chunk.size_read) / self._sampwidth
245 data.fromfile(chunk.file.file, nitems)
246 # "tell" data chunk how much was read
247 chunk.size_read = chunk.size_read + nitems * self._sampwidth
248 # do the same for the outermost chunk
249 chunk = chunk.file
250 chunk.size_read = chunk.size_read + nitems * self._sampwidth
251 data.byteswap()
252 data = data.tostring()
253 else:
254 data = self._data_chunk.read(nframes * self._framesize)
255 if self._convert and data:
256 data = self._convert(data)
Guido van Rossum54e54c62001-09-04 19:14:14 +0000257 self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth)
Guido van Rossume7b146f2000-02-04 15:28:42 +0000258 return data
259
260 #
261 # Internal methods.
262 #
263
264 def _read_fmt_chunk(self, chunk):
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100265 wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack('<HHLLH', chunk.read(14))
Guido van Rossume7b146f2000-02-04 15:28:42 +0000266 if wFormatTag == WAVE_FORMAT_PCM:
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100267 sampwidth = struct.unpack('<H', chunk.read(2))[0]
Guido van Rossum54e54c62001-09-04 19:14:14 +0000268 self._sampwidth = (sampwidth + 7) // 8
Guido van Rossume7b146f2000-02-04 15:28:42 +0000269 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000270 raise Error, 'unknown format: %r' % (wFormatTag,)
Guido van Rossume7b146f2000-02-04 15:28:42 +0000271 self._framesize = self._nchannels * self._sampwidth
272 self._comptype = 'NONE'
273 self._compname = 'not compressed'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000274
275class Wave_write:
Guido van Rossume7b146f2000-02-04 15:28:42 +0000276 """Variables used in this class:
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000277
Guido van Rossume7b146f2000-02-04 15:28:42 +0000278 These variables are user settable through appropriate methods
279 of this class:
280 _file -- the open file with methods write(), close(), tell(), seek()
281 set through the __init__() method
282 _comptype -- the AIFF-C compression type ('NONE' in AIFF)
283 set through the setcomptype() or setparams() method
284 _compname -- the human-readable AIFF-C compression type
285 set through the setcomptype() or setparams() method
286 _nchannels -- the number of audio channels
287 set through the setnchannels() or setparams() method
288 _sampwidth -- the number of bytes per audio sample
289 set through the setsampwidth() or setparams() method
290 _framerate -- the sampling frequency
291 set through the setframerate() or setparams() method
292 _nframes -- the number of audio frames written to the header
293 set through the setnframes() or setparams() method
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000294
Guido van Rossume7b146f2000-02-04 15:28:42 +0000295 These variables are used internally only:
296 _datalength -- the size of the audio samples written to the header
297 _nframeswritten -- the number of frames actually written
298 _datawritten -- the size of the audio samples actually written
299 """
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000300
Guido van Rossume7b146f2000-02-04 15:28:42 +0000301 def __init__(self, f):
Tim Peterscfc41782000-10-09 23:43:55 +0000302 self._i_opened_the_file = None
Neal Norwitz0e67fd42004-08-01 22:48:06 +0000303 if isinstance(f, basestring):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000304 f = __builtin__.open(f, 'wb')
Tim Peterscfc41782000-10-09 23:43:55 +0000305 self._i_opened_the_file = f
Georg Brandlab1f4672007-03-15 07:41:30 +0000306 try:
307 self.initfp(f)
308 except:
309 if self._i_opened_the_file:
310 f.close()
311 raise
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000312
Guido van Rossume7b146f2000-02-04 15:28:42 +0000313 def initfp(self, file):
314 self._file = file
315 self._convert = None
316 self._nchannels = 0
317 self._sampwidth = 0
318 self._framerate = 0
319 self._nframes = 0
320 self._nframeswritten = 0
321 self._datawritten = 0
322 self._datalength = 0
Georg Brandlef805a62010-11-20 11:29:09 +0000323 self._headerwritten = False
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000324
Guido van Rossume7b146f2000-02-04 15:28:42 +0000325 def __del__(self):
Tim Peterscfc41782000-10-09 23:43:55 +0000326 self.close()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000327
Guido van Rossume7b146f2000-02-04 15:28:42 +0000328 #
329 # User visible methods.
330 #
331 def setnchannels(self, nchannels):
332 if self._datawritten:
333 raise Error, 'cannot change parameters after starting to write'
334 if nchannels < 1:
335 raise Error, 'bad # of channels'
336 self._nchannels = nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000337
Guido van Rossume7b146f2000-02-04 15:28:42 +0000338 def getnchannels(self):
339 if not self._nchannels:
340 raise Error, 'number of channels not set'
341 return self._nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000342
Guido van Rossume7b146f2000-02-04 15:28:42 +0000343 def setsampwidth(self, sampwidth):
344 if self._datawritten:
345 raise Error, 'cannot change parameters after starting to write'
346 if sampwidth < 1 or sampwidth > 4:
347 raise Error, 'bad sample width'
348 self._sampwidth = sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000349
Guido van Rossume7b146f2000-02-04 15:28:42 +0000350 def getsampwidth(self):
351 if not self._sampwidth:
352 raise Error, 'sample width not set'
353 return self._sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000354
Guido van Rossume7b146f2000-02-04 15:28:42 +0000355 def setframerate(self, framerate):
356 if self._datawritten:
357 raise Error, 'cannot change parameters after starting to write'
358 if framerate <= 0:
359 raise Error, 'bad frame rate'
360 self._framerate = framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000361
Guido van Rossume7b146f2000-02-04 15:28:42 +0000362 def getframerate(self):
363 if not self._framerate:
364 raise Error, 'frame rate not set'
365 return self._framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000366
Guido van Rossume7b146f2000-02-04 15:28:42 +0000367 def setnframes(self, nframes):
368 if self._datawritten:
369 raise Error, 'cannot change parameters after starting to write'
370 self._nframes = nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000371
Guido van Rossume7b146f2000-02-04 15:28:42 +0000372 def getnframes(self):
373 return self._nframeswritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000374
Guido van Rossume7b146f2000-02-04 15:28:42 +0000375 def setcomptype(self, comptype, compname):
376 if self._datawritten:
377 raise Error, 'cannot change parameters after starting to write'
378 if comptype not in ('NONE',):
379 raise Error, 'unsupported compression type'
380 self._comptype = comptype
381 self._compname = compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000382
Guido van Rossume7b146f2000-02-04 15:28:42 +0000383 def getcomptype(self):
384 return self._comptype
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000385
Guido van Rossume7b146f2000-02-04 15:28:42 +0000386 def getcompname(self):
387 return self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000388
Antoine Pitroub9d49632010-01-04 23:22:44 +0000389 def setparams(self, params):
390 nchannels, sampwidth, framerate, nframes, comptype, compname = params
Guido van Rossume7b146f2000-02-04 15:28:42 +0000391 if self._datawritten:
392 raise Error, 'cannot change parameters after starting to write'
393 self.setnchannels(nchannels)
394 self.setsampwidth(sampwidth)
395 self.setframerate(framerate)
396 self.setnframes(nframes)
397 self.setcomptype(comptype, compname)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000398
Guido van Rossume7b146f2000-02-04 15:28:42 +0000399 def getparams(self):
400 if not self._nchannels or not self._sampwidth or not self._framerate:
401 raise Error, 'not all parameters set'
402 return self._nchannels, self._sampwidth, self._framerate, \
403 self._nframes, self._comptype, self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000404
Guido van Rossume7b146f2000-02-04 15:28:42 +0000405 def setmark(self, id, pos, name):
406 raise Error, 'setmark() not supported'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000407
Guido van Rossume7b146f2000-02-04 15:28:42 +0000408 def getmark(self, id):
409 raise Error, 'no marks'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000410
Guido van Rossume7b146f2000-02-04 15:28:42 +0000411 def getmarkers(self):
412 return None
Tim Peterse1190062001-01-15 03:34:38 +0000413
Guido van Rossume7b146f2000-02-04 15:28:42 +0000414 def tell(self):
415 return self._nframeswritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000416
Guido van Rossume7b146f2000-02-04 15:28:42 +0000417 def writeframesraw(self, data):
418 self._ensure_header_written(len(data))
Guido van Rossum54e54c62001-09-04 19:14:14 +0000419 nframes = len(data) // (self._sampwidth * self._nchannels)
Guido van Rossume7b146f2000-02-04 15:28:42 +0000420 if self._convert:
421 data = self._convert(data)
422 if self._sampwidth > 1 and big_endian:
423 import array
424 data = array.array(_array_fmts[self._sampwidth], data)
Serhiy Storchaka71378032013-10-17 23:03:48 +0300425 assert data.itemsize == self._sampwidth
Guido van Rossume7b146f2000-02-04 15:28:42 +0000426 data.byteswap()
427 data.tofile(self._file)
428 self._datawritten = self._datawritten + len(data) * self._sampwidth
429 else:
430 self._file.write(data)
431 self._datawritten = self._datawritten + len(data)
432 self._nframeswritten = self._nframeswritten + nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000433
Guido van Rossume7b146f2000-02-04 15:28:42 +0000434 def writeframes(self, data):
435 self.writeframesraw(data)
436 if self._datalength != self._datawritten:
437 self._patchheader()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000438
Guido van Rossume7b146f2000-02-04 15:28:42 +0000439 def close(self):
Tim Peterscfc41782000-10-09 23:43:55 +0000440 if self._file:
Serhiy Storchaka499d8212013-10-12 21:35:33 +0300441 try:
442 self._ensure_header_written(0)
443 if self._datalength != self._datawritten:
444 self._patchheader()
445 self._file.flush()
446 finally:
447 self._file = None
Tim Peterscfc41782000-10-09 23:43:55 +0000448 if self._i_opened_the_file:
449 self._i_opened_the_file.close()
450 self._i_opened_the_file = None
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000451
Guido van Rossume7b146f2000-02-04 15:28:42 +0000452 #
453 # Internal methods.
454 #
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000455
Guido van Rossume7b146f2000-02-04 15:28:42 +0000456 def _ensure_header_written(self, datasize):
Georg Brandlef805a62010-11-20 11:29:09 +0000457 if not self._headerwritten:
Guido van Rossume7b146f2000-02-04 15:28:42 +0000458 if not self._nchannels:
459 raise Error, '# channels not specified'
460 if not self._sampwidth:
461 raise Error, 'sample width not specified'
462 if not self._framerate:
463 raise Error, 'sampling rate not specified'
464 self._write_header(datasize)
465
466 def _write_header(self, initlength):
Georg Brandlef805a62010-11-20 11:29:09 +0000467 assert not self._headerwritten
Guido van Rossume7b146f2000-02-04 15:28:42 +0000468 self._file.write('RIFF')
469 if not self._nframes:
470 self._nframes = initlength / (self._nchannels * self._sampwidth)
471 self._datalength = self._nframes * self._nchannels * self._sampwidth
472 self._form_length_pos = self._file.tell()
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100473 self._file.write(struct.pack('<L4s4sLHHLLHH4s',
Guido van Rossume7b146f2000-02-04 15:28:42 +0000474 36 + self._datalength, 'WAVE', 'fmt ', 16,
475 WAVE_FORMAT_PCM, self._nchannels, self._framerate,
476 self._nchannels * self._framerate * self._sampwidth,
477 self._nchannels * self._sampwidth,
478 self._sampwidth * 8, 'data'))
479 self._data_length_pos = self._file.tell()
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100480 self._file.write(struct.pack('<L', self._datalength))
Georg Brandlef805a62010-11-20 11:29:09 +0000481 self._headerwritten = True
Guido van Rossume7b146f2000-02-04 15:28:42 +0000482
483 def _patchheader(self):
Georg Brandlef805a62010-11-20 11:29:09 +0000484 assert self._headerwritten
Guido van Rossume7b146f2000-02-04 15:28:42 +0000485 if self._datawritten == self._datalength:
486 return
487 curpos = self._file.tell()
488 self._file.seek(self._form_length_pos, 0)
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100489 self._file.write(struct.pack('<L', 36 + self._datawritten))
Guido van Rossume7b146f2000-02-04 15:28:42 +0000490 self._file.seek(self._data_length_pos, 0)
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100491 self._file.write(struct.pack('<L', self._datawritten))
Guido van Rossume7b146f2000-02-04 15:28:42 +0000492 self._file.seek(curpos, 0)
493 self._datalength = self._datawritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000494
Fred Drakef9607821999-06-17 15:18:47 +0000495def open(f, mode=None):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000496 if mode is None:
497 if hasattr(f, 'mode'):
498 mode = f.mode
499 else:
500 mode = 'rb'
501 if mode in ('r', 'rb'):
502 return Wave_read(f)
503 elif mode in ('w', 'wb'):
504 return Wave_write(f)
505 else:
506 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000507
508openfp = open # B/W compatibility