Vega strike Python Modules doc  0.5.1
Documentation of the " Modules " folder of Vega strike
 All Data Structures Namespaces Files Functions Variables
wave.py
Go to the documentation of this file.
1 """Stuff to parse WAVE files.
2 
3 Usage.
4 
5 Reading WAVE files:
6  f = wave.open(file, 'r')
7 where file is either the name of a file or an open file pointer.
8 The open file pointer must have methods read(), seek(), and close().
9 When the setpos() and rewind() methods are not used, the seek()
10 method is not necessary.
11 
12 This 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)
32 The position returned by tell() and the position given to setpos()
33 are compatible and have nothing to do with the actual position in the
34 file.
35 The close() method is called automatically when the class instance
36 is destroyed.
37 
38 Writing WAVE files:
39  f = wave.open(file, 'w')
40 where file is either the name of a file or an open file pointer.
41 The open file pointer must have methods write(), tell(), seek(), and
42 close().
43 
44 This 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
62 You should set the parameters before the first writeframesraw or
63 writeframes. The total number of frames does not need to be set,
64 but when it is set to the correct value, the header does not have to
65 be patched up.
66 It is best to first set all parameters, perhaps possibly the
67 compression type, and then write audio frames using writeframesraw.
68 When all frames have been written, either call writeframes('') or
69 close() to patch up the sizes in the header.
70 The close() method is called automatically when the class instance
71 is destroyed.
72 """
73 
74 import __builtin__
75 
76 __all__ = ["open", "openfp", "Error"]
77 
78 class Error(Exception):
79  pass
80 
81 WAVE_FORMAT_PCM = 0x0001
82 
83 _array_fmts = None, 'b', 'h', None, 'l'
84 
85 # Determine endian-ness
86 import struct
87 if struct.pack("h", 1) == "\000\001":
88  big_endian = 1
89 else:
90  big_endian = 0
91 
92 from chunk import Chunk
93 
94 class Wave_read:
95  """Variables used in this class:
96 
97  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
116 
117  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  """
124 
125  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
149  self._nframes = chunk.chunksize // self._framesize
150  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'
155 
156  def __init__(self, f):
157  self._i_opened_the_file = None
158  if type(f) == type(''):
159  f = __builtin__.open(f, 'rb')
160  self._i_opened_the_file = f
161  # else, assume it is an open file object already
162  self.initfp(f)
163 
164  def __del__(self):
165  self.close()
166  #
167  # User visible methods.
168  #
169  def getfp(self):
170  return self._file
171 
172  def rewind(self):
173  self._data_seek_needed = 1
174  self._soundpos = 0
175 
176  def close(self):
177  if self._i_opened_the_file:
178  self._i_opened_the_file.close()
179  self._i_opened_the_file = None
180  self._file = None
181 
182  def tell(self):
183  return self._soundpos
184 
185  def getnchannels(self):
186  return self._nchannels
187 
188  def getnframes(self):
189  return self._nframes
190 
191  def getsampwidth(self):
192  return self._sampwidth
193 
194  def getframerate(self):
195  return self._framerate
196 
197  def getcomptype(self):
198  return self._comptype
199 
200  def getcompname(self):
201  return self._compname
202 
203  def getparams(self):
204  return self.getnchannels(), self.getsampwidth(), \
205  self.getframerate(), self.getnframes(), \
206  self.getcomptype(), self.getcompname()
207 
208  def getmarkers(self):
209  return None
210 
211  def getmark(self, id):
212  raise Error, 'no marks'
213 
214  def setpos(self, pos):
215  if pos < 0 or pos > self._nframes:
216  raise Error, 'position not in range'
217  self._soundpos = pos
218  self._data_seek_needed = 1
219 
220  def readframes(self, nframes):
221  if self._data_seek_needed:
222  self._data_chunk.seek(0, 0)
223  pos = self._soundpos * self._framesize
224  if pos:
225  self._data_chunk.seek(pos, 0)
226  self._data_seek_needed = 0
227  if nframes == 0:
228  return ''
229  if self._sampwidth > 1 and big_endian:
230  # unfortunately the fromfile() method does not take
231  # something that only looks like a file object, so
232  # we have to reach into the innards of the chunk object
233  import array
234  chunk = self._data_chunk
235  data = array.array(_array_fmts[self._sampwidth])
236  nitems = nframes * self._nchannels
237  if nitems * self._sampwidth > chunk.chunksize - chunk.size_read:
238  nitems = (chunk.chunksize - chunk.size_read) / self._sampwidth
239  data.fromfile(chunk.file.file, nitems)
240  # "tell" data chunk how much was read
241  chunk.size_read = chunk.size_read + nitems * self._sampwidth
242  # do the same for the outermost chunk
243  chunk = chunk.file
244  chunk.size_read = chunk.size_read + nitems * self._sampwidth
245  data.byteswap()
246  data = data.tostring()
247  else:
248  data = self._data_chunk.read(nframes * self._framesize)
249  if self._convert and data:
250  data = self._convert(data)
251  self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth)
252  return data
253 
254  #
255  # Internal methods.
256  #
257 
258  def _read_fmt_chunk(self, chunk):
259  wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack('<hhllh', chunk.read(14))
260  if wFormatTag == WAVE_FORMAT_PCM:
261  sampwidth = struct.unpack('<h', chunk.read(2))[0]
262  self._sampwidth = (sampwidth + 7) // 8
263  else:
264  raise Error, 'unknown format: ' + `wFormatTag`
265  self._framesize = self._nchannels * self._sampwidth
266  self._comptype = 'NONE'
267  self._compname = 'not compressed'
268 
269 class Wave_write:
270  """Variables used in this class:
271 
272  These variables are user settable through appropriate methods
273  of this class:
274  _file -- the open file with methods write(), close(), tell(), seek()
275  set through the __init__() method
276  _comptype -- the AIFF-C compression type ('NONE' in AIFF)
277  set through the setcomptype() or setparams() method
278  _compname -- the human-readable AIFF-C compression type
279  set through the setcomptype() or setparams() method
280  _nchannels -- the number of audio channels
281  set through the setnchannels() or setparams() method
282  _sampwidth -- the number of bytes per audio sample
283  set through the setsampwidth() or setparams() method
284  _framerate -- the sampling frequency
285  set through the setframerate() or setparams() method
286  _nframes -- the number of audio frames written to the header
287  set through the setnframes() or setparams() method
288 
289  These variables are used internally only:
290  _datalength -- the size of the audio samples written to the header
291  _nframeswritten -- the number of frames actually written
292  _datawritten -- the size of the audio samples actually written
293  """
294 
295  def __init__(self, f):
296  self._i_opened_the_file = None
297  if type(f) == type(''):
298  f = __builtin__.open(f, 'wb')
299  self._i_opened_the_file = f
300  self.initfp(f)
301 
302  def initfp(self, file):
303  self._file = file
304  self._convert = None
305  self._nchannels = 0
306  self._sampwidth = 0
307  self._framerate = 0
308  self._nframes = 0
309  self._nframeswritten = 0
310  self._datawritten = 0
311  self._datalength = 0
312 
313  def __del__(self):
314  self.close()
315 
316  #
317  # User visible methods.
318  #
319  def setnchannels(self, nchannels):
320  if self._datawritten:
321  raise Error, 'cannot change parameters after starting to write'
322  if nchannels < 1:
323  raise Error, 'bad # of channels'
324  self._nchannels = nchannels
325 
326  def getnchannels(self):
327  if not self._nchannels:
328  raise Error, 'number of channels not set'
329  return self._nchannels
330 
331  def setsampwidth(self, sampwidth):
332  if self._datawritten:
333  raise Error, 'cannot change parameters after starting to write'
334  if sampwidth < 1 or sampwidth > 4:
335  raise Error, 'bad sample width'
336  self._sampwidth = sampwidth
337 
338  def getsampwidth(self):
339  if not self._sampwidth:
340  raise Error, 'sample width not set'
341  return self._sampwidth
342 
343  def setframerate(self, framerate):
344  if self._datawritten:
345  raise Error, 'cannot change parameters after starting to write'
346  if framerate <= 0:
347  raise Error, 'bad frame rate'
348  self._framerate = framerate
349 
350  def getframerate(self):
351  if not self._framerate:
352  raise Error, 'frame rate not set'
353  return self._framerate
354 
355  def setnframes(self, nframes):
356  if self._datawritten:
357  raise Error, 'cannot change parameters after starting to write'
358  self._nframes = nframes
359 
360  def getnframes(self):
361  return self._nframeswritten
362 
363  def setcomptype(self, comptype, compname):
364  if self._datawritten:
365  raise Error, 'cannot change parameters after starting to write'
366  if comptype not in ('NONE',):
367  raise Error, 'unsupported compression type'
368  self._comptype = comptype
369  self._compname = compname
370 
371  def getcomptype(self):
372  return self._comptype
373 
374  def getcompname(self):
375  return self._compname
376 
377  def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
378  if self._datawritten:
379  raise Error, 'cannot change parameters after starting to write'
380  self.setnchannels(nchannels)
381  self.setsampwidth(sampwidth)
382  self.setframerate(framerate)
383  self.setnframes(nframes)
384  self.setcomptype(comptype, compname)
385 
386  def getparams(self):
387  if not self._nchannels or not self._sampwidth or not self._framerate:
388  raise Error, 'not all parameters set'
389  return self._nchannels, self._sampwidth, self._framerate, \
390  self._nframes, self._comptype, self._compname
391 
392  def setmark(self, id, pos, name):
393  raise Error, 'setmark() not supported'
394 
395  def getmark(self, id):
396  raise Error, 'no marks'
397 
398  def getmarkers(self):
399  return None
400 
401  def tell(self):
402  return self._nframeswritten
403 
404  def writeframesraw(self, data):
405  self._ensure_header_written(len(data))
406  nframes = len(data) // (self._sampwidth * self._nchannels)
407  if self._convert:
408  data = self._convert(data)
409  if self._sampwidth > 1 and big_endian:
410  import array
411  data = array.array(_array_fmts[self._sampwidth], data)
412  data.byteswap()
413  data.tofile(self._file)
414  self._datawritten = self._datawritten + len(data) * self._sampwidth
415  else:
416  self._file.write(data)
417  self._datawritten = self._datawritten + len(data)
418  self._nframeswritten = self._nframeswritten + nframes
419 
420  def writeframes(self, data):
421  self.writeframesraw(data)
422  if self._datalength != self._datawritten:
423  self._patchheader()
424 
425  def close(self):
426  if self._file:
427  self._ensure_header_written(0)
428  if self._datalength != self._datawritten:
429  self._patchheader()
430  self._file.flush()
431  self._file = None
432  if self._i_opened_the_file:
433  self._i_opened_the_file.close()
434  self._i_opened_the_file = None
435 
436  #
437  # Internal methods.
438  #
439 
440  def _ensure_header_written(self, datasize):
441  if not self._datawritten:
442  if not self._nchannels:
443  raise Error, '# channels not specified'
444  if not self._sampwidth:
445  raise Error, 'sample width not specified'
446  if not self._framerate:
447  raise Error, 'sampling rate not specified'
448  self._write_header(datasize)
449 
450  def _write_header(self, initlength):
451  self._file.write('RIFF')
452  if not self._nframes:
453  self._nframes = initlength / (self._nchannels * self._sampwidth)
454  self._datalength = self._nframes * self._nchannels * self._sampwidth
455  self._form_length_pos = self._file.tell()
456  self._file.write(struct.pack('<l4s4slhhllhh4s',
457  36 + self._datalength, 'WAVE', 'fmt ', 16,
458  WAVE_FORMAT_PCM, self._nchannels, self._framerate,
459  self._nchannels * self._framerate * self._sampwidth,
460  self._nchannels * self._sampwidth,
461  self._sampwidth * 8, 'data'))
462  self._data_length_pos = self._file.tell()
463  self._file.write(struct.pack('<l', self._datalength))
464 
465  def _patchheader(self):
466  if self._datawritten == self._datalength:
467  return
468  curpos = self._file.tell()
469  self._file.seek(self._form_length_pos, 0)
470  self._file.write(struct.pack('<l', 36 + self._datawritten))
471  self._file.seek(self._data_length_pos, 0)
472  self._file.write(struct.pack('<l', self._datawritten))
473  self._file.seek(curpos, 0)
474  self._datalength = self._datawritten
475 
476 def open(f, mode=None):
477  if mode is None:
478  if hasattr(f, 'mode'):
479  mode = f.mode
480  else:
481  mode = 'rb'
482  if mode in ('r', 'rb'):
483  return Wave_read(f)
484  elif mode in ('w', 'wb'):
485  return Wave_write(f)
486  else:
487  raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
488 
489 openfp = open # B/W compatibility