alan@461
|
1 # Carfac.py - Cochlear filter model based on Dick Lyons work. This material taken from his Hearing book (to be published)
|
alan@470
|
2 # Author: Al Strelzoff: strelz@mit.edu
|
alan@461
|
3
|
alan@470
|
4 # The point of this file is to extract some of the formulas from the book and practice with them so as to better understand the filters.
|
alan@470
|
5 # The file has been written and tested for python 2.7
|
alan@461
|
6
|
alan@471
|
7 from numpy import cos, sin, pi, e, real,imag,arctan2,log10
|
alan@470
|
8
|
alan@470
|
9
|
alan@470
|
10 from pylab import figure, plot,loglog, title, axis, show
|
alan@461
|
11
|
alan@461
|
12 fs = 22050.0 # sampling rate
|
alan@461
|
13
|
alan@461
|
14
|
alan@461
|
15 # given a frequency f, return the ERB
|
alan@461
|
16 def ERB_Hz(f):
|
alan@461
|
17 # Ref: Glasberg and Moore: Hearing Research, 47 (1990), 103-138
|
alan@461
|
18 return 24.7 * (1.0 + 4.37 * f / 1000.0)
|
alan@461
|
19
|
alan@461
|
20
|
alan@461
|
21 # ERB parameters
|
alan@461
|
22 ERB_Q = 1000.0/(24.7*4.37) # 9.2645
|
alan@461
|
23 ERB_break_freq = 1000/4.37 # 228.833
|
alan@461
|
24
|
alan@461
|
25 ERB_per_step = 0.3333
|
alan@461
|
26
|
alan@461
|
27 # set up channels
|
alan@461
|
28
|
alan@461
|
29 first_pole_theta = .78 * pi # We start at the top frequency.
|
alan@461
|
30 pole_Hz = first_pole_theta * fs / (2.0*pi) # frequency of top pole
|
alan@461
|
31 min_pole_Hz = 40.0 # bottom frequency
|
alan@461
|
32
|
alan@461
|
33 # set up the pole frequencies according to the above parameters
|
alan@461
|
34 pole_freqs = [] # empty list of pole frequencies to fill, zeroth will be the top
|
alan@461
|
35 while pole_Hz > min_pole_Hz:
|
alan@461
|
36 pole_Hz = pole_Hz - ERB_per_step * ERB_Hz(pole_Hz)
|
alan@461
|
37 pole_freqs.append(pole_Hz)
|
alan@461
|
38
|
alan@461
|
39 n_ch = len(pole_freqs) # n_ch is the number of channels or frequency steps
|
alan@461
|
40 print('num channels',n_ch)
|
alan@461
|
41
|
alan@461
|
42 # Now we have n_ch, the number of channels, so can make the array of filters by instantiating the filter class (see below)
|
alan@461
|
43
|
alan@461
|
44 # before we make the filters, let's plot the position of the frequencies and the values of ERB at each.
|
alan@461
|
45
|
alan@461
|
46 fscale = []
|
alan@461
|
47 erbs = []
|
alan@461
|
48
|
alan@470
|
49 figure(1)
|
alan@461
|
50 for i in range(n_ch):
|
alan@461
|
51
|
alan@461
|
52 f = pole_freqs[i] # the frequencies from the list
|
alan@461
|
53 ERB = ERB_Hz(f) # the ERB value at each frequency
|
alan@461
|
54 fscale.append(f)
|
alan@461
|
55 erbs.append(ERB)
|
alan@461
|
56
|
alan@461
|
57 # plot a verticle hash at each frequency:
|
alan@461
|
58 u = []
|
alan@461
|
59 v = []
|
alan@461
|
60 for j in range(5):
|
alan@461
|
61 u.append(f)
|
alan@461
|
62 v.append(10.0 + float(j))
|
alan@461
|
63
|
alan@461
|
64 plot(u,v)
|
alan@461
|
65
|
alan@461
|
66 loglog(fscale,erbs)
|
alan@461
|
67
|
alan@461
|
68 title('ERB scale')
|
alan@461
|
69
|
alan@461
|
70
|
alan@461
|
71
|
alan@461
|
72 # This filter class includes some methods useful only in design. They will not be used in run time implementation.
|
alan@461
|
73 # From figure 14.3 in Dick Lyon's book.
|
alan@470
|
74 # When translating to C++, this class will become a struct, and all the methods will be moved outside.
|
alan@461
|
75
|
alan@461
|
76
|
alan@461
|
77 #########################################################The Carfac filter class#################################################################################
|
alan@461
|
78
|
alan@461
|
79 # fixed parameters
|
alan@461
|
80 min_zeta = 0.12
|
alan@461
|
81
|
alan@461
|
82 class carfac():
|
alan@461
|
83
|
alan@461
|
84
|
alan@461
|
85 # instantiate the class (in C++, the constructor)
|
alan@461
|
86 def __init__(self,f):
|
alan@461
|
87
|
alan@461
|
88 self.frequency = f
|
alan@461
|
89
|
alan@461
|
90 theta = 2.0 * pi * f/fs
|
alan@461
|
91 r = 1.0 - sin(theta) * min_zeta
|
alan@461
|
92 a = r * cos(theta)
|
alan@461
|
93 c = r * sin(theta)
|
alan@470
|
94 h = sin(theta)
|
alan@470
|
95 g = (1.0 - 2.0 * a + r ** 2)/(1.0 - 2.0 * a + h * c + r ** 2)
|
alan@470
|
96
|
alan@470
|
97
|
alan@461
|
98
|
alan@470
|
99 self.gh = g*h # no need to repeat in real time
|
alan@461
|
100
|
alan@461
|
101 # make all parameters properties of the class
|
alan@461
|
102 self.a = a
|
alan@461
|
103 self.c = c
|
alan@461
|
104 self.r = r
|
alan@461
|
105 self.theta = theta
|
alan@461
|
106 self.h = h
|
alan@461
|
107 self.g = g
|
alan@461
|
108
|
alan@461
|
109
|
alan@461
|
110 # the two storage elements. Referring to diagram 14.3 on p.263, z2 is the upper storage register, z1, the lower
|
alan@461
|
111 self.z1 = 0.0
|
alan@461
|
112 self.z2 = 0.0
|
alan@461
|
113
|
alan@461
|
114
|
alan@461
|
115 # frequency response of this filter
|
alan@461
|
116 self.H = []
|
alan@461
|
117
|
alan@461
|
118
|
alan@461
|
119
|
alan@461
|
120 # the total frequency magnitude of this filter including all the filters in front of this one
|
alan@461
|
121 self.HT = [] # this list will be filled by multiplying all the H's ahead of it together with its own (H)
|
alan@461
|
122
|
alan@461
|
123
|
alan@461
|
124
|
alan@461
|
125 # execute one clock tick. Take in one input and output one result. Execution semantics taken from fig. 14.3
|
alan@461
|
126 # This execution model is not tested in this file. Here for reference. See the file Exec.py for testing this execution model. This is the main run time method.
|
alan@461
|
127 def input(self,X):
|
alan@461
|
128
|
alan@461
|
129 # recover the class definitions of these variables. These statements below take up zero time at execution since they are just compiler declarations.
|
alan@470
|
130 # computation below is organized as some loads, followed by 3 2x2 multiply accumulates
|
alan@470
|
131 # Note: this function is not exercised in this file and is here only for reference
|
alan@461
|
132
|
alan@461
|
133 a = self.a
|
alan@461
|
134 c = self.c
|
alan@461
|
135 g = self.g
|
alan@470
|
136 gh = self.gh
|
alan@461
|
137 z1 = self.z1 # z1 is the lower storage in fig. 14.3
|
alan@461
|
138 z2 = self.z2
|
alan@461
|
139
|
alan@461
|
140 # calculate what the next value of z1 will be, but don't overwrite current value yet.
|
alan@470
|
141 next_z1 = (a * z1) + (c * z2) # Note: it is a 2 element multiply accumulate. compute first so as not to have to do twice.
|
alan@461
|
142 # the output Y
|
alan@470
|
143 Y = g * X + gh * next_z1 # Note: organized as a 2 element multiply accumulate.
|
alan@461
|
144
|
alan@461
|
145 #stores
|
alan@470
|
146 self.z2 = X + (a * z2) - (c * z1) #Note: this is a 2 element multiply accumulate
|
alan@470
|
147 self.z1 = next_z1
|
alan@461
|
148
|
alan@461
|
149 return Y # The output
|
alan@461
|
150
|
alan@461
|
151 # complex frequency response of this filter at frequency w. That is, what it contributes to the cascade
|
alan@461
|
152 # this method is used for test only. It finds the frequency magnitude. Not included in run time filter class.
|
alan@461
|
153 def Hw(self,w):
|
alan@461
|
154
|
alan@461
|
155 a = self.a
|
alan@461
|
156 c = self.c
|
alan@461
|
157 g = self.g
|
alan@461
|
158 h = self.h
|
alan@461
|
159 r = self.r
|
alan@461
|
160 z = e ** (complex(0,w)) # w is in radians so this is z = exp(jw)
|
alan@470
|
161 return g * (1.0 + (h*c*z)/(z**2 - 2.0*a*z + r**2 ))
|
alan@461
|
162
|
alan@461
|
163
|
alan@461
|
164
|
alan@461
|
165 ######################################################End of Carfac filter class########################################################################
|
alan@471
|
166
|
alan@471
|
167
|
alan@461
|
168 # instantiate the filters
|
alan@461
|
169
|
alan@461
|
170 # n_ch is the number of filters as determined above
|
alan@461
|
171
|
alan@461
|
172 Filters = [] # the list of all filters, the zeroth is the top frequency
|
alan@461
|
173 for i in range(n_ch):
|
alan@461
|
174 f = pole_freqs[i]
|
alan@461
|
175 filter = carfac(f) # note: get the correct parameters for r and h from Dick's matlab script. Load them here from a table.
|
alan@461
|
176 Filters.append(filter)
|
alan@461
|
177
|
alan@461
|
178
|
alan@461
|
179
|
alan@461
|
180 # sweep parameters
|
alan@461
|
181 steps = 1000
|
alan@461
|
182
|
alan@470
|
183 figure(2)
|
alan@470
|
184 title('CarFac individual filter frequency response')
|
alan@470
|
185 # note: the scales are linear, not logrithmic as in the book
|
alan@461
|
186
|
alan@461
|
187
|
alan@461
|
188 for i in range(n_ch):
|
alan@461
|
189 filter = Filters[i]
|
alan@461
|
190 # plotting arrays
|
alan@461
|
191 u = []
|
alan@461
|
192 v = []
|
alan@461
|
193 # calculate the frequency magnitude by stepping the frequency in radians
|
alan@461
|
194 for j in range(steps):
|
alan@461
|
195
|
alan@470
|
196 w = pi * float(j)/steps
|
alan@461
|
197 u.append(w)
|
alan@461
|
198 mag = filter.Hw(w) # freq mag at freq w
|
alan@461
|
199 filter.H.append(mag) # save for later use
|
alan@461
|
200 filter.HT.append(mag) # will be total response of cascade to this point after we do the multiplication in a step below
|
alan@470
|
201 v.append(abs(mag)) # y plotting axis
|
alan@461
|
202
|
alan@461
|
203
|
alan@461
|
204 plot(u,v)
|
alan@461
|
205
|
alan@461
|
206
|
alan@461
|
207 # calculate the phase response of the same group of filters
|
alan@461
|
208 figure(3)
|
alan@470
|
209 title('Carfac individual filter Phase lag')
|
alan@461
|
210
|
alan@461
|
211
|
alan@461
|
212 for i in range(n_ch):
|
alan@461
|
213 filter = Filters[i]
|
alan@461
|
214
|
alan@461
|
215 u = []
|
alan@461
|
216 v = []
|
alan@461
|
217 for j in range(steps):
|
alan@470
|
218 x = pi * float(j)/steps
|
alan@461
|
219
|
alan@461
|
220 u.append(x)
|
alan@461
|
221
|
alan@461
|
222 mag = filter.H[j]
|
alan@470
|
223 phase = arctan2(-imag(mag),-real(mag)) - pi # this formula used to avoid wrap around
|
alan@461
|
224
|
alan@461
|
225 v.append(phase) # y plotting axis
|
alan@461
|
226
|
alan@461
|
227 plot(u,v)
|
alan@470
|
228 axis([0.0,pi,-3.0,0.05])
|
alan@461
|
229
|
alan@461
|
230
|
alan@470
|
231 # calulate and plot cascaded frequency response
|
alan@461
|
232
|
alan@461
|
233 figure(4)
|
alan@470
|
234 title('CarFac cascaded filter frequency response')
|
alan@461
|
235
|
alan@461
|
236
|
alan@461
|
237 for i in range(n_ch-1):
|
alan@461
|
238
|
alan@461
|
239 filter = Filters[i]
|
alan@461
|
240 next = Filters[i+1]
|
alan@461
|
241
|
alan@461
|
242
|
alan@461
|
243 u = []
|
alan@461
|
244 v = []
|
alan@461
|
245 for j in range(steps):
|
alan@470
|
246 w = pi * float(j)/steps
|
alan@470
|
247 u.append(w)
|
alan@461
|
248 mag = filter.HT[j] * next.HT[j]
|
alan@470
|
249 next.HT[j] = mag
|
alan@470
|
250 v.append(abs(mag))
|
alan@461
|
251
|
alan@461
|
252
|
alan@461
|
253 plot(u,v)
|
alan@461
|
254
|
alan@461
|
255
|
alan@461
|
256
|
alan@461
|
257 # calculate and plot the phase responses of the cascaded filters
|
alan@461
|
258
|
alan@470
|
259 figure(5)
|
alan@470
|
260 title('Carfac cascaded filter Phase lag')
|
alan@461
|
261
|
alan@461
|
262 for i in range(n_ch):
|
alan@470
|
263
|
alan@461
|
264 filter = Filters[i]
|
alan@461
|
265
|
alan@461
|
266
|
alan@461
|
267 u = []
|
alan@470
|
268 v = [] # store list of phases
|
alan@470
|
269 w = [] # second copy of phases needed for phase unwrapping
|
alan@461
|
270 for j in range(steps):
|
alan@470
|
271 x = pi * float(j)/steps
|
alan@461
|
272
|
alan@461
|
273 u.append(x)
|
alan@461
|
274 mag = filter.HT[j]
|
alan@470
|
275 a = imag(mag)
|
alan@470
|
276 b = real(mag)
|
alan@470
|
277 phase = arctan2(a,b)
|
alan@461
|
278
|
alan@470
|
279 v.append(phase)
|
alan@470
|
280 w.append(phase)
|
alan@461
|
281
|
alan@470
|
282 # unwrap the phase
|
alan@461
|
283
|
alan@461
|
284
|
alan@470
|
285 for i in range(1,len(v)):
|
alan@470
|
286 diff = v[i]-v[i-1]
|
alan@470
|
287 if diff > pi:
|
alan@470
|
288 for j in range(i,len(w)):
|
alan@470
|
289 w[j] -= 2.0 * pi
|
alan@470
|
290 elif diff < -pi:
|
alan@470
|
291 for j in range(i,len(w)):
|
alan@470
|
292 w[j] += 2.0 * pi
|
alan@470
|
293
|
alan@470
|
294 else: continue
|
alan@471
|
295
|
alan@471
|
296 # convert delay to cycles
|
alan@471
|
297 for i in range(len(w)):
|
alan@471
|
298 w[i] /= 2.0 * pi
|
alan@471
|
299
|
alan@470
|
300
|
alan@470
|
301 plot(u,w)
|
alan@471
|
302 axis([0.0,pi,-5.0,0.05])
|
alan@471
|
303
|
alan@471
|
304 # calculate group delay as cascaded lag/filter center frequency
|
alan@471
|
305
|
alan@471
|
306 figure(6)
|
alan@471
|
307 title('Carfac group delay')
|
alan@471
|
308
|
alan@471
|
309 for i in range(n_ch):
|
alan@471
|
310
|
alan@471
|
311 filter = Filters[i]
|
alan@471
|
312
|
alan@471
|
313
|
alan@471
|
314 u = []
|
alan@471
|
315 v = [] # store list of phases
|
alan@471
|
316 w = [] # second copy of phases needed for phase unwrapping
|
alan@471
|
317 for j in range(1,steps):
|
alan@471
|
318 x = pi * float(j)/steps
|
alan@471
|
319
|
alan@471
|
320 u.append(x)
|
alan@471
|
321 mag = filter.HT[j]
|
alan@471
|
322 a = imag(mag)
|
alan@471
|
323 b = real(mag)
|
alan@471
|
324 phase = arctan2(a,b)
|
alan@471
|
325
|
alan@471
|
326 v.append(phase)
|
alan@471
|
327 w.append(phase)
|
alan@471
|
328
|
alan@471
|
329
|
alan@471
|
330 for i in range(1,len(v)):
|
alan@471
|
331 diff = v[i]-v[i-1]
|
alan@471
|
332 if diff > pi:
|
alan@471
|
333 for j in range(i,len(w)):
|
alan@471
|
334 w[j] -= 2.0 * pi
|
alan@471
|
335 elif diff < -pi:
|
alan@471
|
336 for j in range(i,len(w)):
|
alan@471
|
337 w[j] += 2.0 * pi
|
alan@471
|
338
|
alan@471
|
339 else: continue
|
alan@471
|
340
|
alan@471
|
341 # calculate the group delay from: GroupDelay(n)=-[((ph(n+1)-ph(n-1))/(w(n+1)-w(n-1))]/2pi # w in radians
|
alan@471
|
342 # or gd[n] = - ((delta ph)/(delta w))/2pi
|
alan@471
|
343 # delta ph = w[n+1] - w[n-1]
|
alan@471
|
344 # delta w = pi* fs/steps # we divide up the frequency range fs into fractions of pi radians.
|
alan@471
|
345 # gd[n] = - steps(w[n+1] - w[n-1])/fs
|
alan@471
|
346
|
alan@471
|
347 A = -(float(steps))/(2 *fs) # note. could have calculated this once outside the filter loop
|
alan@471
|
348
|
alan@471
|
349 # gd[n] = A *(w[n+1] - w[n-1])
|
alan@471
|
350
|
alan@471
|
351 gd = [] # in radians
|
alan@471
|
352 gd.append(0.0) # pad to equalize size of u array, end points are then meaningless
|
alan@471
|
353 for n in range(1,len(w)-1):
|
alan@471
|
354 gd.append(A * (w[n+1] - w[n-1]))
|
alan@471
|
355
|
alan@471
|
356 gd.append(0.0)
|
alan@471
|
357
|
alan@471
|
358 plot(u,gd)
|
alan@471
|
359 axis([0.1,pi,-0.01,0.05])
|
alan@471
|
360
|
alan@461
|
361 show()
|
alan@461
|
362
|