Package gmisclib :: Module wavio
[frames] | no frames]

Source Code for Module gmisclib.wavio

  1  #!/usr/bin/env python 
  2   
  3  """When run as a script: 
  4  python ~/lib/wavio.py [-g gain] -wavin|-wavout infile outfile 
  5  Reads or writes .wav files from any format supported by 
  6  gpkimgclass.py . 
  7   
  8  As a library, allows reading of class gpk_img data from a .WAV 
  9  file, and writing class gpk_img to a .WAV file. 
 10  """ 
 11   
 12  import wave 
 13  import gpkimgclass 
 14  import numpy 
 15   
 16  OV_ERROR = 0 #: Cause L{write} to raise an error on overflow. 
 17  OV_LIMIT = 1 #: Cause L{write} to truncate overflows. 
 18  OV_IGNORE = 2 #: Cause L{write} to silently ignore overflows. 
 19  T_ERROR = 0 #: Cause L{read} to raise an error when timing is out of range. 
 20  T_LIMIT = 1 #: Cause L{read} to quietly supply as much of the requested data as possible. 
 21   
22 -class Overflow(ValueError):
23 - def __init__(self, *s):
24 ValueError.__init__(self, *s)
25
26 -class BadFileFormatError(Exception):
27 - def __init__(self, *s):
28 Exception.__init__(self, *s)
29 30 31 32 _sizes = { 33 2: (numpy.int16, 16), 34 4: (numpy.int32, 32) 35 } 36 37
38 -def read_hdr(fn):
39 """Read header information for a .WAV file. The header contains 40 information formatted as per L{gpkimgclass.gpk_img}, i.e. FITS standard information. 41 @param fn: filename 42 @type fn: str 43 @rtype: dict(str: str or float or int) 44 """ 45 try: 46 w = wave.open(fn, "r") 47 except wave.Error, x: 48 raise BadFileFormatError, "%s: %s" % (x, fn) 49 assert w.getcomptype() == 'NONE', "Can't handle %s compression" % w.getcompname() 50 nc = w.getnchannels() 51 dt = 1.0/w.getframerate() 52 numtype, bitpix = _sizes[w.getsampwidth()] 53 nf = w.getnframes() 54 return { 55 'NAXIS1': nc, 'NAXIS2': nf, 56 'CDELT2': dt, 'CRPIX2': 1, 'CRVAL2': 0.0, 57 'BITPIX': bitpix, '_NAME': fn, '_FILETYPE': 'WAV' 58 }
59 60
61 -def read(fn, tstart=None, t_end=None, t_error=T_ERROR):
62 """Reads in a .WAV file and returns a L{gpkimgclass.gpk_img<gpkimgclass>} instance 63 that contains the header and data information. If tstart and/or 64 t_end are specified, it seeks and only reads in the necessary data 65 between tstart and t_end. 66 @param fn: filename 67 @type fn: str 68 @param tstart: time at which to start reading audio data 69 @param t_end: time at which to stop reading audio data 70 @type tstart: float 71 @type t_end: float 72 @param t_error: What to do if the requested C{t_start} or C{t_end} are out of range? 73 Either raise a L{ValueError} (L{T_ERROR}) or adjust the limits (L{T_LIMIT}). 74 @type t_error: int, either C{T_ERROR} or C{T_LIMIT}. 75 @return: audio data 76 @rtype: L{gpkimgclass} instance 77 """ 78 try: 79 w = wave.open(fn, "r") 80 except wave.Error, x: 81 raise BadFileFormatError, "%s: %s" % (x, fn) 82 assert w.getcomptype() == 'NONE', "Can't handle %s compression" % w.getcompname() 83 nc = w.getnchannels() 84 dt = 1.0/w.getframerate() 85 numtype, bitpix = _sizes[w.getsampwidth()] 86 if tstart is not None: 87 istart = int(round(tstart/dt)) 88 if istart < 0: 89 if t_error == T_ERROR: 90 raise ValueError, "tstart=%.3fs < 0" % tstart 91 else: 92 istart = 0 93 w.setpos(istart) 94 else: 95 istart = 0 96 tstart = 0.0 97 if t_end is not None: 98 nf = int(round(t_end/dt)) 99 if nf > w.getnframes(): 100 if t_error == T_ERROR: 101 raise ValueError, "t_end=%.3fs which is beyond the end of the wave file (%.3fs)" % (t_end, w.getnframes()*dt/nc) 102 else: 103 nf = w.getnframes() 104 nf -= istart 105 else: 106 nf = w.getnframes() - istart 107 if nf < 0: 108 raise ValueError, "t_end=%.4f < tstart=%.4f" % (t_end, tstart) 109 data = numpy.fromstring(w.readframes(nf), numtype) 110 w.close() 111 assert data.shape[0]==nf*nc, "Reshape from %d to %dx%d" % (data.shape[0], nf, nc) 112 data = numpy.reshape(data, (nf, nc)) 113 hdr = { 114 'NAXIS1': nc, 'NAXIS2': nf, 115 'CDELT2': dt, 'CRPIX2': 1, 'CRVAL2': istart*dt, 116 'BITPIX': bitpix, '_NAME': fn, '_FILETYPE': 'WAV' 117 } 118 return gpkimgclass.gpk_img(hdr, data)
119 120
121 -def _itemsize(z):
122 """This exists for Numeric/NumPy compatibility""" 123 x = z.itemsize 124 if isinstance(x, int): 125 return x 126 if callable(x): 127 return x() 128 raise RuntimeError, 'Cannot deal with %s' % str(type(x))
129 130 _typecodes = { 131 _itemsize(numpy.zeros((1,), numpy.int32))*8: 132 (numpy.int32, float(2**31-1), float(1-2**31)), 133 _itemsize(numpy.zeros((1,), numpy.int16))*8: 134 (numpy.int16, float(2**15-1), float(1-2**15)), 135 _itemsize(numpy.zeros((1,), numpy.int8))*8: 136 (numpy.int8, float(2**7-1), float(1-2**7)) 137 } 138
139 -def write(data, fname, scalefac=1, allow_overflow=OV_ERROR):
140 """@param data: is a class gpk_img object containing 141 data to be written (note that the header information is 142 ignored except for the sampling rate (C{CDELT2}) and bits per pixel (C{BITPIX})). 143 The length and number of channels is taken from C{data.d.shape}. 144 @param fname: is the name of a file to write it to (or a file object), 145 @type fname: str or file, 146 @param scalefac: is a factor to multiply the data 147 @param allow_overflow: can be either 148 - L{OV_ERROR} (default, means raise a ValueError exception 149 if the data*scalefac overflows), 150 - L{OV_LIMIT} (means limit the data*scalefac to prevent 151 overflows -- this clips the audio), or 152 - L{OV_IGNORE} (means let the overflows happen and don't worry.) 153 @except ValueError: Missing information in C{data}. 154 @except Overflow: The output format is clipping the data. Strictly 155 speaking, on 16-bit data, 32767 is considered clipping even 156 though it possibly might be OK. However such extreme values 157 are very likely to be generated by clipping at an earlier 158 stage of the processing, so it's probably an error even if 159 the error is not being made here. 160 """ 161 if not (data.dt() > 0.0): 162 raise ValueError, "Cannot set sampling rate: dt=%g\n" % data.dt() 163 164 hdr = data.hdr 165 166 if not hdr.has_key('BITPIX') or int(hdr['BITPIX'])==0: 167 bitpix = 16 168 else: 169 bitpix = abs(int(hdr['BITPIX'])) 170 assert bitpix % 8 == 0 171 if bitpix > 32: 172 bitpix = 32 173 174 tc, vmax, vmin = _typecodes[bitpix] 175 dds = data.d * scalefac 176 if allow_overflow == OV_ERROR: 177 rdds = numpy.ravel(dds) 178 if not numpy.alltrue(numpy.greater_equal(rdds, vmin)): 179 i = numpy.argmin(rdds) 180 raise Overflow, "Scaled data overflows negative: %s<%s at %d" % (rdds[i], vmin, i) 181 if not numpy.alltrue(numpy.less_equal(rdds, vmax)): 182 i = numpy.argmax(rdds) 183 raise Overflow, "Scaled data overflows positive %s>%s at %d" % (rdds[i], vmin, i) 184 elif allow_overflow == OV_LIMIT: 185 dds = numpy.clip(dds, vmin, vmax) 186 else: 187 assert allow_overflow == OV_IGNORE 188 189 d = numpy.around(dds).astype(tc).tostring() 190 w = wave.open(fname, "w") 191 w.setnchannels(data.d.shape[1]) 192 w.setnframes(data.d.shape[0]) 193 w.setsampwidth(bitpix/8) 194 w.setframerate( int(round( 1.0/float(data.dt()) )) ) 195 w.writeframesraw(d) 196 w.close()
197 198 199 if __name__ == '__main__': 200 import sys 201 arglist = sys.argv[1:] 202 gain = None 203 if len(arglist)==0: 204 print __doc__ 205 sys.exit(1) 206 if arglist[0] == '-g': 207 arglist.pop(0) 208 gain = float(arglist.pop(0)) 209 if arglist[0] == '-wavin': 210 x = read(arglist[1]) 211 if gain is not None: 212 numpy.multiply(x.d, gain, x.d) 213 x.write(arglist[2]) 214 elif arglist[0] == '-wavout': 215 x = gpkimgclass.read(arglist[1]) 216 rxd = numpy.ravel(x.d) 217 mxv = max( rxd[numpy.argmax(rxd)], -rxd[numpy.argmin(rxd)] ) 218 print "# max=", mxv 219 if gain is None: 220 gain = 32000.0/mxv 221 write(x, arglist[2], gain) 222 else: 223 print __doc__ 224 sys.exit(1) 225