annotate matlab/bmm/carfac/Carfac.py @ 610:01986636257a

Second check-in of Alex Brandmeyer's C++ implementation of CARFAC. Addressed style issues and completed implementation of remaining functions. Still needs proper testing of the output stages against the MATLAB version, and runtime functions need improvements in efficiency.
author alexbrandmeyer
date Thu, 16 May 2013 17:33:23 +0000
parents 40cc1c0f20a8
children
rev   line source
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