Source code for brian2hears.filtering.tan_carney

import warnings

import numpy as np
import scipy.signal as signal

from brian2 import (NeuronGroup, network_operation,
                    Hz, ms)

from brian2hears.filtering.filterbank import (FunctionFilterbank,
                                              ControlFilterbank,
                                              CombinedFilterbank,
                                              RestructureFilterbank)
from brian2hears.filtering.linearfilterbank import LinearFilterbank
from brian2hears.filtering.filterbankgroup import FilterbankGroup


__all__=['TanCarney', 'MiddleEar', 'ZhangSynapse', 'ZhangSynapseSpikes',
         'ZhangSynapseRate']

[docs]class MiddleEar(LinearFilterbank): ''' Implements the middle ear model from Tan & Carney (2003) (linear filter with two pole pairs and one double zero). The gain is normalized for the response of the analog filter at 1000Hz as in the model of Tan & Carney (their actual C code does however result in a slightly different normalization, the difference in overall level is about 0.33dB (to get exactly the same output as in their model, set the ``gain`` parameter to 0.962512703689). Tan, Q., and L. H. Carney. "A Phenomenological Model for the Responses of Auditory-nerve Fibers. II. Nonlinear Tuning with a Frequency Glide". The Journal of the Acoustical Society of America 114 (2003): 2007. ''' def __init__(self, source, gain=1, **kwds): # Automatically duplicate mono input to fit the desired output shape gain = np.atleast_1d(gain) if len(gain) != source.nchannels and len(gain) != 1: if source.nchannels != 1: raise ValueError('Can only automatically duplicate source ' 'channels for mono sources, use ' 'RestructureFilterbank.') source = RestructureFilterbank(source, len(gain)) samplerate = source.samplerate zeros = np.array([-200, -200]) poles = np.array([-250 + 400j, -250 - 400j, -2000 + 6000j, -2000 - 6000j]) # use an arbitrary gain here, will be normalized afterwards b, a = signal.zpk2tf(zeros, poles * 2 * np.pi, 1.5e9) # normalize the response at 1000Hz (of the analog filter) resp = np.abs(signal.freqs(b, a, [1000*2*np.pi])[1]) # response magnitude b /= resp bd, ad = signal.bilinear(b, a, samplerate) bd = (np.tile(bd, (source.nchannels, 1)).T * gain).T ad = np.tile(ad, (source.nchannels, 1)) LinearFilterbank.__init__(self, source, bd, ad, **kwds)
[docs]class ZhangSynapseSpikes(NeuronGroup): ''' The spike-generating Poisson process (with absolute and relative refractoriness) of an IHC-AN synapse according to the Zhang et al. (2001) model. The ``source`` has to have a state variable ``s``, representing the firing rate (e.g. the class `.ZhangSynapseRate`). The ``n_per_channel`` argument can be used to generate multiple spike trains for every channel of the source group. ''' def __init__(self, source, n_per_channel=1, params=None): params = ZhangSynapse._get_parameters(params) c_0, c_1 = params['c_0'], params['c_1'] s_0, s_1 = params['s_0'], params['s_1'] R_A = params['R_A'] ns = dict(s_0=s_0, s_1=s_1, c_0=c_0, c_1=c_1) eqs = ''' # time-varying discharge rate, input into this model s : Hz # discharge-history effect (Equation 20 in differential equation form) H = c_0*e_0 + c_1*e_1 : 1 de_0/dt = -e_0/s_0 : 1 (unless refractory) de_1/dt = -e_1/s_1 : 1 (unless refractory) # final time-varying discharge rate for the Poisson process, equation 19 R = s * (1 - H) : Hz ''' # make sure that the s value is first updated in # ZhangSynapseRate, then this NeuronGroup is # updated by setting order+1 @network_operation(dt=source.dt[:], when='start', order=source.order+1) def distribute_input(): self.s[:] = source.s[:].repeat(n_per_channel) NeuronGroup.__init__(self, len(source) * n_per_channel, model=eqs, threshold='rand()<R*dt', reset=''' e_0 = 1 e_1 = 1 ''', refractory=R_A, dt=source.dt[:], order=source.order+1, namespace=ns, method='euler', ) self.contained_objects.append(distribute_input)
[docs]class ZhangSynapse(ZhangSynapseSpikes): ''' A `FilterbankGroup` that represents an IHC-AN synapse according to the Zhang et al. (2001) model. The ``source`` should be a filterbank, producing V_ihc (e.g. `TanCarney`). ``CF`` specifies the characteristic frequencies of the AN fibers. ``params`` overwrites any parameters values given in the publication. The group emits spikes according to a time-varying Poisson process with absolute and relative refractoriness (probability of spiking is given by state variable ``R``). The continuous probability of spiking without refractoriness is available in the state variable ``s``. The ``n_per_channel`` argument can be used to generate multiple spike trains for every channel. If all you need is the state variable ``s``, you can use the class `.ZhangSynapseRate` instead which does not simulate the spike-generating Poisson process. For details see: Zhang, X., M. G. Heinz, I. C. Bruce, and L. H. Carney. "A Phenomenological Model for the Responses of Auditory-nerve Fibers: I. Nonlinear Tuning with Compression and Suppression". The Journal of the Acoustical Society of America 109 (2001): 648. ''' @staticmethod def _get_parameters(params=None): # Default values for parameters from table 1, Zhang et al. 2001 default_params = {'spont': 50*Hz, # In the example C code, this is used (with comment: "read Frank's cmpa.c") 'A_SS': 130*Hz, 'tau_ST': 60*ms, 'tau_R': 2*ms, 'A_RST': 6, 'PTS': 8.627, 'P_Imax': 0.6, 'c_0': 0.5, 'c_1': 0.5, 'R_A': 0.75*ms, 's_0': 1*ms, 's_1': 12.5*ms} if params is None: return default_params for param, value in params.items(): if not param in default_params: raise KeyError(('"%s" is not a valid parameter, ' 'has to be one of: %s') % (param, str(default_params.keys()))) default_params[param] = value return default_params def __init__(self, source, CF, n_per_channel=1, params=None): params = ZhangSynapse._get_parameters(params) rate_model = ZhangSynapseRate(source, CF, params) ZhangSynapseSpikes.__init__(self, rate_model, n_per_channel, params) self.contained_objects.append(rate_model)
[docs]class ZhangSynapseRate(FilterbankGroup): ''' A `FilterbankGroup` that represents an IHC-AN synapse according to the Zhang et al. (2001) model, see `ZhangSynapse` for details. This class does not actually generate any spikes, it only simulates the time-varying firing rate (not taking refractory effects into account) ``s``. ''' def __init__(self, source, CF, params=None): params = ZhangSynapse._get_parameters(params) spont = params['spont'] A_SS = params['A_SS'] tau_ST = params['tau_ST'] tau_R = params['tau_R'] A_RST = params['A_RST'] PTS = params['PTS'] P_Imax = params['P_Imax'] c_0 = params['c_0'] c_1 = params['c_1'] R_A = params['R_A'] s_0 = params['s_0'] s_1 = params['s_1'] # Equations A1-A5 of Zhang et al. 2001 A_ON = PTS * A_SS # onset rate A_R = (A_ON - A_SS) * A_RST / (1 + A_RST) # rapid response amplitude A_ST = A_ON - A_SS - A_R # short-term response amplitude P_rest = P_Imax * spont / A_ON # resting permeability C_G = spont * (A_ON - spont) / (A_ON*P_rest*(1 - spont/A_SS)) # global concentration # Equations A6 (intermediate parameters for store volume computation) gamma_1 = C_G / spont gamma_2 = C_G / A_SS kappa_1 = -1 / tau_R kappa_2 = -1 / tau_ST # Equations A7-A9 (immediate volume) V_I0 = (1 - P_Imax/P_rest)/(gamma_1*((A_R*(kappa_1-kappa_2)/(C_G*P_Imax)) + kappa_2/(P_rest*gamma_1) - kappa_2/(P_Imax*gamma_2))) V_I1 = (1 - P_Imax/P_rest)/(gamma_1*((A_ST*(kappa_2-kappa_1)/(C_G*P_Imax)) + kappa_1/(P_rest*gamma_1) - kappa_1/(P_Imax*gamma_2))) V_I = 0.5 * (V_I0 + V_I1) # Equations A10 (other intermediate parameters) alpha = gamma_2 / (kappa_1*kappa_2) beta = -(kappa_1 + kappa_2) * alpha theta_1 = alpha * P_Imax/V_I theta_2 = V_I/P_Imax theta_3 = gamma_2 - 1/P_Imax # Equations A11-A12 (local and global permeabilities) P_L = ((beta - theta_2*theta_3)/theta_1 - 1) * P_Imax P_G = 1 / (theta_3 - 1/P_L) # Equations A13-A15 V_L = theta_1*P_L*P_G # local volume C_Irest = spont/P_rest # resting value of immediate concentration C_Lrest = C_Irest*(P_rest + P_L)/P_L # local concentration # Equation 18 with A16 and A17 p_1 = P_rest / np.log(2) # Equation A17 (using an expression based on the spontaneous rate instead of 18.54, based on the C code) V_sat2 = 2 + 3*np.log10(np.asarray(CF)/1000.0) V_sat = 20.0*(spont + 1*Hz)/(spont + 5*Hz)*P_Imax*((V_sat2 > 1.5)*(V_sat2 - 1.5) + 1.5) # Following Equation A16 (p_2 is the same as P_ST) p_2_exponent = abs(np.log(2)*V_sat/P_rest) temp1 = np.zeros_like(p_2_exponent) temp1[p_2_exponent>=100] = p_2_exponent[p_2_exponent>=100] temp1[p_2_exponent<100] = np.log(np.exp(p_2_exponent[p_2_exponent<100])-1) p_2 = np.clip(temp1, -np.inf, abs(p_2_exponent)) ns = dict( spont=spont, P_Imax=P_Imax, p_1=p_1, P_rest=P_rest, P_L=P_L, V_I=V_I, P_G=P_G, C_G=C_G, ) eqs = ''' # input into the Synapse V_ihc : 1 # CF in Hz CF_param : 1 # Equation 17 with some corrections to fix overflow problems base_P_I_exponent = p_2 * V_ihc : 1 clip_base_P_I_exponent = clip(base_P_I_exponent, -1e100, 100) : 1 P_I_exponent = base_P_I_exponent*(base_P_I_exponent>=100)+log(1+exp(clip_base_P_I_exponent))*(base_P_I_exponent<100) : 1 P_I = p_1 * P_I_exponent : 1 # Following Equation A16 (p_2 is the same as P_ST) p_2 : 1 # Equation A18-A19 # Concentration in the stores (as differential instead of difference equation) dC_I/dt = (-P_I*C_I + P_L*(C_L - C_I))/V_I : Hz dC_L/dt = (-P_L*(C_L - C_I) + P_G*(C_G - C_L))/V_I : Hz # time-varying discharge rate (ignoring refractory effects), equation A20 s = C_I * P_I : Hz ''' FilterbankGroup.__init__(self, source, 'V_ihc', eqs, namespace=ns, method='euler') self.CF_param = np.asarray(CF) self.p_2 = p_2 self.C_I = C_Irest self.C_L = C_Lrest
def set_parameters(cf,param): parameters=dict() parameters['fc_LP_control']=800 #Hz parameters['fc_LP_fb']=500 #Hz parameters['fp1']=1.0854*cf-106.0034 parameters['ta']=10**(np.log10(cf)*1.0230 + 0.1607) parameters['tb']=10**(np.log10(cf)*1.4292 - 1.1550) - 1000 parameters['gain80']=10**(np.log10(cf)*0.5732 + 1.5220) parameters['rgain']=10**( np.log10(cf)*0.4 + 1.9) parameters['average_control']=0.3357 parameters['zero_r']= np.array(-10**( np.log10(cf)*1.5-0.9 )) if param: if not isinstance(param, dict): raise TypeError('given parameters must be a dict') for key in param.keys(): if key != 'nlgain' and not key in parameters: raise KeyError(key + ' is invalid key entry for given parameters') parameters[key] = param[key] parameters['nlgain']= (parameters['gain80'] - parameters['rgain'])/parameters['average_control'] return parameters class Control_Coefficients: def __init__(self,cf,samplerate): self.cf = cf self.PI2 = 2.*3.14159265358979 self.nch=len(cf) self.fs_bilinear = float(2.0*samplerate)#*ones(self.nch) # self.fs_bilinear =tile(self.fs_bilinear.reshape(self.nch,-1),3) self.x_cf=11.9*np.log10(0.8+cf/456); self.f_shift=(pow(10,((self.x_cf+1.2)/11.9))-0.8)*456-cf self.wbw=cf/4.0 self.filt_a = np.zeros((len(cf),3,5), order='F') #8 5 # self.filt_a[:,0,:] = 1 self.filt_b = np.zeros((len(cf),3,5), order='F') self.control_signal = 0 self.preal = np.zeros((self.nch,6)) self.pimg = np.zeros((self.nch,6)) self.preal,self.pimg = self.analog_poles() def return_coefficients(self,control_signal): self.wbw=-(self.preal[:,0] - control_signal)/self.PI2 self.gain_norm_bp=((self.PI2**2 * np.sqrt(self.wbw**2 + self.f_shift**2) * np.sqrt((2*self.cf+self.f_shift)**2 + self.wbw**2) )**3)/np.sqrt(self.PI2**2*self.cf**2)# iord = [1,3,5] preal = self.preal[:,iord]-control_signal.T #actually control_signal is the same for the three channels temp=(self.fs_bilinear-(preal))**2 + self.pimg[:,iord]**2 self.filt_a[:,0,0:3] = 1. self.filt_a[:,1,0:3]= -2*(self.fs_bilinear**2-(preal)**2-self.pimg[:,iord]**2)/temp self.filt_a[:,2,0:3] = ((self.fs_bilinear+(preal))**2+self.pimg[:,iord]**2)/temp self.filt_b[:,0,0:3] = 1./temp self.filt_b[:,1,0:3] = 2./temp self.filt_b[:,2,0:3] = 1./temp self.filt_a[:,0,3] = 1. self.filt_a[:,1,3]= 1. ## changed from 1 to 0 self.filt_a[:,2,3] = 0. self.filt_b[:,0,3] = self.fs_bilinear self.filt_b[:,1,3] = -self.fs_bilinear self.filt_b[:,2,3] = 0 # self.filt_b[:,:,3] = self.gain_norm_bp*self.filt_b[:,:,3] self.filt_a[:,0,4] = 1. self.filt_b[:,0,4] = self.gain_norm_bp return self.filt_b,self.filt_a def analog_poles(self): self.preal[:,0] = -self.PI2*self.wbw #that should be -, actually there are never used self.preal[:,1] = -self.PI2*self.wbw self.preal[:,2] = self.preal[:,0] self.preal[:,3] = self.preal[:,1] self.preal[:,4] = self.preal[:,0] self.preal[:,5] = self.preal[:,1] self.pimg[:,0] = self.PI2*(self.cf+self.f_shift) self.pimg[:,1] = -self.PI2*(self.cf+self.f_shift) self.pimg[:,2] = self.pimg[:,0] self.pimg[:,3] = self.pimg[:,1] self.pimg[:,4] = self.pimg[:,0] self.pimg[:,5] = self.pimg[:,1] return self.preal,self.pimg class Signal_Coefficients: def __init__(self,cf,samplerate,parameters): self.t = 0 self.cf = cf self.PI2 = 2*3.14159265358979 self.nch=len(cf) self.fs_bilinear = float(2.0*samplerate)#*ones(self.nch) self.order_of_pole = 20 self.half_order_pole = self.order_of_pole/2 self.order_of_zero = self.half_order_pole self.filt_a = np.zeros((len(cf),3,11), order='F') self.filt_b = np.zeros((len(cf),3,11), order='F') self.preal = np.zeros((self.nch,20)) self.pimg = np.zeros((self.nch,20)) self.control_signal = 0 self.rgain =parameters['rgain']# 10**(log10(cf)*0.4 + 1.9) self.fp1=parameters['fp1']#1.0854*cf-106.0034 self.ta= parameters['ta']# 10**(log10(cf)*1.0230 + 0.1607) self.tb= parameters['tb']# 10**(log10(cf)*1.4292 - 1.1550) - 1000 self.zeroa = parameters['zero_r']# -10**(log10(cf)*1.5-0.9 ) self.zeroamat = np.tile(self.zeroa.reshape(self.nch,-1),10) self.preal,self.pimg = self.analog_poles(0) self.cfmat = np.tile(self.cf.reshape(self.nch,-1),20) self.gain_norm = np.sqrt(np.prod((2*np.pi*self.cfmat-self.pimg[:,0:20])**2+self.preal[:,0:20]**2,axis=1)) self.gain_norm = self.gain_norm /(np.sqrt((2*np.pi*self.cf)**2+self.zeroa**2))**self.order_of_zero def return_coefficients(self,control_signal): self.preal,self.pimg = self.analog_poles(control_signal) iord = np.arange(2,22,2)-1 temp=(self.fs_bilinear-self.preal[:,iord])**2 + self.pimg[:,iord]**2 self.filt_a[:,0,:10] = 1 self.filt_a[:,1,:10] = -2*(self.fs_bilinear**2-self.preal[:,iord]**2-self.pimg[:,iord]**2)/temp self.filt_a[:,2,:10] = ((self.fs_bilinear+self.preal[:,iord])**2+self.pimg[:,iord]**2)/temp self.filt_b[:,0,:10] = (-self.zeroamat+self.fs_bilinear)/temp self.filt_b[:,1,:10] = (-2*self.zeroamat)/temp self.filt_b[:,2,:10] = (-self.zeroamat-self.fs_bilinear)/temp self.filt_a[:,0,10] = 1. self.filt_b[:,0,10] = self.gain_norm/3. return self.filt_b,self.filt_a def analog_poles(self,control_signal): aa = -self.rgain-control_signal aa[aa>=0] = 100 self.preal[:,0] = -self.rgain-control_signal self.preal[:,4] = self.preal[:,0]-self.ta self.preal[:,2] = (self.preal[:,0]+self.preal[:,4])*0.5 self.preal[:,1] = self.preal[:,0] self.preal[:,3] = self.preal[:,2] self.preal[:,5] = self.preal[:,4] self.preal[:,6] = self.preal[:,0] self.preal[:,7] = self.preal[:,1] self.preal[:,8] = self.preal[:,4] self.preal[:,9] = self.preal[:,5] self.preal[:,10:] = self.preal[:,:10] self.pimg[:,0] = self.PI2*self.fp1 self.pimg[:,4] = self.pimg[:,0]-self.tb self.pimg[:,2] = (self.pimg[:,0]+self.pimg[:,4])*0.5 self.pimg[:,1] = -self.pimg[:,0] self.pimg[:,3] = -self.pimg[:,2] self.pimg[:,5] = -self.pimg[:,4] self.pimg[:,6] = self.pimg[:,0] self.pimg[:,7] = self.pimg[:,1] self.pimg[:,8] = self.pimg[:,4] self.pimg[:,9] = self.pimg[:,5] self.pimg[:,10:] = self.pimg[:,:10] return self.preal,self.pimg class Filter_Update: def __init__(self, target,coef): self.coef = coef self.target = target self.param = [] def __call__(self, input): reshaped_input = input[-1,:].reshape(1,-1) self.target.filt_b,self.target.filt_a = self.coef.return_coefficients(reshaped_input) self.param.append(self.coef.control_signal) class LowPass_IHC(LinearFilterbank): def __init__(self,source,cf,fc,gain,order): nch = len(cf) TWOPI = 2*np.pi self.samplerate = source.samplerate c = 2.0 * self.samplerate c1LP = ( c/Hz - TWOPI*fc ) / ( c/Hz + TWOPI*fc ) c2LP = TWOPI*fc/Hz / (TWOPI*fc + c/Hz) b_temp = np.array([c2LP,c2LP]) a_temp = np.array([1,-c1LP]) filt_b = np.tile(b_temp.reshape([2,1]),[nch,1,order]) filt_a = np.tile(a_temp.reshape([2,1]),[nch,1,order]) filt_b[:,:,0] = filt_b[:,:,0]*gain LinearFilterbank.__init__(self, source, filt_b, filt_a) class LowPass_filter(LinearFilterbank): def __init__(self,source,cf,fc,gain,order): nch = len(cf) TWOPI = 2*np.pi self.samplerate = source.samplerate c = 2.0 * self.samplerate c1LP = ( c/Hz - TWOPI*fc ) b_temp = np.array([1,1])/ ( c/Hz + TWOPI*fc ) a_temp = np.array([1,-c1LP/ ( c/Hz + TWOPI*fc )]) filt_b = np.tile(b_temp.reshape([2,1]),[nch,1,order]) filt_a = np.tile(a_temp.reshape([2,1]),[nch,1,order]) filt_b[:,:,order-1] = filt_b[:,:,order-1]*gain LinearFilterbank.__init__(self, source, filt_b, filt_a) def saturation_fc(x,A0=1,B=1,C=1,D=1): ind = x>=0 x[ind]=A0*np.log(x[ind]*B+1.0) ind = x<0 dtemp = (-x[ind])**C tempA = -A0*(dtemp+D)/(3*dtemp+D) x[ind]=tempA*np.log(abs(x[ind])*B+1.0) return x class TanCarneyIHC(CombinedFilterbank): def __init__(self, source, cf): CombinedFilterbank.__init__(self, source) source = self.get_modified_source() ## Saturation saturation = FunctionFilterbank(source, saturation_fc, A0=0.1, B=2000, C=1.74, D=6.87e-9) ## low pass IHC ihc = LowPass_IHC(saturation, cf, 3800, 1, 7) self.set_output(ihc) class TanCarneyControl(CombinedFilterbank): def __init__(self, source, cf, update_interval, param=None): CombinedFilterbank.__init__(self, source) source = self.get_modified_source() cf = np.atleast_1d(cf) samplerate=source.samplerate parameters = set_parameters(cf, param) ##### Control Path #### # band pass filter control_coef = Control_Coefficients(cf, samplerate) [filt_b,filt_a] = control_coef.return_coefficients(np.zeros((1,len(cf)))) BP_control = LinearFilterbank(source,filt_b,filt_a) # first non linearity of control path Acp,Bcp,Ccp=100.,2.5,0.60 func_NL1_control=lambda x:np.sign(x)*Bcp*np.log(1.+Acp*abs(x)**Ccp) NL1_control=FunctionFilterbank(BP_control,func_NL1_control) # second non linearity of control path asym,s0,x1,s1=7.,8.,5.,3. shift = 1./(1.+asym) x0 = s0*np.log((1.0/shift-1)/(1+np.exp(x1/s1))) func_NL2_control=lambda x:(1.0/(1.0+np.exp(-(x-x0)/s0)*(1.0+np.exp(-(x-x1)/s1)))-shift)*parameters['nlgain'] NL2_control=FunctionFilterbank(NL1_control,func_NL2_control) #control low pass filter (its output will be used to control the signal path) gain_lp_con = (2*np.pi*parameters['fc_LP_control'])**3*1.5 LP_control = LowPass_filter(NL2_control,cf,parameters['fc_LP_control'],gain_lp_con,3) #low pass filter for feedback to control band pass (its output will be used to control the control path) gain_lp_fb = parameters['fc_LP_fb']*2*np.pi*10 LP_feed_back = LowPass_filter(LP_control,cf,parameters['fc_LP_fb'],gain_lp_fb,1) updater = Filter_Update(BP_control, control_coef) #instantiation of the updater for the control path output = ControlFilterbank(LP_control, LP_feed_back, BP_control, updater, update_interval) #controler for the band pass filter of the control path self.set_output(output) class TanCarneySignal(CombinedFilterbank): def __init__(self, source, cf, update_interval, param=None): CombinedFilterbank.__init__(self, source) source = self.get_modified_source() cf = np.atleast_1d(cf) parameters = set_parameters(cf, param) samplerate=source.samplerate if int(source.samplerate)!=50000: warnings.warn('To use the TanCarney cochlear model the sample rate should be 50kHz') # band pass filter signal_coef = Signal_Coefficients(cf, samplerate,parameters) [filt_b,filt_a] = signal_coef.return_coefficients(np.zeros((1,len(cf)))) BP_signal = LinearFilterbank(source,filt_b,filt_a) control_output = TanCarneyControl(source, cf, update_interval, parameters) updater = Filter_Update(BP_signal, signal_coef) #instantiation of the updater for the signal path output = ControlFilterbank(BP_signal, control_output, BP_signal, updater, update_interval) #controler for the band pass filter of the signal path self.set_output(output)
[docs]class TanCarney(CombinedFilterbank): ''' Class implementing the nonlinear auditory filterbank model as described in Tan, G. and Carney, L., "A phenomenological model for the responses of auditory-nerve fibers. II. Nonlinear tuning with a frequency glide", JASA 2003. The model consists of a control path and a signal path. The control path controls both its own bandwidth via a feedback loop and also the bandwidth of the signal path. Initialised with arguments: ``source`` Source of the cochlear model. ``cf`` List or array of center frequencies. ``update_interval`` Interval in samples controlling how often the band pass filter of the signal pathway is updated. Smaller values are more accurate but increase the computation time. ``param`` Dictionary used to overwrite the default parameters given in the original paper. ''' def __init__(self, source, cf, update_interval=1, param=None): CombinedFilterbank.__init__(self, source) source = self.get_modified_source() cf = np.asarray(np.atleast_1d(cf)) parameters=set_parameters(cf,param) signal = TanCarneySignal(source, cf, update_interval, parameters) ihc = TanCarneyIHC(signal, cf) self.set_output(ihc)