annotate carfac/ear.cc @ 609:aefe2ca0674f

First version of a C++ implementation by Alex Brandmeyer
author alexbrandmeyer
date Mon, 13 May 2013 22:51:15 +0000
parents
children 01986636257a
rev   line source
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 }