1
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
17 OV_LIMIT = 1
18 OV_IGNORE = 2
19 T_ERROR = 0
20 T_LIMIT = 1
21
25
29
30
31
32 _sizes = {
33 2: (numpy.int16, 16),
34 4: (numpy.int32, 32)
35 }
36
37
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
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
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
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