alan@522: # Carfac.py - Cochlear filter model based on Dick Lyons work. This material taken from his Hearing book (to be published) alan@531: # Author: Al Strelzoff: strelz@mit.edu alan@522: alan@531: # 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@531: # The file has been written and tested for python 2.7 alan@522: alan@532: from numpy import cos, sin, pi, e, real,imag,arctan2,log10 alan@531: alan@531: alan@531: from pylab import figure, plot,loglog, title, axis, show alan@522: alan@522: fs = 22050.0 # sampling rate alan@522: alan@522: alan@522: # given a frequency f, return the ERB alan@522: def ERB_Hz(f): alan@522: # Ref: Glasberg and Moore: Hearing Research, 47 (1990), 103-138 alan@522: return 24.7 * (1.0 + 4.37 * f / 1000.0) alan@522: alan@522: alan@522: # ERB parameters alan@522: ERB_Q = 1000.0/(24.7*4.37) # 9.2645 alan@522: ERB_break_freq = 1000/4.37 # 228.833 alan@522: alan@522: ERB_per_step = 0.3333 alan@522: alan@522: # set up channels alan@522: alan@522: first_pole_theta = .78 * pi # We start at the top frequency. alan@522: pole_Hz = first_pole_theta * fs / (2.0*pi) # frequency of top pole alan@522: min_pole_Hz = 40.0 # bottom frequency alan@522: alan@522: # set up the pole frequencies according to the above parameters alan@522: pole_freqs = [] # empty list of pole frequencies to fill, zeroth will be the top alan@522: while pole_Hz > min_pole_Hz: alan@522: pole_Hz = pole_Hz - ERB_per_step * ERB_Hz(pole_Hz) alan@522: pole_freqs.append(pole_Hz) alan@522: alan@522: n_ch = len(pole_freqs) # n_ch is the number of channels or frequency steps alan@522: print('num channels',n_ch) alan@522: alan@522: # 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: alan@522: # before we make the filters, let's plot the position of the frequencies and the values of ERB at each. alan@522: alan@522: fscale = [] alan@522: erbs = [] alan@522: alan@531: figure(1) alan@522: for i in range(n_ch): alan@522: alan@522: f = pole_freqs[i] # the frequencies from the list alan@522: ERB = ERB_Hz(f) # the ERB value at each frequency alan@522: fscale.append(f) alan@522: erbs.append(ERB) alan@522: alan@522: # plot a verticle hash at each frequency: alan@522: u = [] alan@522: v = [] alan@522: for j in range(5): alan@522: u.append(f) alan@522: v.append(10.0 + float(j)) alan@522: alan@522: plot(u,v) alan@522: alan@522: loglog(fscale,erbs) alan@522: alan@522: title('ERB scale') alan@522: alan@522: alan@522: alan@522: # This filter class includes some methods useful only in design. They will not be used in run time implementation. alan@522: # From figure 14.3 in Dick Lyon's book. alan@531: # When translating to C++, this class will become a struct, and all the methods will be moved outside. alan@522: alan@522: alan@522: #########################################################The Carfac filter class################################################################################# alan@522: alan@522: # fixed parameters alan@522: min_zeta = 0.12 alan@522: alan@522: class carfac(): alan@522: alan@522: alan@522: # instantiate the class (in C++, the constructor) alan@522: def __init__(self,f): alan@522: alan@522: self.frequency = f alan@522: alan@522: theta = 2.0 * pi * f/fs alan@522: r = 1.0 - sin(theta) * min_zeta alan@522: a = r * cos(theta) alan@522: c = r * sin(theta) alan@531: h = sin(theta) alan@531: g = (1.0 - 2.0 * a + r ** 2)/(1.0 - 2.0 * a + h * c + r ** 2) alan@531: alan@531: alan@522: alan@531: self.gh = g*h # no need to repeat in real time alan@522: alan@522: # make all parameters properties of the class alan@522: self.a = a alan@522: self.c = c alan@522: self.r = r alan@522: self.theta = theta alan@522: self.h = h alan@522: self.g = g alan@522: alan@522: alan@522: # the two storage elements. Referring to diagram 14.3 on p.263, z2 is the upper storage register, z1, the lower alan@522: self.z1 = 0.0 alan@522: self.z2 = 0.0 alan@522: alan@522: alan@522: # frequency response of this filter alan@522: self.H = [] alan@522: alan@522: alan@522: alan@522: # the total frequency magnitude of this filter including all the filters in front of this one alan@522: self.HT = [] # this list will be filled by multiplying all the H's ahead of it together with its own (H) alan@522: alan@522: alan@522: alan@522: # execute one clock tick. Take in one input and output one result. Execution semantics taken from fig. 14.3 alan@522: # 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: def input(self,X): alan@522: alan@522: # recover the class definitions of these variables. These statements below take up zero time at execution since they are just compiler declarations. alan@531: # computation below is organized as some loads, followed by 3 2x2 multiply accumulates alan@531: # Note: this function is not exercised in this file and is here only for reference alan@522: alan@522: a = self.a alan@522: c = self.c alan@522: g = self.g alan@531: gh = self.gh alan@522: z1 = self.z1 # z1 is the lower storage in fig. 14.3 alan@522: z2 = self.z2 alan@522: alan@522: # calculate what the next value of z1 will be, but don't overwrite current value yet. alan@531: 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@522: # the output Y alan@531: Y = g * X + gh * next_z1 # Note: organized as a 2 element multiply accumulate. alan@522: alan@522: #stores alan@531: self.z2 = X + (a * z2) - (c * z1) #Note: this is a 2 element multiply accumulate alan@531: self.z1 = next_z1 alan@522: alan@522: return Y # The output alan@522: alan@522: # complex frequency response of this filter at frequency w. That is, what it contributes to the cascade alan@522: # this method is used for test only. It finds the frequency magnitude. Not included in run time filter class. alan@522: def Hw(self,w): alan@522: alan@522: a = self.a alan@522: c = self.c alan@522: g = self.g alan@522: h = self.h alan@522: r = self.r alan@522: z = e ** (complex(0,w)) # w is in radians so this is z = exp(jw) alan@531: return g * (1.0 + (h*c*z)/(z**2 - 2.0*a*z + r**2 )) alan@522: alan@522: alan@522: alan@522: ######################################################End of Carfac filter class######################################################################## alan@532: alan@532: alan@522: # instantiate the filters alan@522: alan@522: # n_ch is the number of filters as determined above alan@522: alan@522: Filters = [] # the list of all filters, the zeroth is the top frequency alan@522: for i in range(n_ch): alan@522: f = pole_freqs[i] alan@522: 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: Filters.append(filter) alan@522: alan@522: alan@522: alan@522: # sweep parameters alan@522: steps = 1000 alan@522: alan@531: figure(2) alan@531: title('CarFac individual filter frequency response') alan@531: # note: the scales are linear, not logrithmic as in the book alan@522: alan@522: alan@522: for i in range(n_ch): alan@522: filter = Filters[i] alan@522: # plotting arrays alan@522: u = [] alan@522: v = [] alan@522: # calculate the frequency magnitude by stepping the frequency in radians alan@522: for j in range(steps): alan@522: alan@531: w = pi * float(j)/steps alan@522: u.append(w) alan@522: mag = filter.Hw(w) # freq mag at freq w alan@522: filter.H.append(mag) # save for later use alan@522: filter.HT.append(mag) # will be total response of cascade to this point after we do the multiplication in a step below alan@531: v.append(abs(mag)) # y plotting axis alan@522: alan@522: alan@522: plot(u,v) alan@522: alan@522: alan@522: # calculate the phase response of the same group of filters alan@522: figure(3) alan@531: title('Carfac individual filter Phase lag') alan@522: alan@522: alan@522: for i in range(n_ch): alan@522: filter = Filters[i] alan@522: alan@522: u = [] alan@522: v = [] alan@522: for j in range(steps): alan@531: x = pi * float(j)/steps alan@522: alan@522: u.append(x) alan@522: alan@522: mag = filter.H[j] alan@531: phase = arctan2(-imag(mag),-real(mag)) - pi # this formula used to avoid wrap around alan@522: alan@522: v.append(phase) # y plotting axis alan@522: alan@522: plot(u,v) alan@531: axis([0.0,pi,-3.0,0.05]) alan@522: alan@522: alan@531: # calulate and plot cascaded frequency response alan@522: alan@522: figure(4) alan@531: title('CarFac cascaded filter frequency response') alan@522: alan@522: alan@522: for i in range(n_ch-1): alan@522: alan@522: filter = Filters[i] alan@522: next = Filters[i+1] alan@522: alan@522: alan@522: u = [] alan@522: v = [] alan@522: for j in range(steps): alan@531: w = pi * float(j)/steps alan@531: u.append(w) alan@522: mag = filter.HT[j] * next.HT[j] alan@531: next.HT[j] = mag alan@531: v.append(abs(mag)) alan@522: alan@522: alan@522: plot(u,v) alan@522: alan@522: alan@522: alan@522: # calculate and plot the phase responses of the cascaded filters alan@522: alan@531: figure(5) alan@531: title('Carfac cascaded filter Phase lag') alan@522: alan@522: for i in range(n_ch): alan@531: alan@522: filter = Filters[i] alan@522: alan@522: alan@522: u = [] alan@531: v = [] # store list of phases alan@531: w = [] # second copy of phases needed for phase unwrapping alan@522: for j in range(steps): alan@531: x = pi * float(j)/steps alan@522: alan@522: u.append(x) alan@522: mag = filter.HT[j] alan@531: a = imag(mag) alan@531: b = real(mag) alan@531: phase = arctan2(a,b) alan@522: alan@531: v.append(phase) alan@531: w.append(phase) alan@522: alan@531: # unwrap the phase alan@522: alan@522: alan@531: for i in range(1,len(v)): alan@531: diff = v[i]-v[i-1] alan@531: if diff > pi: alan@531: for j in range(i,len(w)): alan@531: w[j] -= 2.0 * pi alan@531: elif diff < -pi: alan@531: for j in range(i,len(w)): alan@531: w[j] += 2.0 * pi alan@531: alan@531: else: continue alan@532: alan@532: # convert delay to cycles alan@532: for i in range(len(w)): alan@532: w[i] /= 2.0 * pi alan@532: alan@531: alan@531: plot(u,w) alan@532: axis([0.0,pi,-5.0,0.05]) alan@532: alan@532: # calculate group delay as cascaded lag/filter center frequency alan@532: alan@532: figure(6) alan@532: title('Carfac group delay') alan@532: alan@532: for i in range(n_ch): alan@532: alan@532: filter = Filters[i] alan@532: alan@532: alan@532: u = [] alan@532: v = [] # store list of phases alan@532: w = [] # second copy of phases needed for phase unwrapping alan@532: for j in range(1,steps): alan@532: x = pi * float(j)/steps alan@532: alan@532: u.append(x) alan@532: mag = filter.HT[j] alan@532: a = imag(mag) alan@532: b = real(mag) alan@532: phase = arctan2(a,b) alan@532: alan@532: v.append(phase) alan@532: w.append(phase) alan@532: alan@532: alan@532: for i in range(1,len(v)): alan@532: diff = v[i]-v[i-1] alan@532: if diff > pi: alan@532: for j in range(i,len(w)): alan@532: w[j] -= 2.0 * pi alan@532: elif diff < -pi: alan@532: for j in range(i,len(w)): alan@532: w[j] += 2.0 * pi alan@532: alan@532: else: continue alan@532: alan@532: # calculate the group delay from: GroupDelay(n)=-[((ph(n+1)-ph(n-1))/(w(n+1)-w(n-1))]/2pi # w in radians alan@532: # or gd[n] = - ((delta ph)/(delta w))/2pi alan@532: # delta ph = w[n+1] - w[n-1] alan@532: # delta w = pi* fs/steps # we divide up the frequency range fs into fractions of pi radians. alan@532: # gd[n] = - steps(w[n+1] - w[n-1])/fs alan@532: alan@532: A = -(float(steps))/(2 *fs) # note. could have calculated this once outside the filter loop alan@532: alan@532: # gd[n] = A *(w[n+1] - w[n-1]) alan@532: alan@532: gd = [] # in radians alan@532: gd.append(0.0) # pad to equalize size of u array, end points are then meaningless alan@532: for n in range(1,len(w)-1): alan@532: gd.append(A * (w[n+1] - w[n-1])) alan@532: alan@532: gd.append(0.0) alan@532: alan@532: plot(u,gd) alan@532: axis([0.1,pi,-0.01,0.05]) alan@532: alan@522: show() alan@522: