c@0
|
1 """
|
c@0
|
2 A module used for auditory analysis.
|
c@0
|
3
|
c@0
|
4 Models currently implemented:
|
c@6
|
5 * Simple frequency-modulation analysis model.
|
c@6
|
6 * Glasberg and Moore model using gammatone filters
|
c@0
|
7
|
c@0
|
8 Packaged dependencies:
|
c@6
|
9 > utils.py and/or utils.pyc
|
c@6
|
10 > erb.dat
|
c@6
|
11 > outMidFir.dat
|
c@6
|
12 > tq.dat
|
c@0
|
13
|
c@0
|
14 External dependencies:
|
c@6
|
15 > scipy
|
c@6
|
16 > numpy
|
c@6
|
17 > matplotlib
|
c@6
|
18
|
c@6
|
19 Primary functions:
|
c@7
|
20 > calc_partial_loudness
|
c@7
|
21 > calc_loudness
|
c@6
|
22 > get_specific_loudness_noise
|
c@6
|
23 > get_specific_loudness_quiet
|
c@6
|
24 > get_excitation_i
|
c@6
|
25 > plot_excitation_response
|
c@6
|
26 > gammatone_filter
|
c@6
|
27 > holdsworth_gammatone_filter
|
c@0
|
28 """
|
c@0
|
29
|
c@5
|
30 import sys
|
c@0
|
31 import utils
|
c@0
|
32 import scipy.signal as sp
|
c@1
|
33 import scipy.fftpack as fft
|
c@0
|
34 import numpy as np
|
c@0
|
35 import matplotlib.pyplot as plt
|
c@0
|
36
|
c@6
|
37 def calc_loudness_noise(input, fs, SPL, rectify=False, inc_loud_region = False, verbose = False):
|
c@5
|
38
|
c@6
|
39 numTracks = np.shape(input)[0]
|
c@6
|
40 lenSig = np.shape(input)[-1]
|
c@6
|
41 exc_i = np.zeros((numTracks, 39, lenSig))
|
c@6
|
42
|
c@6
|
43 for i in range(numTracks):
|
c@6
|
44 exc_i[i] = get_excitation_i(input[i], fs, SPL, rectify = rectify, verbose = verbose)
|
c@6
|
45
|
c@6
|
46 sl = get_specific_loudness_noise(exc_i, inc_loud_region = inc_loud_region)
|
c@6
|
47 loudness = 2*np.sum(sl,1)
|
c@5
|
48
|
c@8
|
49 return sl, loudness
|
c@5
|
50
|
c@7
|
51 def calc_loudness_quiet(input, fs, SPL, rectify = False, inc_loud_region = False, verbose = False):
|
c@6
|
52
|
c@6
|
53 exc_i = get_excitation_i(input, fs, SPL, rectify = rectify, verbose = verbose)
|
c@7
|
54 sl = get_specific_loudness_quiet(exc_i, inc_loud_region = inc_loud_region)
|
c@6
|
55
|
c@6
|
56 loudness = 2*np.sum(sl,0)
|
c@6
|
57
|
c@8
|
58 return sl, loudness
|
c@5
|
59
|
c@6
|
60 def get_specific_loudness_noise(exc_i, analyse = "all", inc_loud_region = False):
|
c@0
|
61 """
|
c@6
|
62
|
c@6
|
63 """
|
c@6
|
64 exc_i = np.array(exc_i)
|
c@6
|
65
|
c@6
|
66 lenSig = np.shape(exc_i)[-1] #length signal
|
c@6
|
67 numTracks = np.shape(exc_i)[0]
|
c@6
|
68 tq, G, A, alpha = get_specific_loudness_parameters(lenSig)
|
c@6
|
69 K = calc_K_parameter(lenSig)
|
c@6
|
70 specific_loudness = exc_low = exc_mid = exc_high = np.zeros(np.shape(exc_i))
|
c@6
|
71
|
c@6
|
72 exc_signoise = sum(exc_i, 0)
|
c@6
|
73 signoise_gt_tl = get_greater_than_tl(exc_signoise) #boolean array of elements greater than the loud threshold
|
c@6
|
74 signoise_lt_tl = ~signoise_gt_tl
|
c@6
|
75
|
c@7
|
76 if analyse.lower() == "all":
|
c@7
|
77 loop = range(numTracks);
|
c@7
|
78 elif analyse.lower() == "first":
|
c@7
|
79 loop = range(1)
|
c@6
|
80
|
c@6
|
81 for i in loop:
|
c@6
|
82
|
c@6
|
83 exc_sig = exc_i[i]
|
c@6
|
84 exc_noise = exc_signoise - exc_sig
|
c@6
|
85 tn = K*exc_noise + tq
|
c@6
|
86
|
c@8
|
87 less_than_tn = get_less_than_tq(exc_sig, tn) #boolean array of elements less than or equal to the threshold of quiet
|
c@8
|
88 greater_than_tn = ~less_than_tn #boolean array of elements greater than the threshold of quiet
|
c@6
|
89
|
c@6
|
90 if inc_loud_region:
|
c@7
|
91 c1 = signoise_lt_tl * greater_than_tn #case 1
|
c@7
|
92 c2 = signoise_lt_tl * less_than_tn #case 2
|
c@7
|
93 c3 = signoise_gt_tl * greater_than_tn #case 3
|
c@8
|
94 c4 = signoise_gt_tl * less_than_tn #case 4
|
c@7
|
95 specific_loudness[i, c3] = get_specific_loudness_noise_c3(exc_sig[c3], exc_noise[c3], tq[c3], tn[c3], G[c3], A[c3], alpha[c3])
|
c@7
|
96 specific_loudness[i, c4] = get_specific_loudness_noise_c4(exc_sig[c4], exc_noise[c4], tq[c4], tn[c4], G[c4], A[c4], alpha[c4])
|
c@6
|
97 else:
|
c@8
|
98 c1 = greater_than_tn #case 1
|
c@8
|
99 c2 = less_than_tn #case 2
|
c@6
|
100
|
c@7
|
101 specific_loudness[i][c1] = get_specific_loudness_noise_c1(exc_sig[c1], exc_noise[c1], tq[c1], tn[c1], G[c1], A[c1], alpha[c1])
|
c@7
|
102 specific_loudness[i][c2] = get_specific_loudness_noise_c2(exc_sig[c2], exc_noise[c2], tq[c2], tn[c2], G[c2], A[c2], alpha[c2])
|
c@6
|
103
|
c@6
|
104 return specific_loudness
|
c@6
|
105
|
c@6
|
106 def get_specific_loudness_noise_c1(exc_sig, exc_noise, tq, tn, G, A, alpha):
|
c@6
|
107 """
|
c@6
|
108
|
c@6
|
109 """
|
c@6
|
110 C = 0.046871 #constant parameter
|
c@6
|
111 sl_c1 = C*(((exc_sig + exc_noise)*G + A)**alpha - A**alpha) - C*((((tn + exc_noise)*G + A)**alpha) - (tq*G + A)**alpha) * (tn/exc_sig)**0.3
|
c@6
|
112
|
c@6
|
113 return sl_c1
|
c@6
|
114
|
c@6
|
115 def get_specific_loudness_noise_c2(exc_sig, exc_noise, tq, tn, G, A, alpha):
|
c@6
|
116 """
|
c@6
|
117 Parameters:
|
c@6
|
118 exc_sig
|
c@6
|
119 exc_noise
|
c@6
|
120 tq
|
c@6
|
121 tn
|
c@6
|
122 G
|
c@6
|
123 A
|
c@6
|
124 alpha
|
c@6
|
125 """
|
c@6
|
126
|
c@6
|
127 C = 0.046871 #constant parameter
|
c@7
|
128 sl_c2 = (C*((2*exc_sig)/(exc_sig + tn))**1.5) * (((tq*G + A)**alpha - A**alpha)/(((0.0 + tn + exc_noise)*G + A)**alpha - (exc_noise*G + A)**alpha)) * (((exc_sig + exc_noise)*G + A)**alpha - (exc_noise*G + A)**alpha)
|
c@6
|
129
|
c@6
|
130 return sl_c2
|
c@6
|
131
|
c@6
|
132 def get_specific_loudness_noise_c3(exc_sig, exc_noise, tq, tn, G, A, alpha):
|
c@6
|
133
|
c@6
|
134 C = 0.046871 #constant parameter
|
c@6
|
135 C2 = C/((1.04 * 10**6)**0.5)
|
c@6
|
136 sl_c3 = (C2*(exc_sig + exc_noise)**0.5) - C2*((tn + exc_noise)**0.5 - (tq*G + A)**alpha + A**alpha) * (tn/exc_sig)**0.3
|
c@6
|
137
|
c@6
|
138 return sl_c3
|
c@6
|
139
|
c@6
|
140 def get_specific_loudness_noise_c4(exc_sig, exc_noise, tq, tn, G, A, alpha):
|
c@6
|
141
|
c@6
|
142 C = 0.046871 #constant parameter
|
c@6
|
143 sl_c4 = (C*((2*exc_sig)/(exc_sig + tn))**1.5) * (((tq*G + A)**alpha - A**alpha)/((tn + exc_noise)**0.5 - (exc_noise)**0.5)) * ((exc_sig + exc_noise)**0.5 - (exc_noise)**0.5)
|
c@6
|
144
|
c@6
|
145 return sl_c4
|
c@6
|
146
|
c@6
|
147 def get_specific_loudness_quiet(exc_i, inc_loud_region = False):
|
c@6
|
148 """
|
c@6
|
149 A function to calculate the specific loudness of the excitation patterns at each ERB. Specific loudness is calculated for three regions of signal intensity, low level, mid level and high level. The high level processing is optional, and by default, ignored. When ignored the high level region is processed equivalent to that of the mid region.
|
c@3
|
150
|
c@3
|
151 Parameters:
|
c@6
|
152 * exc_i (type: array-like of floats) - an array of shape (39, lenSig) of the excitation intensity of the signal. The outer dimension determines the ERB channel. (Required)
|
c@3
|
153 * inc_loud_region (type: boolean) - Specifies whether to process the high levels differently to the mid levels. (Optional; Default = False)
|
c@0
|
154 """
|
c@3
|
155 exc_i = np.array(exc_i)
|
c@3
|
156
|
c@1
|
157 lenSig = np.shape(exc_i)[-1] #length signal
|
c@3
|
158 tq, G, A, alpha = get_specific_loudness_parameters(lenSig)
|
c@1
|
159 specific_loudness = exc_low = exc_mid = exc_high = np.zeros(np.shape(exc_i))
|
c@0
|
160
|
c@1
|
161 less_than_tq = get_less_than_tq(exc_i, tq) #boolean array of elements less than or equal to the threshold of quiet
|
c@1
|
162 greater_than_tq = ~less_than_tq #boolean array of elements greater than the threshold of quiet
|
c@1
|
163 greater_than_tl = get_greater_than_tl(exc_i) #boolean array of elements greater than the loud threshold
|
c@8
|
164 gttq_lttl = greater_than_tq * ~greater_than_tl #boolean array of elements greater than the threshold of quiet but less than the threshold of loud
|
c@0
|
165
|
c@1
|
166 exc_low = exc_i[less_than_tq]
|
c@1
|
167 specific_loudness[less_than_tq] = get_specific_loudness_low(exc_low, G[less_than_tq], tq[less_than_tq], A[less_than_tq], alpha[less_than_tq])
|
c@1
|
168
|
c@1
|
169 if(inc_loud_region):
|
c@1
|
170 exc_mid = exc_i[gttq_lttl]
|
c@1
|
171 exc_high = exc_i[greater_than_tl]
|
c@1
|
172 specific_loudness[gttq_lttl] = get_specific_loudness_mid(exc_mid, G[gttq_lttl], A[gttq_lttl], alpha[gttq_lttl])
|
c@1
|
173 specific_loudness[greater_than_tl] = get_specific_loudness_high(exc_high)
|
c@1
|
174 else:
|
c@1
|
175 exc_mid = exc_i[greater_than_tq]
|
c@1
|
176 specific_loudness[greater_than_tq] = get_specific_loudness_mid(exc_mid, G[greater_than_tq], A[greater_than_tq], alpha[greater_than_tq])
|
c@1
|
177
|
c@1
|
178 return specific_loudness
|
c@0
|
179
|
c@3
|
180 def get_specific_loudness_parameters(lenSig = 1):
|
c@3
|
181 """
|
c@6
|
182 Loads and returns the specific loudness values. If lenSig is specified, the parameters are shaped equal to the excitation signal to allow for matrix elementwise operations between the parameters and signal. (Assumes excitation signal shape is [39, lenSig]).
|
c@3
|
183
|
c@3
|
184 Parameters:
|
c@3
|
185 * lenSig (type: numerical int) - the length of the excitation signal to be analysed. (Optional; Default = 1)
|
c@3
|
186
|
c@3
|
187 Returns:
|
c@3
|
188 * tq (type: numpy array of floats) - the threshold of quiet for each centre frequency of the ERBs
|
c@3
|
189 * G (type: numpy array of floats) - the frequency dependent gain values for each centre frequency of the ERBs
|
c@3
|
190 * A (type: numpy array of floats) - the frequency dependent A values for each centre frequency of the ERBs
|
c@3
|
191 * alpha (type: numpy array of floats) - the frequency dependent alpha values for each centre frequency of the ERBs
|
c@3
|
192 """
|
c@3
|
193
|
c@3
|
194 tq_dB, A, alpha = utils.load_sl_parameters() #load tq, A and alpha parameters
|
c@3
|
195 tq_dB = np.transpose(np.tile(tq_dB, (lenSig,1)))
|
c@3
|
196 tq = 10**(tq_dB / 10)
|
c@3
|
197 A = np.transpose(np.tile(A, (lenSig,1)))
|
c@3
|
198 alpha = np.transpose(np.tile(alpha, (lenSig,1)))
|
c@3
|
199 tq500_dB = 3.73 #threshold of quiet at 500Hz reference
|
c@3
|
200 G = 10**((tq500_dB - tq_dB)/10) #gain parameters
|
c@3
|
201
|
c@3
|
202 return tq, G, A, alpha
|
c@3
|
203
|
c@6
|
204 def calc_K_parameter(lenSig = 1):
|
c@6
|
205 """
|
c@6
|
206
|
c@6
|
207 """
|
c@6
|
208
|
c@6
|
209
|
c@6
|
210 nerbs = 39
|
c@6
|
211 erbs = np.array(range(1,nerbs+1))
|
c@6
|
212 fc = (10**(erbs/21.366)-1)/0.004368
|
c@6
|
213
|
c@6
|
214 fkRef = 10**np.append(np.append(np.array(1), np.linspace(np.log10(50), np.log10(500), 11)), np.linspace(3,4.5,16))
|
c@6
|
215 kRef = np.append(np.append(13, np.linspace(13,-3,11)), np.ones((1,16))*-3)
|
c@6
|
216 K = np.interp(fc,fkRef,kRef)
|
c@8
|
217 K = 10**(K/10)
|
c@6
|
218 K = np.transpose(np.tile(K, (lenSig,1)))
|
c@6
|
219
|
c@6
|
220 return K
|
c@6
|
221
|
c@1
|
222 def get_specific_loudness_low(exc_low, G, tq, A, alpha):
|
c@3
|
223 """
|
c@3
|
224 Returns the specific loudness of the low level parts of the signal. Use get_specific_loudness_parameters() and specify lenSig
|
c@3
|
225 to obtain the correct values for G, tq, A and alpha. Use get_less_than_tq() to find the indexes of the samples with lower level
|
c@3
|
226 excitations.
|
c@3
|
227
|
c@6
|
228 EXAMPLE USE
|
c@3
|
229
|
c@3
|
230 # exc_i is the excitation intensity
|
c@3
|
231 lenSig = np.shape(exc_i)[-1] # find the length of the signal
|
c@3
|
232 tq, G, A, alpha = get_specific_loudness_parameters(lenSig) # get the shaped loudness parameters
|
c@3
|
233 less_than_tq = get_less_than_tq(exc_i, tq) # find which samples are lower level
|
c@3
|
234 specific_loudness[less_than_tq] = get_specific_loudness_low(exc_low[less_than_tq], G[less_than_tq], tq[less_than_tq], A[less_than_tq], alpha[less_than_tq])
|
c@3
|
235 # only process the low level part of the signal
|
c@3
|
236
|
c@3
|
237 Parameters:
|
c@3
|
238 * exc_low (type: array-like matrix of floats) - the lower level (less than or equal to tq) excitation pattern for each ERB
|
c@3
|
239 (Required)
|
c@3
|
240 * G (type: array-like matrix of floats) - the frequency dependent loudness gain parameters for each ERB, must be same shape
|
c@3
|
241 as exc_low. (Required)
|
c@3
|
242 * tq (type: array-like matrix of floats) - the frequency dependent threshold of quiet parameters for each ERB. Must be same
|
c@3
|
243 shape as exc_low. (Required)
|
c@3
|
244 * A (type: array-like matrix of floats) - the frequency dependent A parameters for each ERB. Must be same shape as exc_low.
|
c@3
|
245 * alpha (type: array-like matrix of floats) - the frequency dependent alpha parameters for each ERB. Must be same shape as
|
c@3
|
246 exc_low. (Required)
|
c@3
|
247
|
c@3
|
248 Returns:
|
c@3
|
249 * specific_loudness_low (type: array-like matrix of floats) - An array with dimensions equal to the exc_low containing the
|
c@3
|
250 specific loudness of the signal at levels below the threshold of quiet.
|
c@3
|
251 """
|
c@1
|
252
|
c@5
|
253 C = 0.046871 #constant parameter
|
c@5
|
254 specific_loudness_low = C * ((2.0*exc_low)/(0.0+exc_low+tq))**1.5 * ((G * exc_low + A)**alpha - A**alpha)
|
c@1
|
255
|
c@1
|
256 return specific_loudness_low
|
c@1
|
257
|
c@1
|
258 def get_specific_loudness_mid(exc_mid, G, A, alpha):
|
c@3
|
259 """
|
c@3
|
260 Returns the specific loudness of the mid level parts of the signal.
|
c@3
|
261
|
c@6
|
262 EXAMPLE USE
|
c@3
|
263
|
c@3
|
264 # exc_i is the excitation intensity
|
c@3
|
265 lenSig = np.shape(exc_i)[-1] # find the length of the signal
|
c@3
|
266 tq, G, A, alpha = get_specific_loudness_parameters(lenSig) # get the shaped loudness parameters
|
c@3
|
267 less_than_tq = get_less_than_tq(exc_i, tq) # find which samples are lower level
|
c@3
|
268 greater_than_tq = ~less_than_tq # find which samples are greater than the threshold of quiet
|
c@3
|
269 greater_than_tl = get_greater_than_tl(exc_i) # find which samples are greater than the loud threshold
|
c@3
|
270 gttq_lttl = greater_than_tq[~greater_than_tl] # find which samples are mid level
|
c@3
|
271 specific_loudness[gttq_lttl] = get_specific_loudness_low(exc_low[gttq_lttl], G[gttq_lttl], tq[gttq_lttl], A[gttq_lttl], alpha[gttq_lttl])
|
c@3
|
272 # only process the mid level part of the signal
|
c@3
|
273
|
c@6
|
274 NOTE: The above is an example of use assuming the higher level processing IS NOT IGNORED. Use variable greater_than_tq if processing higher levels equivalent to the mid.
|
c@3
|
275
|
c@3
|
276
|
c@3
|
277 Parameters:
|
c@3
|
278 * exc_mid (type: array-like matrix of floats) - the mid level (larger than tq (and, optionally less than high level threshold))
|
c@3
|
279 excitation pattern for each ERB (Required)
|
c@6
|
280 * G (type: array-like matrix of floats) - the frequency dependent loudness gain parameters for each ERB, must be same shape as exc_low. (Required)
|
c@3
|
281 * A (type: array-like matrix of floats) - the frequency dependent A parameters for each ERB. Must be same shape as exc_low.
|
c@6
|
282 * alpha (type: array-like matrix of floats) - the frequency dependent alpha parameters for each ERB. Must be same shape as exc_low. (Required)
|
c@3
|
283
|
c@3
|
284 Returns:
|
c@6
|
285 * specific_loudness_mid (type: array-like matrix of floats) - An array with dimensions equal to the exc_mid containing the specific loudness of the signal at mid levels.
|
c@3
|
286 """
|
c@1
|
287
|
c@6
|
288 C = 0.046871 #constant parameter
|
c@5
|
289 specific_loudness_mid = C * ((G * exc_mid + A)**alpha - (A**alpha))
|
c@1
|
290
|
c@1
|
291 return specific_loudness_mid
|
c@1
|
292
|
c@1
|
293 def get_specific_loudness_high(exc_high):
|
c@1
|
294 """
|
c@3
|
295 Returns the specific loudness of the high level parts of the signal.
|
c@3
|
296
|
c@6
|
297 EXAMPLE USE
|
c@3
|
298
|
c@3
|
299 # exc_i is the excitation intensity
|
c@3
|
300 lenSig = np.shape(exc_i)[-1] # find the length of the signal
|
c@3
|
301 tq, G, A, alpha = get_specific_loudness_parameters(lenSig) # get the shaped loudness parameters
|
c@3
|
302 greater_than_tl = get_greater_than_tl(exc_i) # find which samples are greater than the loud threshold
|
c@3
|
303 specific_loudness[greater_than_tl] = get_specific_loudness_low(exc_low[greater_than_tl], G[greater_than_tl], tq[greater_than_tl], A[greater_than_tl], alpha[greater_than_tl])
|
c@3
|
304 # only process the mid level part of the signal
|
c@3
|
305
|
c@3
|
306 Parameters:
|
c@3
|
307 * exc_high (type: array-like matrix of floats) - the high level (larger than the threshold of high level) excitation pattern
|
c@3
|
308 for each ERB (Required)
|
c@3
|
309
|
c@3
|
310 Returns:
|
c@6
|
311 * specific_loudness_high (type: array-like matrix of floats) - An array with dimensions equal to the exc_high containing the specific loudness of the signal at high levels.
|
c@1
|
312 """
|
c@1
|
313
|
c@6
|
314 C = 0.046871 #constant parameter
|
c@1
|
315 specific_loudness_high = C * (exc_high / (1.04 * 10**6))**0.5
|
c@1
|
316
|
c@1
|
317 return specific_loudness_high
|
c@1
|
318
|
c@1
|
319 def get_greater_than_tl(exc_i):
|
c@1
|
320 """
|
c@1
|
321 A function to return if each element of the excitation intensity is greater than the threshold of loud.
|
c@1
|
322
|
c@1
|
323 Parameters:
|
c@1
|
324 * exc_i (type: array-like matrix of floats) - the input excitation intensity
|
c@1
|
325
|
c@1
|
326 Returns:
|
c@6
|
327 * le_tq (type: array-like matrix of booleans) - a boolean array with dimensions equal to the input specifying if the excitation intensity is greater than the threshold of loud
|
c@1
|
328 """
|
c@1
|
329
|
c@1
|
330 lenSig = np.shape(exc_i)[-1]
|
c@1
|
331 g_tl = exc_i[:,:]>np.transpose(np.tile(10**10,(lenSig,39)))
|
c@1
|
332
|
c@1
|
333 return g_tl
|
c@1
|
334
|
c@1
|
335 def get_less_than_tq(exc_i, tq):
|
c@1
|
336 """
|
c@1
|
337 A function to return if each element of the excitation intensity is less than the threshold of quiet.
|
c@1
|
338
|
c@1
|
339 Parameters:
|
c@1
|
340 * exc_i (type: array-like matrix of floats) - the input excitation intensity
|
c@1
|
341 * tq (type: array-like matrix of floats) - the threshold of quiet for each ERB.
|
c@1
|
342
|
c@1
|
343 Returns:
|
c@6
|
344 * le_tq (type: array-like matrix of booleans) - a boolean array with dimensions equal to the input specifying if the excitation intensity is less than the threshold of quiet
|
c@1
|
345 """
|
c@1
|
346
|
c@1
|
347 if (np.shape(exc_i)!=np.shape(tq)):
|
c@1
|
348 np.transpose(np.tile(exc_i,(np.shape(exc_i)[-1],1)))
|
c@1
|
349
|
c@1
|
350 le_tq = exc_i<=tq
|
c@1
|
351
|
c@1
|
352 return le_tq
|
c@1
|
353
|
c@1
|
354 def get_excitation_i(input, fs, SPL, rectify=False, verbose = False):
|
c@0
|
355 """
|
c@0
|
356 A function to calculate the excitation intensity of the input signal.
|
c@0
|
357
|
c@0
|
358 Parameters:
|
c@0
|
359 * input (type: array-like matrix of floats) - signal normalised to an amplitude range of -1 to 1. (Required)
|
c@0
|
360 * fs (type: numerical) - sample frequency of the signal, input. (Required)
|
c@6
|
361 * SPL (type: double) - the sound pressure level (SPL) at 0 dBFS, e.g., the SPL of a sine wave with peaks at amplitude 1 and troughs at amplitude -1. (Required)
|
c@6
|
362 * rectify (type: boolean) - Specifies whether to include half wave rectification, modelling the direction of that the cochlear nerves vibrate. True to include, False to ignore. (Optional; Default = False)
|
c@0
|
363
|
c@0
|
364 Returns:
|
c@6
|
365 * gtfs (type: numpy array of floats) - array with size ((39,) + np.shape(input)) containing the excitation pattern (in sound intensity) for each ERB of input signal. The excitation pattern for the nth ERB can be accessed with gtfs[n].
|
c@0
|
366 """
|
c@0
|
367
|
c@0
|
368 input = np.array(input)
|
c@5
|
369
|
c@5
|
370 inputPa = v_Pascal(input, SPL)
|
c@5
|
371
|
c@5
|
372 inputOMFir = outMidFir(inputPa)
|
c@5
|
373
|
c@6
|
374 b = gammatone_filter(fs, normalise = True)
|
c@5
|
375 gtfs = decomposition(inputOMFir, b, verbose = verbose)
|
c@5
|
376
|
c@0
|
377 if (rectify):
|
c@0
|
378 gtfs = half_rectification(gtfs)
|
c@5
|
379
|
c@5
|
380 gtfsI = pa_i(gtfs)
|
c@5
|
381 gtfsN = at_normalise(gtfsI)
|
c@0
|
382
|
c@5
|
383 b1,a1 = exp_smoothing(fs)
|
c@5
|
384 gtfsF = np.zeros(np.shape(gtfsI))
|
c@5
|
385
|
c@5
|
386 for i in range(39):
|
c@5
|
387 gtfsF[i] = sp.lfilter(b1,a1,gtfsN[i])
|
c@5
|
388
|
c@5
|
389 return gtfsF
|
c@0
|
390
|
c@4
|
391 def plot_excitation_response(input = None, fs = 44100, outMidFilt = True, gammatone = True, xscale = 'log', yscale = 'log'):
|
c@0
|
392 """
|
c@0
|
393 A function that plots the transfer function of the outer middle ear and each gammatone filter.
|
c@0
|
394
|
c@0
|
395 Parameters:
|
c@0
|
396 * fs (type: numerical) - the sampling frequency of the signal. (Optional; Default = 44100)
|
c@0
|
397 * outMidFilt (type: boolean) - filter the signal by the outer and middle ear FIR filter. (Optional; Default = True)
|
c@0
|
398 * xscale (type: string) - the scale of the frequency axis. Values are 'log' or 'linear'. (Optional; Default = 'log')
|
c@0
|
399 * yscale (type: string) - the scale of the amplitude axis. Values are 'log' or 'linear'. (Optional; Default = 'log')
|
c@0
|
400 """
|
c@1
|
401
|
c@4
|
402 if input == None:
|
c@4
|
403 input = np.zeros((np.ceil(fs)))
|
c@1
|
404 input[0] = 1
|
c@4
|
405
|
c@0
|
406 if(outMidFilt): input = outMidFir(input)
|
c@4
|
407 if(gammatone):
|
c@6
|
408 b = gammatone_filter(fs, normalise = True)
|
c@4
|
409 input = decomposition(input, b)
|
c@6
|
410 #input = holdsworth_gammatone_filter(input,fs)
|
c@4
|
411 numPlot = range(np.shape(input)[0])
|
c@4
|
412 else:
|
c@4
|
413 numPlot = (0,)
|
c@4
|
414 input = input.reshape(1,len(input))
|
c@4
|
415
|
c@4
|
416 for i in numPlot:
|
c@4
|
417 utils.plot_fft(input[i],xscale, yscale, False)
|
c@0
|
418
|
c@0
|
419 plt.show()
|
c@0
|
420
|
c@4
|
421 return
|
c@0
|
422
|
c@0
|
423 def get_modulation_i(input, fs):
|
c@0
|
424 """
|
c@6
|
425 A function to calculate the modulation intensity of the input intensity signal. The function implements a filter bank of bandpass filters with cut off frequencies ranging from 0.25 to 16 Hz.
|
c@0
|
426
|
c@0
|
427 Parameters:
|
c@0
|
428 * input (type: array-like matrix of floats) - the input intensity signal.
|
c@0
|
429 E.g., use get_excitation_i() to obtain excitation intensity and use as input.
|
c@0
|
430 * fs (type: numerical) - sampling frequency of input signal
|
c@0
|
431
|
c@0
|
432 Returns:
|
c@6
|
433 * y (type: numpy array of floats) - array with size ((10,) + np.shape(input)) containing the modulation intensity of the signal at each modulation filter. The modulation intensity for the nth filter can be accessed with y[n].
|
c@0
|
434 """
|
c@0
|
435
|
c@0
|
436 input = np.array(input)
|
c@0
|
437 b = fir_antialias(fs)
|
c@0
|
438 input_lp = sp.lfilter(b,(1),input_fr)
|
c@0
|
439 input_ds = downsample(input_lp, fs)
|
c@0
|
440 fc = np.array(utils.exp_sequence(-2,4,10))
|
c@0
|
441 bw = fc/2
|
c@0
|
442 y = decomposition(input_ds, fs, fc, bw)
|
c@0
|
443
|
c@0
|
444 return y
|
c@0
|
445
|
c@0
|
446 def outMidFir(input):
|
c@0
|
447 """
|
c@0
|
448 A function to filter the input signal with the response of the outer and middle ear.
|
c@0
|
449
|
c@0
|
450 Parameters:
|
c@0
|
451 * input (type: array-like matrix of floats) - signal normalised to an amplitude range of -1 to 1. (Required)
|
c@0
|
452
|
c@0
|
453 Returns:
|
c@6
|
454 * y (type: numpy array of floats) - array with dimensions equal to the input signal filtered by the response of the outer and middle ear.
|
c@0
|
455 """
|
c@0
|
456
|
c@0
|
457 input = np.array(input)
|
c@0
|
458 b = utils.load_outMidFir_coeff()
|
c@0
|
459 y = sp.lfilter(b, (1), input)
|
c@0
|
460
|
c@0
|
461 return y
|
c@0
|
462
|
c@6
|
463 def gammatone_filter(fs, normalise = False):
|
c@0
|
464 """
|
c@6
|
465 A function to return the filter coefficients of 39 different gammatones with fc of ERBs 1 to 39.
|
c@0
|
466
|
c@0
|
467 Parameters:
|
c@0
|
468 * input (type: array-like matrix of floats) - signal normalised to an amplitude range of -1 to 1. (Required)
|
c@0
|
469 * fs (type: numerical) - sample frequency of the signal, input. (Required)
|
c@0
|
470
|
c@0
|
471 Returns:
|
c@6
|
472 * y (type: numpy array of floats) - array with size ((39),np.shape(input)) containing the impulse response of the signal at each gammatone filter. The response at the nth gammatone filter can be accessed by y[n].
|
c@0
|
473 """
|
c@0
|
474
|
c@4
|
475 nerbs = 39
|
c@4
|
476 erbs = np.array(range(1,nerbs+1))
|
c@4
|
477 fc = (10**(erbs/21.366)-1)/0.004368
|
c@4
|
478 bw = 24.673 * (1 + 0.004368*fc)
|
c@4
|
479 N = 4
|
c@4
|
480 filterLength = 4096
|
c@4
|
481 t = 1.0*np.array(range(filterLength))/fs
|
c@4
|
482
|
c@4
|
483 b = np.zeros((nerbs,filterLength))
|
c@4
|
484
|
c@4
|
485 for i in range(39):
|
c@7
|
486 b[i] = t**(N-1) * np.exp(-2*np.pi*bw[i]*t) * np.cos(2*np.pi*fc[i]*t)
|
c@4
|
487
|
c@7
|
488 if normalise:
|
c@7
|
489 A = 1/(np.abs(fft.fft(b[i], 4*filterLength))).max()
|
c@7
|
490 b[i] *= A
|
c@7
|
491
|
c@4
|
492 return b
|
c@4
|
493
|
c@6
|
494 def holdsworth_gammatone_filter(input, fs):
|
c@4
|
495 """
|
c@4
|
496
|
c@4
|
497 """
|
c@0
|
498 input = np.array(input)
|
c@4
|
499 input = input + 0j
|
c@4
|
500
|
c@4
|
501 T = 1.0/fs
|
c@4
|
502
|
c@4
|
503 ERBs = np.array(range(1,40))
|
c@4
|
504 f0 = (10**(ERBs/21.4)-1)/4.37e-3
|
c@4
|
505
|
c@4
|
506 inLen = len(input)
|
c@4
|
507 b = 24.673 * (1 + 0.004368*f0)
|
c@4
|
508 k = np.array(range(inLen)) + 0j
|
c@4
|
509 out = np.zeros((39,inLen))
|
c@4
|
510
|
c@4
|
511 for erb in range(39):
|
c@4
|
512
|
c@4
|
513 zArr = input*np.exp(-2*np.pi*1j*f0[erb]*k*T)
|
c@4
|
514 wArr = np.zeros((inLen+1))
|
c@4
|
515
|
c@4
|
516 for i in range(1,inLen+1):
|
c@4
|
517 wArr[i] = wArr[i-1] + (1 - np.exp(-2*np.pi*b[erb]*T))*(zArr[i-1] - wArr[i-1])
|
c@4
|
518
|
c@4
|
519 out[erb] = (wArr[1:]*np.exp(2*np.pi*1j*f0[erb]*k*T)).real
|
c@4
|
520
|
c@4
|
521 return out
|
c@4
|
522
|
c@0
|
523
|
c@0
|
524 def v_Pascal(input, SPL):
|
c@0
|
525 """
|
c@0
|
526 A function to convert a signal, normalised to an amplitude range of -1 to 1, to a signal represented in pressure (units: Pascal).
|
c@0
|
527
|
c@0
|
528 Parameters:
|
c@0
|
529 * input (type: array-like matrix of floats) - signal normalised to an amplitude range of -1 to 1. (Required)
|
c@0
|
530 * SPL (type: double) - the sound pressure level (SPL) at 0 dBFS, e.g., the SPL of a sine
|
c@0
|
531 wave with peaks at amplitude 1 and troughs at amplitude -1. (Required)
|
c@0
|
532
|
c@0
|
533 Returns:
|
c@0
|
534 * y (type: numpy array of floats) - array with dimensions equal to the input signal containing the input represented
|
c@0
|
535 as a pressure signal.
|
c@6
|
536
|
c@6
|
537 NOTE If input signal contains one or more zeros, a divide by zero warning will be outputted. This warning can be ignored.
|
c@0
|
538 """
|
c@0
|
539
|
c@0
|
540 input = np.array(input)
|
c@5
|
541 y = np.sign(input)*(0.00002*10**(np.log10(np.abs(input))+SPL/20.0))
|
c@0
|
542
|
c@0
|
543 return y
|
c@0
|
544
|
c@0
|
545 def pa_i(input, C=406):
|
c@0
|
546 """
|
c@0
|
547 A function to convert a pressure signal (unit: Pascal) to an intensity signal.
|
c@0
|
548
|
c@0
|
549 Parameters:
|
c@0
|
550 * input (type: array-like matrix of floats) - pressure signal (unit: Pascal) (Required)
|
c@0
|
551 * C (type: double) - the acoustic impedance of the air (Optional; Default = 406)
|
c@0
|
552
|
c@0
|
553 Returns:
|
c@0
|
554 * y (type: numpy array of floats) - array with dimensions equal to the input signal containing the input represented
|
c@0
|
555 as a pressure signal.
|
c@0
|
556 """
|
c@0
|
557
|
c@0
|
558 input = np.array(input)
|
c@0
|
559 y = (input**2) / C
|
c@0
|
560
|
c@0
|
561 return y
|
c@0
|
562
|
c@0
|
563 def at_normalise(input):
|
c@0
|
564 """
|
c@0
|
565 A function to normalise an intensity signal with the audibility threshold.
|
c@0
|
566
|
c@0
|
567 Parameters:
|
c@0
|
568 * input (type: array-like matrix of floats) - intensity signal (unit: Pascal) (Required)
|
c@0
|
569
|
c@0
|
570 Returns:
|
c@0
|
571 * y (type: numpy array of floats) - array with dimensions equal to the input signal containing the input normalised
|
c@0
|
572 with the audibility threshold.
|
c@0
|
573 """
|
c@0
|
574
|
c@0
|
575 input = np.array(input)
|
c@5
|
576 y = input / (1*(10**-12))
|
c@0
|
577
|
c@1
|
578 return y
|
c@0
|
579
|
c@0
|
580 def downsample(input, factor=100):
|
c@0
|
581 """
|
c@0
|
582 A function to downsample a signal, input, with sampling frequency, fs, by a downsample factor of factor.
|
c@0
|
583
|
c@0
|
584 NOTE: It is advised to use the fir_antialias() function before downsampling to remove any high frequencies
|
c@0
|
585 which would otherwise represented as low frequencies due to aliasing.
|
c@0
|
586
|
c@0
|
587 Parameters:
|
c@0
|
588 * input (type: array-like matrix of floats) - input signal. (Required)
|
c@0
|
589 * factor - downsample factor (Optional; Default = 100)
|
c@0
|
590
|
c@0
|
591 Returns:
|
c@0
|
592 * output (type: numpy array of floats) - array with outer dimensions equivalent to the to the input, and
|
c@0
|
593 inner dimension equal to np.floor(lenIn / factor) where lenIn is the length of the inner dimension.
|
c@0
|
594 """
|
c@0
|
595
|
c@0
|
596 input = np.array(input)
|
c@0
|
597 shapeIn = np.shape(input)
|
c@0
|
598 nDim = np.shape(shapeIn)
|
c@0
|
599 lenIn = shapeIn[nDim[0]-1]
|
c@0
|
600 lenOut = np.floor(lenIn / factor)
|
c@0
|
601 n = np.linspace(0,lenIn,lenOut, endpoint=False).astype(np.int)
|
c@0
|
602 output = input[...,n]
|
c@0
|
603
|
c@0
|
604 return output
|
c@0
|
605
|
c@0
|
606 def half_rectification(input):
|
c@0
|
607 """
|
c@0
|
608 A function which performs a half-wave rectification on the input signal.
|
c@0
|
609
|
c@0
|
610 Parameters:
|
c@0
|
611 * input (type: array-like matrix of floats) - input signal. (Required)
|
c@0
|
612
|
c@0
|
613 Returns:
|
c@0
|
614 * y (type: numpy array of floats) - an array with dimensions of input containing the half-wave rectification of
|
c@0
|
615 input.
|
c@0
|
616 """
|
c@0
|
617
|
c@0
|
618
|
c@0
|
619 y = np.array(input)
|
c@0
|
620 y[y<0] = 0
|
c@0
|
621
|
c@0
|
622 return y
|
c@0
|
623
|
c@4
|
624 def decomposition(input, b, a = None, verbose = False):
|
c@0
|
625 """
|
c@4
|
626 A function to run the input through a bandpass filter bank with parameters defined by the b and a coefficints.
|
c@0
|
627
|
c@0
|
628 Parameters:
|
c@0
|
629 * input (type: array-like matrix of floats) - input signal. (Required)
|
c@4
|
630 * b (type: array-like matrix of floats) - the b coefficients of each filter in shape b[numOfFilters][numOfCoeffs]. (Required)
|
c@4
|
631 * a (type: array-like matrix of floats) - the a coefficients of each filter in shape a[numOfFilters][numOfCoeffs].
|
c@4
|
632 If not specified, the filter is treated as a FIR filter. (Optional; Default = 1)
|
c@4
|
633 * verbose (type: boolean) - determines whether to display the current subroutine/progress of the procedure.
|
c@4
|
634 (Optional; Default = False)
|
c@4
|
635
|
c@4
|
636 Returns:
|
c@4
|
637 * y (type: numpy array of floats) - an array with inner dimensions equal to that of the input and outer dimension equal to
|
c@4
|
638 the length of fc (i.e. the number of bandpass filters in the bank) containing the outputs to each filter. The output
|
c@4
|
639 signal of the nth filter can be accessed using y[n].
|
c@4
|
640 """
|
c@4
|
641
|
c@4
|
642 input = np.array(input)
|
c@4
|
643 bShape = np.shape(b)
|
c@4
|
644 nFilters = bShape[0]
|
c@4
|
645
|
c@4
|
646 if a == None:
|
c@4
|
647 a = np.ones(nFilters)
|
c@4
|
648
|
c@4
|
649 shape = (nFilters,) + np.shape(input)
|
c@4
|
650 shape = shape[0:]
|
c@4
|
651 y = np.zeros(shape)
|
c@4
|
652
|
c@6
|
653 if(verbose): sys.stdout.write("Running filterbank decomposition.\n")
|
c@4
|
654 for i in range(nFilters):
|
c@5
|
655 if(verbose):
|
c@5
|
656 sys.stdout.write("\r" + str(int(np.round(100.0*i/nFilters))) + "% complete.")
|
c@5
|
657 sys.stdout.flush()
|
c@8
|
658 x = np.array(input)
|
c@8
|
659 y[i] = sp.fftconvolve(x,b[i],'same')
|
c@6
|
660
|
c@6
|
661 if(verbose): sys.stdout.write("\n")
|
c@6
|
662
|
c@4
|
663 return y
|
c@4
|
664
|
c@4
|
665 def get_Modulation_filter_bank_coefficients(fs, fc, bw, order = 1024, verbose = False):
|
c@4
|
666 """
|
c@4
|
667 A function which returns the b and a coefficients of the modulation filter bank of length equal to the length of fc. Each
|
c@4
|
668 bandpass filter is designed by defining the centre frequencies, fc, and bandwidths, bw.
|
c@4
|
669
|
c@4
|
670 Parameters:
|
c@0
|
671 * fs (type: numerical) - the sampling frequency of the input signal. (Required)
|
c@0
|
672 * fc (type: array-like vector of floats) - the centre off frequencies (unnormalised) of each bandpass filter.
|
c@0
|
673 The length of this vector determines the number of filters in the bank.
|
c@0
|
674 * bw (type: array-like vector of floats) - the bandwidths (unnormalised) of each bandpass filter. Must be equal
|
c@0
|
675 to or more than the length of fc. If the length is more, all elements exceeding the length of fc - 1 will be
|
c@0
|
676 ignored.
|
c@0
|
677 * order (type: numerical int) - the order of the filter (number of taps minus 1) (Optional; Default = 1024)
|
c@0
|
678 * verbose (type: boolean) - determines whether to display the current subroutine/progress of the procedure.
|
c@0
|
679 (Optional; Default = False)
|
c@0
|
680
|
c@0
|
681 Returns:
|
c@4
|
682 * b (type: numpy array of floats) - an array of size order + 1 (i.e. a coefficient for each tap)
|
c@0
|
683 """
|
c@4
|
684
|
c@4
|
685
|
c@0
|
686 nFreqs = len(fc)
|
c@4
|
687
|
c@5
|
688 if(verbose): sys.stdout.write("Running frequency decomposition.")
|
c@4
|
689
|
c@0
|
690 for i in range(nFreqs):
|
c@5
|
691 if(verbose):
|
c@5
|
692 sys.stdout.write("\r" + str(int(np.round(100.0*i/nFreqs))) + "% complete.")
|
c@5
|
693 sys.stdout.flush()
|
c@0
|
694 low = fc[i]-bw[i]/2;
|
c@0
|
695 high = fc[i]+bw[i]/2;
|
c@0
|
696 b = fir_bandpass(low, high, fs, order, verbose)
|
c@4
|
697
|
c@4
|
698 return b
|
c@0
|
699
|
c@3
|
700 def exp_smoothing(fs, tc = 0.01):
|
c@3
|
701 """
|
c@3
|
702 Designs an exponential filter, y[n] = alpha*x[n] - -(1-alpha)*y[n-1], where alpha = T/(tc+T), where T
|
c@3
|
703 is the inverse of the sampling frequency and time constant, tc, is specified.
|
c@3
|
704
|
c@3
|
705 Parameters:
|
c@3
|
706 * fs (type: numerical) - sampling frequency of the signal to be filtered (Required)
|
c@3
|
707 * tc (type: numerical) - the time constant of the filter. (Optional; Default = 0.01)
|
c@3
|
708
|
c@3
|
709 Returns:
|
c@3
|
710 * b (type: numerical) - the coefficient of x[n]. Equal to alpha.
|
c@3
|
711 * a (type: numpy array of floats) - an array of size 2 of the coefficients of y[n] (a[0] = 1) and y[n-1]
|
c@3
|
712 """
|
c@3
|
713
|
c@3
|
714 T = 1.0/fs
|
c@3
|
715 alpha = T/(tc + T)
|
c@3
|
716 b = [alpha]
|
c@3
|
717 a = [1, -(1-alpha)]
|
c@3
|
718
|
c@3
|
719 return b, a
|
c@3
|
720
|
c@3
|
721 def antialias_fir(fs, fc=100, order=64):
|
c@3
|
722 """
|
c@3
|
723 A function which returns the b coefficients for a lowpass fir filter with specified requirements.
|
c@3
|
724 Made specifically to remove aliasing when downsampling, but can be used for any application that
|
c@3
|
725 requires a lowpass filter.
|
c@3
|
726
|
c@3
|
727 Parameters:
|
c@3
|
728 * fs (type: numerical) - sampling frequency of the signal to be filtered (Required)
|
c@3
|
729 * fc (type: numerical) - unnormalised cut off frequency of the filter (Optional; Default = 100)
|
c@3
|
730 * order (type: numerical int) - the order of the filter (number of taps minus 1) (Optional; Default = 64)
|
c@3
|
731
|
c@3
|
732 Returns:
|
c@3
|
733 * b (type: numpy array of floats) - an array of size order + 1 (i.e. a coefficient for each tap)
|
c@3
|
734 """
|
c@3
|
735
|
c@3
|
736 nyquist = 0.5*fs
|
c@3
|
737 fcNorm = fc/nyquist
|
c@3
|
738 b = sp.firwin(order+1, fcNorm)
|
c@3
|
739
|
c@3
|
740 return b
|
c@3
|
741
|
c@4
|
742 def fir_bandpass(low, high, fs, order = 4096, verbose = False):
|
c@0
|
743 """
|
c@0
|
744 A function which returns the b coefficients for a bandpass fir filter with specified requirements.
|
c@0
|
745
|
c@0
|
746 Parameters:
|
c@0
|
747 * low - the lower cutoff frequency of the filter. (Required)
|
c@0
|
748 * high - the upper cutoff frequency of the filter. (Required)
|
c@0
|
749 * fs (type: numerical) - sampling frequency of the signal to be filtered. (Required)
|
c@0
|
750 * order (type: numerical int) - the order of the filter (number of taps minus 1) (Optional; Default = 1024)
|
c@6
|
751 * verbose (type: boolean) - determines whether to display info on the current subroutine
|
c@0
|
752 of the procedure. (Optional; Default = False)
|
c@0
|
753
|
c@0
|
754 Returns:
|
c@0
|
755 * b (type: numpy array of floats) - an array of size order + 1 (i.e. a coefficient for each tap).
|
c@0
|
756 """
|
c@0
|
757
|
c@0
|
758 nyquist = 0.5*fs
|
c@0
|
759 lowNorm = low/nyquist
|
c@0
|
760 highNorm = high/nyquist
|
c@5
|
761 if(verbose): sys.stdout.write("Low: " + str(lowNorm) + "; High: " + str(highNorm))
|
c@0
|
762 b = sp.firwin(order+1, [lowNorm, highNorm], pass_zero=False)
|
c@0
|
763
|
c@0
|
764 return b |