alan@522
|
1 # Carfac.py - Cochlear filter model based on Dick Lyons work. This material taken from his Hearing book (to be published)
|
alan@522
|
2 # Author: Al Strelzoff
|
alan@522
|
3
|
alan@522
|
4 from numpy import cos, sin, tan, sinh, arctan, pi, e, real,imag,arccos,arcsin,arctan2,log10,log
|
alan@522
|
5
|
alan@522
|
6 from pylab import figure, clf, plot,loglog, xlabel, ylabel, xlim, ylim, title, grid, axes, axis, show
|
alan@522
|
7
|
alan@522
|
8 fs = 22050.0 # sampling rate
|
alan@522
|
9 Nyq = fs/2.0 # nyquist frequency
|
alan@522
|
10
|
alan@522
|
11
|
alan@522
|
12 # given a frequency f, return the ERB
|
alan@522
|
13 def ERB_Hz(f):
|
alan@522
|
14 # Ref: Glasberg and Moore: Hearing Research, 47 (1990), 103-138
|
alan@522
|
15 return 24.7 * (1.0 + 4.37 * f / 1000.0)
|
alan@522
|
16
|
alan@522
|
17
|
alan@522
|
18 # ERB parameters
|
alan@522
|
19 ERB_Q = 1000.0/(24.7*4.37) # 9.2645
|
alan@522
|
20 ERB_break_freq = 1000/4.37 # 228.833
|
alan@522
|
21
|
alan@522
|
22 ERB_per_step = 0.3333
|
alan@522
|
23
|
alan@522
|
24 # set up channels
|
alan@522
|
25
|
alan@522
|
26 first_pole_theta = .78 * pi # We start at the top frequency.
|
alan@522
|
27 pole_Hz = first_pole_theta * fs / (2.0*pi) # frequency of top pole
|
alan@522
|
28 min_pole_Hz = 40.0 # bottom frequency
|
alan@522
|
29
|
alan@522
|
30 # set up the pole frequencies according to the above parameters
|
alan@522
|
31 pole_freqs = [] # empty list of pole frequencies to fill, zeroth will be the top
|
alan@522
|
32 while pole_Hz > min_pole_Hz:
|
alan@522
|
33 pole_Hz = pole_Hz - ERB_per_step * ERB_Hz(pole_Hz)
|
alan@522
|
34 pole_freqs.append(pole_Hz)
|
alan@522
|
35
|
alan@522
|
36 n_ch = len(pole_freqs) # n_ch is the number of channels or frequency steps
|
alan@522
|
37 print('num channels',n_ch)
|
alan@522
|
38
|
alan@522
|
39 # Now we have n_ch, the number of channels, so can make the array of filters by instantiating the filter class (see below)
|
alan@522
|
40
|
alan@522
|
41 # before we make the filters, let's plot the position of the frequencies and the values of ERB at each.
|
alan@522
|
42
|
alan@522
|
43 fscale = []
|
alan@522
|
44 erbs = []
|
alan@522
|
45
|
alan@522
|
46 figure(0)
|
alan@522
|
47 for i in range(n_ch):
|
alan@522
|
48
|
alan@522
|
49 f = pole_freqs[i] # the frequencies from the list
|
alan@522
|
50 ERB = ERB_Hz(f) # the ERB value at each frequency
|
alan@522
|
51 fscale.append(f)
|
alan@522
|
52 erbs.append(ERB)
|
alan@522
|
53
|
alan@522
|
54 # plot a verticle hash at each frequency:
|
alan@522
|
55 u = []
|
alan@522
|
56 v = []
|
alan@522
|
57 for j in range(5):
|
alan@522
|
58 u.append(f)
|
alan@522
|
59 v.append(10.0 + float(j))
|
alan@522
|
60
|
alan@522
|
61 plot(u,v)
|
alan@522
|
62
|
alan@522
|
63 loglog(fscale,erbs)
|
alan@522
|
64
|
alan@522
|
65 title('ERB scale')
|
alan@522
|
66
|
alan@522
|
67
|
alan@522
|
68
|
alan@522
|
69 # This filter class includes some methods useful only in design. They will not be used in run time implementation.
|
alan@522
|
70 # From figure 14.3 in Dick Lyon's book.
|
alan@522
|
71
|
alan@522
|
72
|
alan@522
|
73 #########################################################The Carfac filter class#################################################################################
|
alan@522
|
74
|
alan@522
|
75 # fixed parameters
|
alan@522
|
76 min_zeta = 0.12
|
alan@522
|
77
|
alan@522
|
78 class carfac():
|
alan@522
|
79
|
alan@522
|
80
|
alan@522
|
81 # instantiate the class (in C++, the constructor)
|
alan@522
|
82 def __init__(self,f):
|
alan@522
|
83
|
alan@522
|
84 self.frequency = f
|
alan@522
|
85
|
alan@522
|
86 theta = 2.0 * pi * f/fs
|
alan@522
|
87
|
alan@522
|
88 r = 1.0 - sin(theta) * min_zeta
|
alan@522
|
89
|
alan@522
|
90
|
alan@522
|
91 a = r * cos(theta)
|
alan@522
|
92 c = r * sin(theta)
|
alan@522
|
93
|
alan@522
|
94
|
alan@522
|
95 h = c
|
alan@522
|
96
|
alan@522
|
97 g = 1.0/(1.0 + h * r * sin(theta) / (1.0 - 2.0 * r * cos(theta) + r ** 2))
|
alan@522
|
98
|
alan@522
|
99 # make all parameters properties of the class
|
alan@522
|
100 self.a = a
|
alan@522
|
101 self.c = c
|
alan@522
|
102 self.r = r
|
alan@522
|
103 self.theta = theta
|
alan@522
|
104 self.h = h
|
alan@522
|
105 self.g = g
|
alan@522
|
106
|
alan@522
|
107
|
alan@522
|
108 # the two storage elements. Referring to diagram 14.3 on p.263, z2 is the upper storage register, z1, the lower
|
alan@522
|
109 self.z1 = 0.0
|
alan@522
|
110 self.z2 = 0.0
|
alan@522
|
111
|
alan@522
|
112
|
alan@522
|
113 # frequency response of this filter
|
alan@522
|
114 self.H = []
|
alan@522
|
115
|
alan@522
|
116
|
alan@522
|
117
|
alan@522
|
118 # the total frequency magnitude of this filter including all the filters in front of this one
|
alan@522
|
119 self.HT = [] # this list will be filled by multiplying all the H's ahead of it together with its own (H)
|
alan@522
|
120
|
alan@522
|
121
|
alan@522
|
122
|
alan@522
|
123
|
alan@522
|
124
|
alan@522
|
125
|
alan@522
|
126 # execute one clock tick. Take in one input and output one result. Execution semantics taken from fig. 14.3
|
alan@522
|
127 # 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@522
|
128 def input(self,X):
|
alan@522
|
129
|
alan@522
|
130 # recover the class definitions of these variables. These statements below take up zero time at execution since they are just compiler declarations.
|
alan@522
|
131
|
alan@522
|
132
|
alan@522
|
133 a = self.a
|
alan@522
|
134 c = self.c
|
alan@522
|
135 h = self.h
|
alan@522
|
136 g = self.g
|
alan@522
|
137 z1 = self.z1 # z1 is the lower storage in fig. 14.3
|
alan@522
|
138 z2 = self.z2
|
alan@522
|
139
|
alan@522
|
140 # calculate what the next value of z1 will be, but don't overwrite current value yet.
|
alan@522
|
141 next_z1 = (a * z1) - (c * z2) # Note: view this as next_z1 = a*z1 + (-c*z2) so that it is a 2 element multiply accumulate
|
alan@522
|
142 # the output Y
|
alan@522
|
143 Y = g * (X + h * next_z1) # Note: reorganize this as Y = g*X + (g*h) * next_z1 g*h is a precomputed constant so then the form is a 2 element multiply accumulate.
|
alan@522
|
144
|
alan@522
|
145 #stores
|
alan@522
|
146 z2 = (a * z2) + (c * z1) #Note: this is a 2 element multiply accumulate
|
alan@522
|
147 z1 = next_z1
|
alan@522
|
148
|
alan@522
|
149 return Y # The output
|
alan@522
|
150
|
alan@522
|
151 # complex frequency response of this filter at frequency w. That is, what it contributes to the cascade
|
alan@522
|
152 # this method is used for test only. It finds the frequency magnitude. Not included in run time filter class.
|
alan@522
|
153 def Hw(self,w):
|
alan@522
|
154
|
alan@522
|
155 a = self.a
|
alan@522
|
156 c = self.c
|
alan@522
|
157 g = self.g
|
alan@522
|
158 h = self.h
|
alan@522
|
159 r = self.r
|
alan@522
|
160 z = e ** (complex(0,w)) # w is in radians so this is z = exp(jw)
|
alan@522
|
161 return g * (1.0 + (h*c*z)/(z**2 - 2.0*a*z + r**2 )) # from page ?? of Lyon's book.
|
alan@522
|
162
|
alan@522
|
163
|
alan@522
|
164 # Note: to get the complex frequency response of this filter at frequency w, get Hw(w) and then compute arctan2(-imag(Hw(w))/-real(Hw(w)) + pi
|
alan@522
|
165
|
alan@522
|
166
|
alan@522
|
167
|
alan@522
|
168
|
alan@522
|
169
|
alan@522
|
170 ######################################################End of Carfac filter class########################################################################
|
alan@522
|
171
|
alan@522
|
172 # instantiate the filters
|
alan@522
|
173
|
alan@522
|
174 # n_ch is the number of filters as determined above
|
alan@522
|
175
|
alan@522
|
176 Filters = [] # the list of all filters, the zeroth is the top frequency
|
alan@522
|
177 for i in range(n_ch):
|
alan@522
|
178 f = pole_freqs[i]
|
alan@522
|
179 filter = carfac(f) # note: get the correct parameters for r and h from Dick's matlab script. Load them here from a table.
|
alan@522
|
180 Filters.append(filter)
|
alan@522
|
181
|
alan@522
|
182
|
alan@522
|
183
|
alan@522
|
184 # sweep parameters
|
alan@522
|
185 steps = 1000
|
alan@522
|
186
|
alan@522
|
187 sum = [] # array to hold the magnitude sum
|
alan@522
|
188 for i in range(steps): sum.append( 0.0 )
|
alan@522
|
189
|
alan@522
|
190 figure(1)
|
alan@522
|
191 title('CarFac frequency response')
|
alan@522
|
192
|
alan@522
|
193 for i in range(n_ch):
|
alan@522
|
194 filter = Filters[i]
|
alan@522
|
195 # plotting arrays
|
alan@522
|
196 u = []
|
alan@522
|
197 v = []
|
alan@522
|
198 # calculate the frequency magnitude by stepping the frequency in radians
|
alan@522
|
199 for j in range(steps):
|
alan@522
|
200
|
alan@522
|
201 w = pi * float(j)/steps
|
alan@522
|
202 u.append(w)
|
alan@522
|
203 mag = filter.Hw(w) # freq mag at freq w
|
alan@522
|
204 filter.H.append(mag) # save for later use
|
alan@522
|
205 filter.HT.append(mag) # will be total response of cascade to this point after we do the multiplication in a step below
|
alan@522
|
206 v.append(real(mag)) # y plotting axis
|
alan@522
|
207 sum[j]+= mag
|
alan@522
|
208
|
alan@522
|
209
|
alan@522
|
210 plot(u,v)
|
alan@522
|
211
|
alan@522
|
212
|
alan@522
|
213
|
alan@522
|
214 figure(2)
|
alan@522
|
215 title('Summed frequency magnitudes')
|
alan@522
|
216 for i in range(steps): sum[i] = abs(sum[i])/n_ch
|
alan@522
|
217 plot(u,sum)
|
alan@522
|
218
|
alan@522
|
219 # calculate the phase response of the same group of filters
|
alan@522
|
220 figure(3)
|
alan@522
|
221 title('Filter Phase')
|
alan@522
|
222
|
alan@522
|
223
|
alan@522
|
224 for i in range(n_ch):
|
alan@522
|
225 filter = Filters[i]
|
alan@522
|
226
|
alan@522
|
227 u = []
|
alan@522
|
228 v = []
|
alan@522
|
229 for j in range(steps):
|
alan@522
|
230 x = float(j)/Nyq
|
alan@522
|
231
|
alan@522
|
232 u.append(x)
|
alan@522
|
233
|
alan@522
|
234 mag = filter.H[j]
|
alan@522
|
235 phase = arctan2(-imag(mag),-real(mag)) + pi # this formula used to avoid wrap around
|
alan@522
|
236
|
alan@522
|
237 v.append(phase) # y plotting axis
|
alan@522
|
238
|
alan@522
|
239 plot(u,v)
|
alan@522
|
240
|
alan@522
|
241
|
alan@522
|
242
|
alan@522
|
243 # calulate and plot cascaded frequency response and summed magnitude
|
alan@522
|
244 sum = [] # array to hold the magnitude sum
|
alan@522
|
245 for i in range(steps): sum.append( 0.0 )
|
alan@522
|
246
|
alan@522
|
247
|
alan@522
|
248 figure(4)
|
alan@522
|
249 title('CarFac Cascaded frequency response')
|
alan@522
|
250
|
alan@522
|
251
|
alan@522
|
252 for i in range(n_ch-1):
|
alan@522
|
253
|
alan@522
|
254 filter = Filters[i]
|
alan@522
|
255 next = Filters[i+1]
|
alan@522
|
256
|
alan@522
|
257
|
alan@522
|
258 u = []
|
alan@522
|
259 v = []
|
alan@522
|
260 for j in range(steps):
|
alan@522
|
261 u.append(float(j)/Nyq)
|
alan@522
|
262 mag = filter.HT[j] * next.HT[j]
|
alan@522
|
263 filter.HT[j] = mag
|
alan@522
|
264 v.append(real(mag))
|
alan@522
|
265 sum[j]+= mag
|
alan@522
|
266
|
alan@522
|
267
|
alan@522
|
268 plot(u,v)
|
alan@522
|
269
|
alan@522
|
270
|
alan@522
|
271
|
alan@522
|
272 figure(5)
|
alan@522
|
273 title('Summed cascaded frequency magnitudes')
|
alan@522
|
274 for i in range(steps): sum[i] = abs(sum[i])/n_ch
|
alan@522
|
275 plot(u,sum)
|
alan@522
|
276
|
alan@522
|
277 # calculate and plot the phase responses of the cascaded filters
|
alan@522
|
278
|
alan@522
|
279 figure(6)
|
alan@522
|
280 title('Filter cascaded Phase')
|
alan@522
|
281
|
alan@522
|
282
|
alan@522
|
283 for i in range(n_ch):
|
alan@522
|
284 filter = Filters[i]
|
alan@522
|
285
|
alan@522
|
286
|
alan@522
|
287 u = []
|
alan@522
|
288 v = []
|
alan@522
|
289 for j in range(steps):
|
alan@522
|
290 x = float(j)/Nyq
|
alan@522
|
291
|
alan@522
|
292 u.append(x)
|
alan@522
|
293 mag = filter.HT[j]
|
alan@522
|
294 phase = arctan2(-imag(mag),-real(mag)) + pi
|
alan@522
|
295
|
alan@522
|
296 v.append(phase) # y plotting axis
|
alan@522
|
297
|
alan@522
|
298
|
alan@522
|
299
|
alan@522
|
300
|
alan@522
|
301 plot(u,v)
|
alan@522
|
302
|
alan@522
|
303 show()
|
alan@522
|
304
|