c@0
|
1 """
|
c@0
|
2 A utility module to assist cortical_model.py with general procedures, e.g., file reading and writing.
|
c@0
|
3
|
c@0
|
4 Packaged dependencies:
|
c@0
|
5 * erb.dat
|
c@0
|
6 * outMidFir.dat
|
c@1
|
7 * tq.dat
|
c@0
|
8
|
c@0
|
9 External dependencies:
|
c@0
|
10 * scipy
|
c@0
|
11 * numpy
|
c@0
|
12 * matplotlib
|
c@9
|
13 * struct
|
c@9
|
14 * wave
|
c@9
|
15 * pyaudio
|
c@9
|
16 * os
|
c@0
|
17 """
|
c@0
|
18
|
c@0
|
19 import numpy as np
|
c@8
|
20 import scipy.io.wavfile as wav
|
c@0
|
21 import matplotlib.pyplot as plt
|
c@0
|
22 import scipy.fftpack as fft
|
c@8
|
23 import wave
|
c@8
|
24 import struct
|
c@8
|
25 from os import getcwd
|
c@9
|
26 import pyaudio
|
c@8
|
27
|
c@8
|
28 gPath = getcwd()
|
c@9
|
29 p = None
|
c@8
|
30
|
c@8
|
31 def set_path_to_data(path):
|
c@9
|
32 """
|
c@9
|
33 Set path to data.
|
c@9
|
34
|
c@9
|
35 Parameters:
|
c@9
|
36 * path (type: string) - directory path to data
|
c@9
|
37 """
|
c@8
|
38 global gPath
|
c@8
|
39 gPath = path
|
c@8
|
40
|
c@8
|
41 return
|
c@0
|
42
|
c@0
|
43 def load_erb_data():
|
c@0
|
44 """
|
c@0
|
45 Loads and returns 39 ERB frequencies and bandwidths
|
c@0
|
46
|
c@0
|
47 Parameters:
|
c@0
|
48 NONE
|
c@0
|
49
|
c@0
|
50 Returns:
|
c@0
|
51 * fc (type: numpy array of floats) - a vector of length 39 with elements containing
|
c@0
|
52 the centre frequencies of each ERB.
|
c@0
|
53 * bw (type: numpy array of floats) - a vector of length 39 with elements containing
|
c@0
|
54 the bandwidths of each ERB.
|
c@0
|
55
|
c@0
|
56 Dependencies:
|
c@0
|
57 * erb.dat (place in the same folder as utils.pyc)
|
c@0
|
58 """
|
c@1
|
59 # read data from a text file
|
c@8
|
60 data=np.loadtxt(gPath + "/erb.dat",delimiter=",")
|
c@0
|
61
|
c@0
|
62 # load centre frequencies from the first column into fc
|
c@0
|
63 fc=np.array(data[:,0])
|
c@0
|
64 # load bandwidths from the second column into bw
|
c@0
|
65 bw=np.array(data[:,1])
|
c@0
|
66
|
c@0
|
67 return fc,bw
|
c@0
|
68
|
c@0
|
69 def load_outMidFir_coeff():
|
c@0
|
70 """
|
c@0
|
71 Loads and returns 39 ERB frequencies and bandwidths
|
c@0
|
72
|
c@0
|
73 Parameters:
|
c@0
|
74 NONE
|
c@0
|
75
|
c@0
|
76 Returns:
|
c@0
|
77 * b (type: numpy array of floats) - a vector of length 4097 containing the impulse
|
c@0
|
78 response of the outer middle ear.
|
c@0
|
79
|
c@0
|
80 Dependencies:
|
c@0
|
81 * outMidFir.dat (place in the same folder as utils.pyc)
|
c@0
|
82 """
|
c@0
|
83
|
c@8
|
84 b=np.array(np.loadtxt(gPath + "/outMidFir.dat", delimiter=","))
|
c@0
|
85
|
c@0
|
86 return b
|
c@0
|
87
|
c@1
|
88 def load_sl_parameters():
|
c@1
|
89 """
|
c@1
|
90 Loads the loudness parameters for each ERB fc into a tuple. A shortcut for load_tq_data(), load_A_data() and load_alpha_data().
|
c@1
|
91
|
c@1
|
92 Parameters:
|
c@1
|
93 NONE
|
c@1
|
94
|
c@1
|
95 Returns:
|
c@1
|
96 * tq (type: numpy array of floats) - a vector of length 39 containing the threshold excitation
|
c@1
|
97 intensity in quietat each ERB centre frequency
|
c@1
|
98 * A (type: numpy array of floats) - a vector of length 39 containing the parameter A for each ERB fc
|
c@1
|
99 * alpha (type: numpy array of floats) - a vector of length 39 containing the parameter alpha for each ERB fc
|
c@1
|
100
|
c@1
|
101 Dependencies:
|
c@1
|
102 * tq.dat (place in the same folder as utils.pyc)
|
c@1
|
103 * A.dat (place in the same folder as utils.pyc)
|
c@1
|
104 * alpha.dat (place in the same folder as utils.pyc)
|
c@1
|
105 """
|
c@5
|
106 tq_dB = load_tqdB_data()
|
c@1
|
107 A = load_A_data()
|
c@1
|
108 alpha = load_alpha_data()
|
c@1
|
109
|
c@5
|
110 return tq_dB, A, alpha
|
c@1
|
111
|
c@5
|
112 def load_tqdB_data():
|
c@1
|
113 """
|
c@1
|
114 Loads and returns the excitation threshold of quiet for each ERB fc.
|
c@1
|
115
|
c@1
|
116 Parameters:
|
c@1
|
117 NONE
|
c@1
|
118
|
c@1
|
119 Returns:
|
c@1
|
120 * tq (type: numpy array of floats) - a vector of length 39 containing the threshold excitation
|
c@1
|
121 intensity in quietat each ERB centre frequency
|
c@1
|
122
|
c@1
|
123 Dependencies:
|
c@1
|
124 * tq.dat (place in the same folder as utils.pyc)
|
c@1
|
125 """
|
c@1
|
126
|
c@8
|
127 tq = np.array(np.loadtxt(gPath + "/tq_dB.dat",delimiter=","))
|
c@1
|
128
|
c@1
|
129 return tq
|
c@1
|
130
|
c@1
|
131 def load_A_data():
|
c@1
|
132 """
|
c@1
|
133 Loads and returns the excitation A parameters for each ERB fc.
|
c@1
|
134
|
c@1
|
135 Parameters:
|
c@1
|
136 NONE
|
c@1
|
137
|
c@1
|
138 Returns:
|
c@1
|
139 * A (type: numpy array of floats) - a vector of length 39 containing the parameter A for each ERB fc
|
c@1
|
140
|
c@1
|
141 Dependencies:
|
c@1
|
142 * A.dat (place in the same folder as utils.pyc)
|
c@1
|
143 """
|
c@1
|
144
|
c@8
|
145 A = np.array(np.loadtxt(gPath + "/A.dat",delimiter=","))
|
c@1
|
146
|
c@1
|
147 return A
|
c@1
|
148
|
c@1
|
149 def load_alpha_data():
|
c@1
|
150 """
|
c@1
|
151 Loads and returns the excitation alpha parameters for each ERB fc.
|
c@1
|
152
|
c@1
|
153 Parameters:
|
c@1
|
154 NONE
|
c@1
|
155
|
c@1
|
156 Returns:
|
c@1
|
157 * alpha (type: numpy array of floats) - a vector of length 39 containing the parameter alpha for each ERB fc
|
c@1
|
158
|
c@1
|
159 Dependencies:
|
c@1
|
160 * alpha.dat (place in the same folder as utils.pyc)
|
c@1
|
161 """
|
c@1
|
162
|
c@8
|
163 alpha = np.array(np.loadtxt(gPath + "/alpha.dat",delimiter=","))
|
c@1
|
164
|
c@1
|
165 return alpha
|
c@1
|
166
|
c@0
|
167 def exp_sequence(start, stop, n, base=2):
|
c@0
|
168 """
|
c@0
|
169 Creates a linear sequence with n points starting from start and ending at stop. For each
|
c@1
|
170 element in the sequence, i, the output sequence is 2**i, generating an exponential sequence
|
c@0
|
171 with base 2.
|
c@0
|
172
|
c@0
|
173 Parameters:
|
c@0
|
174 * start (type: numerical int) - determines the first element of the sequence, i.e.,
|
c@1
|
175 base**start. (Required)
|
c@0
|
176 * stop (type: numerical int) - determines the last element of the sequence, i.e.,
|
c@1
|
177 base**stop. (Required)
|
c@0
|
178 * n (type = numerical int) - determines the number of elements in the sequence. (Required)
|
c@0
|
179 * base (type: numerical) - determines the exponential base. (Optional; Default = 2)
|
c@0
|
180
|
c@0
|
181 Returns:
|
c@0
|
182 * seq - the exponential sequence
|
c@0
|
183 """
|
c@0
|
184
|
c@0
|
185 seq = [base**x for x in np.linspace(start,stop,n)]
|
c@0
|
186
|
c@0
|
187 return seq
|
c@0
|
188
|
c@0
|
189 def wavread(file):
|
c@0
|
190 """
|
c@0
|
191 Reads the audio data from a wav file and converts it to floating point ranging from -1 to 1.
|
c@0
|
192
|
c@0
|
193 Parameters:
|
c@0
|
194 * file (type: string) - the name of the wav file to read from. (Required)
|
c@0
|
195
|
c@0
|
196 Returns:
|
c@0
|
197 * fs (type: numerical) - the sampling frequency of the signal storied in file
|
c@0
|
198 * data (type: numpy array of floats) - the data of the signal stored in file normalised
|
c@0
|
199 to an amplitude range of -1 to 1.
|
c@9
|
200
|
c@9
|
201 NOTE: Works for 16 bit and 32 bit files, and any endian.
|
c@0
|
202 """
|
c@0
|
203
|
c@8
|
204 fs,data = wav.read(file)
|
c@1
|
205 data = np.array(int_to_nfloat(data))
|
c@0
|
206
|
c@0
|
207 return fs,data
|
c@0
|
208
|
c@9
|
209 def sound(data, fs):
|
c@9
|
210 """
|
c@9
|
211 Play the audio signal stored in data.
|
c@9
|
212
|
c@9
|
213 Parameters:
|
c@9
|
214 * data (type: array-like matrix of floats) - the signal data normalised from -1 to 1. The signal will be clipped if not
|
c@9
|
215 in this range. (Required)
|
c@9
|
216 * fs (type: numerical) - the sampling frequency of the signal. (Required)
|
c@9
|
217
|
c@9
|
218 NOTE: Only works for mono signals. Place data in a vector.
|
c@9
|
219 """
|
c@9
|
220 global p
|
c@9
|
221
|
c@9
|
222 if p == None:
|
c@9
|
223 p = pyaudio.PyAudio()
|
c@9
|
224
|
c@9
|
225 try:
|
c@9
|
226 N = data.dtype.itemsize/8
|
c@9
|
227 stream = p.open(format = p.get_format_from_width(4), channels=1, rate=fs, output=True)
|
c@9
|
228
|
c@9
|
229 for i in range(len(data)):
|
c@9
|
230 stream.write(np.float32(data[i]), num_frames=1)
|
c@9
|
231
|
c@9
|
232 stream.close()
|
c@9
|
233 except KeyboardInterrupt:
|
c@9
|
234 print "\nClosing stream."
|
c@9
|
235 stream.close()
|
c@9
|
236
|
c@9
|
237 return
|
c@9
|
238
|
c@8
|
239 def wavread2(filename):
|
c@9
|
240 """
|
c@9
|
241 Reads the audio data from a wav file and converts it to floating point ranging from -1 to 1.
|
c@9
|
242
|
c@9
|
243 Parameters:
|
c@9
|
244 * file (type: string) - the name of the wav file to read from. (Required)
|
c@9
|
245
|
c@9
|
246 Returns:
|
c@9
|
247 * fs (type: numerical) - the sampling frequency of the signal storied in file
|
c@9
|
248 * data (type: numpy array of floats) - the data of the signal stored in file normalised
|
c@9
|
249 to an amplitude range of -1 to 1.
|
c@9
|
250
|
c@9
|
251 NOTE: This works for 16bit, 24bit and 32bit files, but must be little endian.
|
c@9
|
252 """
|
c@8
|
253 file = wave.open(filename)
|
c@8
|
254 nChans, sampwidth, fs, nFrames, comptype, compname = file.getparams()
|
c@8
|
255
|
c@8
|
256 N = 8*sampwidth
|
c@8
|
257
|
c@8
|
258 data = np.zeros((nChans, nFrames))
|
c@8
|
259
|
c@8
|
260 if sampwidth == 2:
|
c@8
|
261 unpackChar = 'h'
|
c@8
|
262 elif sampwidth == 3:
|
c@8
|
263 unpackChar = 'hc'
|
c@8
|
264 elif sampwidth == 4:
|
c@8
|
265 unpackChar = 'i'
|
c@8
|
266
|
c@8
|
267 frame = [[] for i in range(nChans)]
|
c@8
|
268
|
c@8
|
269
|
c@8
|
270 for i in range(nFrames):
|
c@8
|
271 bytes = file.readframes(1)
|
c@8
|
272
|
c@8
|
273 if nChans > 1:
|
c@8
|
274 for j in range(nChans):
|
c@8
|
275 index = j*sampwidth
|
c@8
|
276 frame[j] = bytes[index:index+sampwidth]
|
c@8
|
277 else:
|
c@8
|
278 frame[0] = bytes
|
c@8
|
279
|
c@8
|
280 for j in range(nChans):
|
c@8
|
281 if sampwidth == 2:
|
c@8
|
282 data[j][i] = struct.unpack('<h', frame[j])[0]
|
c@8
|
283 elif sampwidth == 3:
|
c@8
|
284 data[j][i] = struct.unpack('<i', frame[j] + ('\0' if frame[j][2] < '\x80' else '\xff'))[0]
|
c@8
|
285 elif sampwidth == 4:
|
c@8
|
286 data[j][i] = struct.unpack('<i', frame[j])[0]
|
c@8
|
287
|
c@8
|
288 data[data<0] = data[data<0]/(2**N)
|
c@8
|
289 data[data>0] = data[data>0]/((2**N)-1)
|
c@8
|
290
|
c@8
|
291 if nChans == 1:
|
c@8
|
292 data = data.reshape((nFrames))
|
c@8
|
293
|
c@8
|
294 file.close()
|
c@8
|
295
|
c@8
|
296 return fs,data
|
c@8
|
297
|
c@0
|
298 def wavwrite(file, fs, data, precision = 16):
|
c@0
|
299 """
|
c@0
|
300 Unnormalises the audio data to a specified integer precision and converts to an int, then
|
c@0
|
301 writes the audio data to a wav file. (E.g., if 16-bit precision, then highest amplitude
|
c@1
|
302 is equal to 2**16).
|
c@0
|
303
|
c@0
|
304 Parameters:
|
c@0
|
305 * file (type: string) - the name of the wav file to write to. (Required)
|
c@0
|
306 * fs (type: numerical) - the sampling frequency of the signal. (Required)
|
c@0
|
307 * data (type: array-like matrix of floats) - the signal data normalised from -1 to 1. The signal will be clipped if not
|
c@0
|
308 in this range. (Required)
|
c@0
|
309 * precision (type: numerical int)- the bit precision to store at. Can only be 16 or 32 bit, because that is
|
c@0
|
310 all scipy allows.
|
c@0
|
311
|
c@0
|
312 Returns:
|
c@0
|
313 NONE
|
c@0
|
314
|
c@0
|
315 TODO explore WAVE package to be allow for 24bit precision.
|
c@0
|
316 """
|
c@0
|
317
|
c@0
|
318 data[data>1] = 1
|
c@0
|
319 data[data<-1] = -1
|
c@0
|
320
|
c@0
|
321 if(precision == 16):
|
c@0
|
322 dtype = np.int16
|
c@0
|
323 elif(precision == 32):
|
c@0
|
324 dtype = np.int32
|
c@0
|
325 else:
|
c@0
|
326 print "Error: precision can only be 16 or 32 bit due to scipy package."
|
c@0
|
327 return
|
c@0
|
328
|
c@0
|
329 data = nfloat_to_int(data)
|
c@8
|
330 wav.write(file, fs, data)
|
c@0
|
331
|
c@0
|
332 return
|
c@0
|
333
|
c@9
|
334 def plot_fft(x, xscale = 'log', yscale = 'log', nyquist = True, show = True):
|
c@0
|
335 """
|
c@0
|
336 Plots the fft of signal x. If the figure is not shown, the current plot is held to allow other plots to
|
c@0
|
337 be added to the same figure.
|
c@0
|
338
|
c@0
|
339 Parameters:
|
c@0
|
340 * x (type: array-like matrix of floats) - the signal to be analysed. (Required)
|
c@0
|
341 * xscale (type: string) - the scale of the frequency axis. Values are 'log' or 'linear'. (Optional; Default = 'log')
|
c@0
|
342 * yscale (type: string) - the scale of the amplitude axis. Values are 'log' or 'linear'. (Optional; Default = 'log')
|
c@0
|
343 * show (type: boolean) - specifies whether the figure should be shown. If False, the current plot will be held so
|
c@0
|
344 other plots can be added to the figure. (Optional; Default = True)
|
c@0
|
345
|
c@0
|
346 Returns:
|
c@0
|
347 NONE
|
c@0
|
348 """
|
c@0
|
349
|
c@4
|
350 plt.gca().set_xscale(xscale)
|
c@1
|
351
|
c@1
|
352 x = np.array(x)
|
c@0
|
353 fftx = np.absolute(fft.fft(x))
|
c@9
|
354 if(nyquist):
|
c@9
|
355 fftx = fftx[0:len(fftx)/2]
|
c@9
|
356
|
c@9
|
357 if(yscale=='log'):
|
c@9
|
358 fftx=20*np.log10(fftx)
|
c@9
|
359 else:
|
c@9
|
360 plt.gca().set_yscale(yscale)
|
c@9
|
361
|
c@9
|
362 plt.plot(fftx)
|
c@9
|
363 plt.xlabel('Frequency (Hz)')
|
c@9
|
364 plt.ylabel('Frequency Response (dB)')
|
c@9
|
365
|
c@0
|
366 if(show):
|
c@0
|
367 plt.show()
|
c@0
|
368 else:
|
c@0
|
369 plt.hold(True)
|
c@0
|
370
|
c@0
|
371 return
|
c@0
|
372
|
c@1
|
373 def plot_waveform(x, show = True):
|
c@1
|
374 """
|
c@1
|
375 Plots the waveform of signal x. If the figure is not shown, the current plot is held to allow other plots to
|
c@1
|
376 be added to the same figure.
|
c@1
|
377
|
c@1
|
378 Parameters:
|
c@1
|
379 * x (type: array-like matrix of floats) - the signal to be plotted. (Required)
|
c@1
|
380 * show (type: boolean) - specifies whether the figure should be shown. If False, the current plot will be held so
|
c@1
|
381 other plots can be added to the figure. (Optional; Default = True)
|
c@1
|
382
|
c@1
|
383 Returns:
|
c@1
|
384 NONE
|
c@1
|
385 """
|
c@1
|
386
|
c@1
|
387 x = np.array(x)
|
c@1
|
388 plt.plot(x)
|
c@1
|
389
|
c@1
|
390 if(show): plt.show()
|
c@1
|
391 else: plt.hold(True)
|
c@1
|
392
|
c@1
|
393 return
|
c@1
|
394
|
c@1
|
395 def int_to_nfloat(input, outputtype=np.float32):
|
c@0
|
396 """
|
c@0
|
397 Convert integer with to floating point with a range from -1 to 1.
|
c@0
|
398
|
c@0
|
399 Parameters:
|
c@0
|
400 * input (type: array-like matrix of ints) - a signed integer array. (Required)
|
c@0
|
401 dtype : the output datatype. (Optional; Default = np.float32)
|
c@0
|
402
|
c@0
|
403 Returns:
|
c@0
|
404 * y (type: array-like matrix of floats) - a float array normalised to a
|
c@0
|
405 range of -1 to 1.
|
c@0
|
406 """
|
c@0
|
407
|
c@0
|
408 input = np.array(input)
|
c@0
|
409 assert input.dtype.kind == 'i', "'input' must be an array-like matrix of integers."
|
c@1
|
410 outputtype = np.dtype(outputtype)
|
c@0
|
411 inputdtype = np.dtype(type(input[0]))
|
c@1
|
412 input = input.astype(outputtype)
|
c@0
|
413
|
c@0
|
414 input[input > 0] = input[input > 0] / np.iinfo(inputdtype).max
|
c@0
|
415 input[input < 0] = input[input < 0] / -np.iinfo(inputdtype).min
|
c@0
|
416
|
c@0
|
417 y = input
|
c@0
|
418 return y
|
c@0
|
419
|
c@0
|
420 def nfloat_to_int(input, type=np.int16):
|
c@0
|
421 """
|
c@0
|
422 Convert a float array with amplitude ranging from -1 to 1 to a unnormalised signed
|
c@0
|
423 integer array with specified precision.
|
c@0
|
424
|
c@0
|
425 Parameters:
|
c@0
|
426 * input (type: array-like matrix of floats) - a float array. (Required)
|
c@0
|
427 dtype : the output datatype (also determines the precision).
|
c@0
|
428 (Optional; Default = np.int16)
|
c@0
|
429
|
c@0
|
430 Returns:
|
c@0
|
431 * y (type: array-like matrix of ints) - an unnormalised to a
|
c@0
|
432 range of -1 to 1.
|
c@0
|
433 """
|
c@0
|
434
|
c@0
|
435 input = np.array(input)
|
c@0
|
436 assert input.dtype.kind == 'f', "'input' must be an array of floats!"
|
c@0
|
437
|
c@1
|
438 input[input > 0] = input[ input > 0 ] * np.iinfo(type).max
|
c@1
|
439 input[input < 0] = input[ input < 0 ] * -np.iinfo(type).min
|
c@0
|
440
|
c@0
|
441 y = input.astype(type)
|
c@0
|
442
|
c@0
|
443 return y |