alexbrandmeyer@609
|
1 //
|
alexbrandmeyer@609
|
2 // ear.cc
|
alexbrandmeyer@609
|
3 // CARFAC Open Source C++ Library
|
alexbrandmeyer@609
|
4 //
|
alexbrandmeyer@609
|
5 // Created by Alex Brandmeyer on 5/10/13.
|
alexbrandmeyer@609
|
6 //
|
alexbrandmeyer@609
|
7 // This C++ file is part of an implementation of Lyon's cochlear model:
|
alexbrandmeyer@609
|
8 // "Cascade of Asymmetric Resonators with Fast-Acting Compression"
|
alexbrandmeyer@609
|
9 // to supplement Lyon's upcoming book "Human and Machine Hearing"
|
alexbrandmeyer@609
|
10 //
|
alexbrandmeyer@609
|
11 // Licensed under the Apache License, Version 2.0 (the "License");
|
alexbrandmeyer@609
|
12 // you may not use this file except in compliance with the License.
|
alexbrandmeyer@609
|
13 // You may obtain a copy of the License at
|
alexbrandmeyer@609
|
14 //
|
alexbrandmeyer@609
|
15 // http://www.apache.org/licenses/LICENSE-2.0
|
alexbrandmeyer@609
|
16 //
|
alexbrandmeyer@609
|
17 // Unless required by applicable law or agreed to in writing, software
|
alexbrandmeyer@609
|
18 // distributed under the License is distributed on an "AS IS" BASIS,
|
alexbrandmeyer@609
|
19 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
alexbrandmeyer@609
|
20 // See the License for the specific language governing permissions and
|
alexbrandmeyer@609
|
21 // limitations under the License.
|
alexbrandmeyer@609
|
22
|
alexbrandmeyer@609
|
23 #include "ear.h"
|
alexbrandmeyer@609
|
24
|
alexbrandmeyer@609
|
25 void Ear::InitEar(long fs, CARParams car_p, IHCParams ihc_p, AGCParams agc_p){
|
alexbrandmeyer@609
|
26 car_params_ = car_p;
|
alexbrandmeyer@609
|
27 ihc_params_ = ihc_p;
|
alexbrandmeyer@609
|
28 agc_params_ = agc_p;
|
alexbrandmeyer@609
|
29 n_ch_ = 0;
|
alexbrandmeyer@609
|
30 FPType pole_hz = car_params_.first_pole_theta_ * fs / (2 * PI);
|
alexbrandmeyer@609
|
31 while (pole_hz > car_params_.min_pole_hz_) {
|
alexbrandmeyer@609
|
32 n_ch_++;
|
alexbrandmeyer@609
|
33 pole_hz = pole_hz - car_params_.erb_per_step_ *
|
alexbrandmeyer@609
|
34 ERBHz(pole_hz, car_params_.erb_break_freq_, car_params_.erb_q_);
|
alexbrandmeyer@609
|
35 }
|
alexbrandmeyer@609
|
36 FloatArray pole_freqs(n_ch_);
|
alexbrandmeyer@609
|
37 pole_hz = car_params_.first_pole_theta_ * fs / (2 * PI);
|
alexbrandmeyer@609
|
38 for(int ch=0;ch < n_ch_; ch++){
|
alexbrandmeyer@609
|
39 pole_freqs(ch) = pole_hz;
|
alexbrandmeyer@609
|
40 pole_hz = pole_hz - car_params_.erb_per_step_ *
|
alexbrandmeyer@609
|
41 ERBHz(pole_hz, car_params_.erb_break_freq_, car_params_.erb_q_);
|
alexbrandmeyer@609
|
42 }
|
alexbrandmeyer@609
|
43 max_channels_per_octave_ = log(2) / log(pole_freqs(0) / pole_freqs(1));
|
alexbrandmeyer@609
|
44 car_coeffs_.DesignFilters(car_params_, fs, &pole_freqs);
|
alexbrandmeyer@609
|
45 agc_coeffs_.DesignAGC(agc_params_, fs, n_ch_);
|
alexbrandmeyer@609
|
46 ihc_coeffs_.DesignIHC(ihc_params_, fs, n_ch_);
|
alexbrandmeyer@609
|
47 car_state_.InitCARState(car_coeffs_);
|
alexbrandmeyer@609
|
48 agc_state_.InitAGCState(agc_coeffs_);
|
alexbrandmeyer@609
|
49 ihc_state_.InitIHCState(ihc_coeffs_);
|
alexbrandmeyer@609
|
50
|
alexbrandmeyer@609
|
51 }
|
alexbrandmeyer@609
|
52
|
alexbrandmeyer@609
|
53 FloatArray Ear::CARStep(FPType input){
|
alexbrandmeyer@609
|
54 FloatArray g(n_ch_);
|
alexbrandmeyer@609
|
55 FloatArray zb(n_ch_);
|
alexbrandmeyer@609
|
56 FloatArray za(n_ch_);
|
alexbrandmeyer@609
|
57 FloatArray v(n_ch_);
|
alexbrandmeyer@609
|
58 FloatArray nlf(n_ch_);
|
alexbrandmeyer@609
|
59 FloatArray r(n_ch_);
|
alexbrandmeyer@609
|
60 FloatArray z1(n_ch_);
|
alexbrandmeyer@609
|
61 FloatArray z2(n_ch_);
|
alexbrandmeyer@609
|
62 FloatArray zy(n_ch_);
|
alexbrandmeyer@609
|
63 FPType in_out;
|
alexbrandmeyer@609
|
64
|
alexbrandmeyer@609
|
65 // do the DOHC stuff:
|
alexbrandmeyer@609
|
66 g = car_state_.g_memory_ + car_state_.dg_memory_; //interp g
|
alexbrandmeyer@609
|
67 zb = car_state_.zb_memory_ + car_state_.dzb_memory_; //AGC interpolation state
|
alexbrandmeyer@609
|
68 // update the nonlinear function of "velocity", and zA (delay of z2):
|
alexbrandmeyer@609
|
69 za = car_state_.za_memory_;
|
alexbrandmeyer@609
|
70 v = car_state_.z2_memory_ - za;
|
alexbrandmeyer@609
|
71 nlf = OHC_NLF(v);
|
alexbrandmeyer@609
|
72 r = car_coeffs_.r1_coeffs_ + (zb * nlf); // zB * nfl is "undamping" delta r
|
alexbrandmeyer@609
|
73 za = car_state_.z2_memory_;
|
alexbrandmeyer@609
|
74 // now reduce state by r and rotate with the fixed cos/sin coeffs:
|
alexbrandmeyer@609
|
75 z1 = r * ((car_coeffs_.a0_coeffs_ * car_state_.z1_memory_) -
|
alexbrandmeyer@609
|
76 (car_coeffs_.c0_coeffs_ * car_state_.z2_memory_));
|
alexbrandmeyer@609
|
77 z2 = r * ((car_coeffs_.c0_coeffs_ * car_state_.z1_memory_) +
|
alexbrandmeyer@609
|
78 (car_coeffs_.a0_coeffs_ * car_state_.z2_memory_));
|
alexbrandmeyer@609
|
79 zy = car_coeffs_.h_coeffs_ * z2;
|
alexbrandmeyer@609
|
80 // Ripple input-output path, instead of parallel, to avoid delay...
|
alexbrandmeyer@609
|
81 // this is the only part that doesn't get computed "in parallel":
|
alexbrandmeyer@609
|
82 in_out = input;
|
alexbrandmeyer@609
|
83 for (int ch = 0; ch < n_ch_; ch++){
|
alexbrandmeyer@609
|
84 z1(ch) = z1(ch) + in_out;
|
alexbrandmeyer@609
|
85 // ripple, saving final channel outputs in zY
|
alexbrandmeyer@609
|
86 in_out = g(ch) * (in_out + zy(ch));
|
alexbrandmeyer@609
|
87 zy(ch) = in_out;
|
alexbrandmeyer@609
|
88 }
|
alexbrandmeyer@609
|
89 car_state_.z1_memory_ = z1;
|
alexbrandmeyer@609
|
90 car_state_.z2_memory_ = z2;
|
alexbrandmeyer@609
|
91 car_state_.za_memory_ = za;
|
alexbrandmeyer@609
|
92 car_state_.zb_memory_ = zb;
|
alexbrandmeyer@609
|
93 car_state_.zy_memory_ = zy;
|
alexbrandmeyer@609
|
94 car_state_.g_memory_ = g;
|
alexbrandmeyer@609
|
95 // car_out is equal to zy state;
|
alexbrandmeyer@609
|
96 return zy;
|
alexbrandmeyer@609
|
97 }
|
alexbrandmeyer@609
|
98
|
alexbrandmeyer@609
|
99 // start with a quadratic nonlinear function, and limit it via a
|
alexbrandmeyer@609
|
100 // rational function; make the result go to zero at high
|
alexbrandmeyer@609
|
101 // absolute velocities, so it will do nothing there.
|
alexbrandmeyer@609
|
102 FloatArray Ear::OHC_NLF(FloatArray velocities){
|
alexbrandmeyer@609
|
103 FloatArray nlf(n_ch_);
|
alexbrandmeyer@609
|
104 nlf = 1 / ((velocities * car_coeffs_.velocity_scale_) +
|
alexbrandmeyer@609
|
105 (car_coeffs_.v_offset_ * car_coeffs_.v_offset_));
|
alexbrandmeyer@609
|
106 return nlf;
|
alexbrandmeyer@609
|
107 }
|
alexbrandmeyer@609
|
108
|
alexbrandmeyer@609
|
109 // One sample-time update of inner-hair-cell (IHC) model, including the
|
alexbrandmeyer@609
|
110 // detection nonlinearity and one or two capacitor state variables.
|
alexbrandmeyer@609
|
111 FloatArray Ear::IHCStep(FloatArray car_out){
|
alexbrandmeyer@609
|
112 FloatArray ihc_out(n_ch_);
|
alexbrandmeyer@609
|
113 FloatArray ac_diff(n_ch_);
|
alexbrandmeyer@609
|
114 FloatArray conductance(n_ch_);
|
alexbrandmeyer@609
|
115 ac_diff = car_out - ihc_state_.ac_coupler_;
|
alexbrandmeyer@609
|
116 ihc_state_.ac_coupler_ = ihc_state_.ac_coupler_ +
|
alexbrandmeyer@609
|
117 (ihc_coeffs_.ac_coeff_ * ac_diff);
|
alexbrandmeyer@609
|
118 if (ihc_coeffs_.just_hwr_) {
|
alexbrandmeyer@609
|
119 //TODO Figure out best implementation with Eigen max/min methods
|
alexbrandmeyer@609
|
120 for (int ch = 0; ch < n_ch_; ch++){
|
alexbrandmeyer@609
|
121 FPType a;
|
alexbrandmeyer@609
|
122 if (ac_diff(ch) > 0){
|
alexbrandmeyer@609
|
123 a = ac_diff(ch);
|
alexbrandmeyer@609
|
124 } else {
|
alexbrandmeyer@609
|
125 a = 0;
|
alexbrandmeyer@609
|
126 }
|
alexbrandmeyer@609
|
127 if (a < 2){
|
alexbrandmeyer@609
|
128 ihc_out(ch) = a;
|
alexbrandmeyer@609
|
129 } else {
|
alexbrandmeyer@609
|
130 ihc_out(ch) = 2;
|
alexbrandmeyer@609
|
131 }
|
alexbrandmeyer@609
|
132 }
|
alexbrandmeyer@609
|
133
|
alexbrandmeyer@609
|
134 } else {
|
alexbrandmeyer@609
|
135 conductance = CARFACDetect(ac_diff);
|
alexbrandmeyer@609
|
136 if (ihc_coeffs_.one_cap_) {
|
alexbrandmeyer@609
|
137 ihc_out = conductance * ihc_state_.cap1_voltage_;
|
alexbrandmeyer@609
|
138 ihc_state_.cap1_voltage_ = ihc_state_.cap1_voltage_ -
|
alexbrandmeyer@609
|
139 (ihc_out * ihc_coeffs_.out1_rate_) +
|
alexbrandmeyer@609
|
140 ((1 - ihc_state_.cap1_voltage_)
|
alexbrandmeyer@609
|
141 * ihc_coeffs_.in1_rate_);
|
alexbrandmeyer@609
|
142 } else {
|
alexbrandmeyer@609
|
143 ihc_out = conductance * ihc_state_.cap2_voltage_;
|
alexbrandmeyer@609
|
144 ihc_state_.cap1_voltage_ = ihc_state_.cap1_voltage_ -
|
alexbrandmeyer@609
|
145 ((ihc_state_.cap1_voltage_ - ihc_state_.cap2_voltage_)
|
alexbrandmeyer@609
|
146 * ihc_coeffs_.out1_rate_) +
|
alexbrandmeyer@609
|
147 ((1 - ihc_state_.cap1_voltage_) * ihc_coeffs_.in1_rate_);
|
alexbrandmeyer@609
|
148 ihc_state_.cap2_voltage_ = ihc_state_.cap2_voltage_ -
|
alexbrandmeyer@609
|
149 (ihc_out * ihc_coeffs_.out2_rate_) +
|
alexbrandmeyer@609
|
150 ((ihc_state_.cap1_voltage_ - ihc_state_.cap2_voltage_)
|
alexbrandmeyer@609
|
151 * ihc_coeffs_.in2_rate_);
|
alexbrandmeyer@609
|
152 }
|
alexbrandmeyer@609
|
153 // smooth it twice with LPF:
|
alexbrandmeyer@609
|
154 ihc_out = ihc_out * ihc_coeffs_.output_gain_;
|
alexbrandmeyer@609
|
155 ihc_state_.lpf1_state_ = ihc_state_.lpf1_state_ +
|
alexbrandmeyer@609
|
156 (ihc_coeffs_.lpf_coeff_ * (ihc_out - ihc_state_.lpf1_state_));
|
alexbrandmeyer@609
|
157 ihc_state_.lpf2_state_ = ihc_state_.lpf2_state_ +
|
alexbrandmeyer@609
|
158 (ihc_coeffs_.lpf_coeff_ *
|
alexbrandmeyer@609
|
159 (ihc_state_.lpf1_state_ - ihc_state_.lpf2_state_));
|
alexbrandmeyer@609
|
160 ihc_out = ihc_state_.lpf2_state_ - ihc_coeffs_.rest_output_;
|
alexbrandmeyer@609
|
161 }
|
alexbrandmeyer@609
|
162 ihc_state_.ihc_accum_ += ihc_out;
|
alexbrandmeyer@609
|
163 return ihc_out;
|
alexbrandmeyer@609
|
164 }
|
alexbrandmeyer@609
|
165
|
alexbrandmeyer@609
|
166 bool Ear::AGCStep(FloatArray ihc_out){
|
alexbrandmeyer@609
|
167 int stage = 0;
|
alexbrandmeyer@609
|
168 FloatArray agc_in(n_ch_);
|
alexbrandmeyer@609
|
169 agc_in = agc_coeffs_.detect_scale_ * ihc_out;
|
alexbrandmeyer@609
|
170 bool updated = AGCRecurse(stage, agc_in);
|
alexbrandmeyer@609
|
171 return updated;
|
alexbrandmeyer@609
|
172 }
|
alexbrandmeyer@609
|
173
|
alexbrandmeyer@609
|
174 bool Ear::AGCRecurse(int stage, FloatArray agc_in){
|
alexbrandmeyer@609
|
175 bool updated = true;
|
alexbrandmeyer@609
|
176 // decim factor for this stage, relative to input or prev. stage:
|
alexbrandmeyer@609
|
177 int decim = agc_coeffs_.decimation_(stage);
|
alexbrandmeyer@609
|
178 // decim phase of this stage (do work on phase 0 only):
|
alexbrandmeyer@609
|
179 //TODO FIX MODULO
|
alexbrandmeyer@609
|
180
|
alexbrandmeyer@609
|
181 int decim_phase = agc_state_.decim_phase_(stage);
|
alexbrandmeyer@609
|
182 decim_phase = decim_phase % decim;
|
alexbrandmeyer@609
|
183 agc_state_.decim_phase_(stage) = decim_phase;
|
alexbrandmeyer@609
|
184 // accumulate input for this stage from detect or previous stage:
|
alexbrandmeyer@609
|
185 agc_state_.input_accum_.block(0,stage,n_ch_,1) =
|
alexbrandmeyer@609
|
186 agc_state_.input_accum_.block(0,stage,n_ch_,1) + agc_in;
|
alexbrandmeyer@609
|
187
|
alexbrandmeyer@609
|
188 // nothing else to do if it's not the right decim_phase
|
alexbrandmeyer@609
|
189 if (decim_phase == 0){
|
alexbrandmeyer@609
|
190 // do lots of work, at decimated rate.
|
alexbrandmeyer@609
|
191 // decimated inputs for this stage, and to be decimated more for next:
|
alexbrandmeyer@609
|
192 agc_in = agc_state_.input_accum_.block(0,stage,n_ch_,1) / decim;
|
alexbrandmeyer@609
|
193 // reset accumulator:
|
alexbrandmeyer@609
|
194 agc_state_.input_accum_.block(0,stage,n_ch_,1) = FloatArray::Zero(n_ch_);
|
alexbrandmeyer@609
|
195
|
alexbrandmeyer@609
|
196 if (stage < (agc_coeffs_.decimation_.size() - 1)){
|
alexbrandmeyer@609
|
197 // recurse to evaluate next stage(s)
|
alexbrandmeyer@609
|
198 updated = AGCRecurse(stage+1, agc_in);
|
alexbrandmeyer@609
|
199 // and add its output to this stage input, whether it updated or not:
|
alexbrandmeyer@609
|
200 agc_in = agc_in + (agc_coeffs_.agc_stage_gain_ *
|
alexbrandmeyer@609
|
201 agc_state_.agc_memory_.block(0,stage+1,n_ch_,1));
|
alexbrandmeyer@609
|
202 }
|
alexbrandmeyer@609
|
203 FloatArray agc_stage_state = agc_state_.agc_memory_.block(0,stage,n_ch_,1);
|
alexbrandmeyer@609
|
204 // first-order recursive smoothing filter update, in time:
|
alexbrandmeyer@609
|
205 agc_stage_state = agc_stage_state + (agc_coeffs_.agc_epsilon_(stage) *
|
alexbrandmeyer@609
|
206 (agc_in - agc_stage_state));
|
alexbrandmeyer@609
|
207 agc_stage_state = AGCSpatialSmooth(stage, agc_stage_state);
|
alexbrandmeyer@609
|
208 agc_state_.agc_memory_.block(0,stage,n_ch_,1) = agc_stage_state;
|
alexbrandmeyer@609
|
209 } else {
|
alexbrandmeyer@609
|
210 updated = false;
|
alexbrandmeyer@609
|
211 }
|
alexbrandmeyer@609
|
212 return updated;
|
alexbrandmeyer@609
|
213 }
|
alexbrandmeyer@609
|
214
|
alexbrandmeyer@609
|
215 FloatArray Ear::AGCSpatialSmooth(int stage, FloatArray stage_state){
|
alexbrandmeyer@609
|
216 int n_iterations = agc_coeffs_.agc_spatial_iterations_(stage);
|
alexbrandmeyer@609
|
217 bool use_fir;
|
alexbrandmeyer@609
|
218 if (n_iterations < 4){
|
alexbrandmeyer@609
|
219 use_fir = true;
|
alexbrandmeyer@609
|
220 } else {
|
alexbrandmeyer@609
|
221 use_fir = false;
|
alexbrandmeyer@609
|
222 }
|
alexbrandmeyer@609
|
223
|
alexbrandmeyer@609
|
224 if (use_fir) {
|
alexbrandmeyer@609
|
225 FloatArray fir_coeffs = agc_coeffs_.agc_spatial_fir_.block(0,stage,3,1);
|
alexbrandmeyer@609
|
226 FloatArray ss_tap1(n_ch_);
|
alexbrandmeyer@609
|
227 FloatArray ss_tap2(n_ch_);
|
alexbrandmeyer@609
|
228 FloatArray ss_tap3(n_ch_);
|
alexbrandmeyer@609
|
229 FloatArray ss_tap4(n_ch_);
|
alexbrandmeyer@609
|
230 int n_taps = agc_coeffs_.agc_spatial_n_taps_(stage);
|
alexbrandmeyer@609
|
231 //Initialize first two taps of stage state, used for both cases
|
alexbrandmeyer@609
|
232 ss_tap1(0) = stage_state(0);
|
alexbrandmeyer@609
|
233 ss_tap1.block(1,0,n_ch_-1,1) = stage_state.block(0,0,n_ch_-1,1);
|
alexbrandmeyer@609
|
234 ss_tap2(n_ch_-1) = stage_state(n_ch_-1);
|
alexbrandmeyer@609
|
235 ss_tap2.block(0,0,n_ch_-1,1) = stage_state.block(1,0,n_ch_-1,1);
|
alexbrandmeyer@609
|
236 switch (n_taps) {
|
alexbrandmeyer@609
|
237 case 3:
|
alexbrandmeyer@609
|
238 stage_state = (fir_coeffs(0) * ss_tap1) +
|
alexbrandmeyer@609
|
239 (fir_coeffs(1) * stage_state) +
|
alexbrandmeyer@609
|
240 (fir_coeffs(2) * ss_tap2);
|
alexbrandmeyer@609
|
241 break;
|
alexbrandmeyer@609
|
242 case 5:
|
alexbrandmeyer@609
|
243 //Initialize last two taps of stage state, used for 5-tap case
|
alexbrandmeyer@609
|
244 ss_tap3(0) = stage_state(0);
|
alexbrandmeyer@609
|
245 ss_tap3(1) = stage_state(1);
|
alexbrandmeyer@609
|
246 ss_tap3.block(2,0,n_ch_-2,1) = stage_state.block(0,0,n_ch_-2,1);
|
alexbrandmeyer@609
|
247 ss_tap4(n_ch_-2) = stage_state(n_ch_-1);
|
alexbrandmeyer@609
|
248 ss_tap4(n_ch_-1) = stage_state(n_ch_-2);
|
alexbrandmeyer@609
|
249 ss_tap4.block(0,0,n_ch_-2,1) = stage_state.block(2,0,n_ch_-2,1);
|
alexbrandmeyer@609
|
250
|
alexbrandmeyer@609
|
251 stage_state = (fir_coeffs(0) * (ss_tap3 + ss_tap1)) +
|
alexbrandmeyer@609
|
252 (fir_coeffs(1) * stage_state) +
|
alexbrandmeyer@609
|
253 (fir_coeffs(2) * (ss_tap2 + ss_tap4));
|
alexbrandmeyer@609
|
254 break;
|
alexbrandmeyer@609
|
255 default:
|
alexbrandmeyer@609
|
256 //TODO Throw Error
|
alexbrandmeyer@609
|
257 std::cout << "Error: bad n-taps in AGCSpatialSmooth" << std::endl;
|
alexbrandmeyer@609
|
258 }
|
alexbrandmeyer@609
|
259
|
alexbrandmeyer@609
|
260 } else {
|
alexbrandmeyer@609
|
261 stage_state = AGCSmoothDoubleExponential(stage_state);
|
alexbrandmeyer@609
|
262 }
|
alexbrandmeyer@609
|
263 return stage_state;
|
alexbrandmeyer@609
|
264 }
|
alexbrandmeyer@609
|
265
|
alexbrandmeyer@609
|
266 FloatArray Ear::AGCSmoothDoubleExponential(FloatArray stage_state){
|
alexbrandmeyer@609
|
267 return stage_state;
|
alexbrandmeyer@609
|
268 }
|