annotate carfac/ear.cc @ 643:8b70f4cf00c7

Additional changes to C++ CARFAC on the basis of ronw's comments on r289. Moved CARFAC::Design to CARFAC::CARFAC and CARFAC::Reset(), moved carfac_common.h to common.h, CARFACDetect to carfac_util.h/cc, FloatArray and Float2dArray to ArrayX and ArrayXX, improved variable naming, made a start on improved commenting documentation.
author alexbrandmeyer
date Tue, 04 Jun 2013 18:30:22 +0000
parents d08c02c8e26f
children 3f01a136c537
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
ronw@628 23 #include <assert.h>
alexbrandmeyer@609 24 #include "ear.h"
alexbrandmeyer@609 25
alexbrandmeyer@643 26 void Ear::Init(const int num_channels, const CARCoeffs& car_coeffs,
alexbrandmeyer@643 27 const IHCCoeffs& ihc_coeffs,
alexbrandmeyer@643 28 const std::vector<AGCCoeffs>& agc_coeffs) {
alexbrandmeyer@643 29 num_channels_ = num_channels;
alexbrandmeyer@636 30 car_coeffs_ = car_coeffs;
alexbrandmeyer@636 31 ihc_coeffs_ = ihc_coeffs;
alexbrandmeyer@636 32 agc_coeffs_ = agc_coeffs;
alexbrandmeyer@610 33 InitCARState();
alexbrandmeyer@610 34 InitIHCState();
alexbrandmeyer@610 35 InitAGCState();
alexbrandmeyer@609 36 }
alexbrandmeyer@609 37
alexbrandmeyer@610 38 void Ear::InitCARState() {
alexbrandmeyer@643 39 car_state_.z1_memory.setZero(num_channels_);
alexbrandmeyer@643 40 car_state_.z2_memory.setZero(num_channels_);
alexbrandmeyer@643 41 car_state_.za_memory.setZero(num_channels_);
alexbrandmeyer@640 42 car_state_.zb_memory = car_coeffs_.zr_coeffs;
alexbrandmeyer@643 43 car_state_.dzb_memory.setZero(num_channels_);
alexbrandmeyer@643 44 car_state_.zy_memory.setZero(num_channels_);
alexbrandmeyer@640 45 car_state_.g_memory = car_coeffs_.g0_coeffs;
alexbrandmeyer@643 46 car_state_.dg_memory.setZero(num_channels_);
alexbrandmeyer@610 47 }
alexbrandmeyer@610 48
alexbrandmeyer@610 49 void Ear::InitIHCState() {
alexbrandmeyer@643 50 ihc_state_.ihc_accum = ArrayX::Zero(num_channels_);
alexbrandmeyer@640 51 if (! ihc_coeffs_.just_half_wave_rectify) {
alexbrandmeyer@643 52 ihc_state_.ac_coupler.setZero(num_channels_);
alexbrandmeyer@643 53 ihc_state_.lpf1_state.setConstant(num_channels_, ihc_coeffs_.rest_output);
alexbrandmeyer@643 54 ihc_state_.lpf2_state.setConstant(num_channels_, ihc_coeffs_.rest_output);
alexbrandmeyer@640 55 if (ihc_coeffs_.one_capacitor) {
alexbrandmeyer@643 56 ihc_state_.cap1_voltage.setConstant(num_channels_, ihc_coeffs_.rest_cap1);
alexbrandmeyer@610 57 } else {
alexbrandmeyer@643 58 ihc_state_.cap1_voltage.setConstant(num_channels_, ihc_coeffs_.rest_cap1);
alexbrandmeyer@643 59 ihc_state_.cap2_voltage.setConstant(num_channels_, ihc_coeffs_.rest_cap2);
alexbrandmeyer@610 60 }
alexbrandmeyer@610 61 }
alexbrandmeyer@610 62 }
alexbrandmeyer@610 63
alexbrandmeyer@610 64 void Ear::InitAGCState() {
alexbrandmeyer@626 65 int n_agc_stages = agc_coeffs_.size();
alexbrandmeyer@626 66 agc_state_.resize(n_agc_stages);
alexbrandmeyer@636 67 for (auto& stage_state : agc_state_) {
alexbrandmeyer@640 68 stage_state.decim_phase = 0;
alexbrandmeyer@643 69 stage_state.agc_memory.setZero(num_channels_);
alexbrandmeyer@643 70 stage_state.input_accum.setZero(num_channels_);
alexbrandmeyer@610 71 }
alexbrandmeyer@610 72 }
alexbrandmeyer@610 73
alexbrandmeyer@637 74 void Ear::CARStep(const FPType input) {
alexbrandmeyer@626 75 // This interpolates g.
alexbrandmeyer@640 76 car_state_.g_memory = car_state_.g_memory + car_state_.dg_memory;
alexbrandmeyer@610 77 // This calculates the AGC interpolation state.
alexbrandmeyer@640 78 car_state_.zb_memory = car_state_.zb_memory + car_state_.dzb_memory;
alexbrandmeyer@610 79 // This updates the nonlinear function of 'velocity' along with zA, which is
alexbrandmeyer@610 80 // a delay of z2.
alexbrandmeyer@643 81 ArrayX nonlinear_fun(num_channels_);
alexbrandmeyer@643 82 ArrayX velocities = car_state_.z2_memory - car_state_.za_memory;
alexbrandmeyer@626 83 OHCNonlinearFunction(velocities, &nonlinear_fun);
alexbrandmeyer@626 84 // Here, zb_memory_ * nonlinear_fun is "undamping" delta r.
alexbrandmeyer@643 85 ArrayX r = car_coeffs_.r1_coeffs + (car_state_.zb_memory * nonlinear_fun);
alexbrandmeyer@640 86 car_state_.za_memory = car_state_.z2_memory;
alexbrandmeyer@610 87 // Here we reduce the CAR state by r and rotate with the fixed cos/sin coeffs.
alexbrandmeyer@643 88 ArrayX z1 = r * ((car_coeffs_.a0_coeffs * car_state_.z1_memory) -
alexbrandmeyer@643 89 (car_coeffs_.c0_coeffs * car_state_.z2_memory));
alexbrandmeyer@640 90 car_state_.z2_memory = r *
alexbrandmeyer@640 91 ((car_coeffs_.c0_coeffs * car_state_.z1_memory) +
alexbrandmeyer@640 92 (car_coeffs_.a0_coeffs * car_state_.z2_memory));
alexbrandmeyer@640 93 car_state_.zy_memory = car_coeffs_.h_coeffs * car_state_.z2_memory;
alexbrandmeyer@610 94 // This section ripples the input-output path, to avoid delay...
alexbrandmeyer@610 95 // It's the only part that doesn't get computed "in parallel":
alexbrandmeyer@626 96 FPType in_out = input;
alexbrandmeyer@643 97 for (int channel = 0; channel < num_channels_; channel++) {
alexbrandmeyer@643 98 z1(channel) = z1(channel) + in_out;
alexbrandmeyer@610 99 // This performs the ripple, and saves the final channel outputs in zy.
alexbrandmeyer@643 100 in_out = car_state_.g_memory(channel) *
alexbrandmeyer@643 101 (in_out + car_state_.zy_memory(channel));
alexbrandmeyer@643 102 car_state_.zy_memory(channel) = in_out;
alexbrandmeyer@609 103 }
alexbrandmeyer@640 104 car_state_.z1_memory = z1;
alexbrandmeyer@609 105 }
alexbrandmeyer@609 106
alexbrandmeyer@610 107 // We start with a quadratic nonlinear function, and limit it via a
alexbrandmeyer@610 108 // rational function. This makes the result go to zero at high
alexbrandmeyer@609 109 // absolute velocities, so it will do nothing there.
alexbrandmeyer@643 110 void Ear::OHCNonlinearFunction(const ArrayX& velocities,
alexbrandmeyer@643 111 ArrayX* nonlinear_fun) {
alexbrandmeyer@640 112 *nonlinear_fun = (1 + ((velocities * car_coeffs_.velocity_scale) +
alexbrandmeyer@640 113 car_coeffs_.v_offset).square()).inverse();
alexbrandmeyer@609 114 }
alexbrandmeyer@609 115
alexbrandmeyer@610 116 // This step is a one sample-time update of the inner-hair-cell (IHC) model,
alexbrandmeyer@610 117 // including the detection nonlinearity and either one or two capacitor state
alexbrandmeyer@610 118 // variables.
alexbrandmeyer@643 119 void Ear::IHCStep(const ArrayX& car_out) {
alexbrandmeyer@643 120 ArrayX ac_diff = car_out - ihc_state_.ac_coupler;
alexbrandmeyer@640 121 ihc_state_.ac_coupler = ihc_state_.ac_coupler +
alexbrandmeyer@640 122 (ihc_coeffs_.ac_coeff * ac_diff);
alexbrandmeyer@640 123 if (ihc_coeffs_.just_half_wave_rectify) {
alexbrandmeyer@643 124 ArrayX output(num_channels_);
alexbrandmeyer@643 125 for (int channel = 0; channel < num_channels_; ++channel) {
alexbrandmeyer@643 126 FPType a = (ac_diff(channel) > 0.0) ? ac_diff(channel) : 0.0;
alexbrandmeyer@643 127 output(channel) = (a < 2) ? a : 2;
alexbrandmeyer@609 128 }
alexbrandmeyer@640 129 ihc_state_.ihc_out = output;
alexbrandmeyer@609 130 } else {
alexbrandmeyer@643 131 ArrayX conductance = CARFACDetect(ac_diff);
alexbrandmeyer@640 132 if (ihc_coeffs_.one_capacitor) {
alexbrandmeyer@640 133 ihc_state_.ihc_out = conductance * ihc_state_.cap1_voltage;
alexbrandmeyer@640 134 ihc_state_.cap1_voltage = ihc_state_.cap1_voltage -
alexbrandmeyer@640 135 (ihc_state_.ihc_out * ihc_coeffs_.out1_rate) +
alexbrandmeyer@640 136 ((1 - ihc_state_.cap1_voltage) * ihc_coeffs_.in1_rate);
alexbrandmeyer@609 137 } else {
alexbrandmeyer@640 138 ihc_state_.ihc_out = conductance * ihc_state_.cap2_voltage;
alexbrandmeyer@640 139 ihc_state_.cap1_voltage = ihc_state_.cap1_voltage -
alexbrandmeyer@640 140 ((ihc_state_.cap1_voltage - ihc_state_.cap2_voltage)
alexbrandmeyer@640 141 * ihc_coeffs_.out1_rate) + ((1 - ihc_state_.cap1_voltage) *
alexbrandmeyer@640 142 ihc_coeffs_.in1_rate);
alexbrandmeyer@640 143 ihc_state_.cap2_voltage = ihc_state_.cap2_voltage -
alexbrandmeyer@640 144 (ihc_state_.ihc_out * ihc_coeffs_.out2_rate) +
alexbrandmeyer@640 145 ((ihc_state_.cap1_voltage - ihc_state_.cap2_voltage)
alexbrandmeyer@640 146 * ihc_coeffs_.in2_rate);
alexbrandmeyer@609 147 }
alexbrandmeyer@610 148 // Here we smooth the output twice using a LPF.
alexbrandmeyer@640 149 ihc_state_.ihc_out *= ihc_coeffs_.output_gain;
alexbrandmeyer@640 150 ihc_state_.lpf1_state += ihc_coeffs_.lpf_coeff *
alexbrandmeyer@640 151 (ihc_state_.ihc_out - ihc_state_.lpf1_state);
alexbrandmeyer@640 152 ihc_state_.lpf2_state += ihc_coeffs_.lpf_coeff *
alexbrandmeyer@640 153 (ihc_state_.lpf1_state - ihc_state_.lpf2_state);
alexbrandmeyer@640 154 ihc_state_.ihc_out = ihc_state_.lpf2_state - ihc_coeffs_.rest_output;
alexbrandmeyer@609 155 }
alexbrandmeyer@640 156 ihc_state_.ihc_accum += ihc_state_.ihc_out;
alexbrandmeyer@609 157 }
alexbrandmeyer@609 158
alexbrandmeyer@643 159 bool Ear::AGCStep(const ArrayX& ihc_out) {
alexbrandmeyer@609 160 int stage = 0;
alexbrandmeyer@643 161 int num_stages = agc_coeffs_[0].num_agc_stages;
alexbrandmeyer@643 162 FPType detect_scale = agc_coeffs_[num_stages - 1].detect_scale;
alexbrandmeyer@626 163 bool updated = AGCRecurse(stage, detect_scale * ihc_out);
alexbrandmeyer@609 164 return updated;
alexbrandmeyer@609 165 }
alexbrandmeyer@609 166
alexbrandmeyer@643 167 bool Ear::AGCRecurse(const int stage, ArrayX agc_in) {
alexbrandmeyer@609 168 bool updated = true;
alexbrandmeyer@636 169 const auto& agc_coeffs = agc_coeffs_[stage];
alexbrandmeyer@636 170 auto& agc_state = agc_state_[stage];
alexbrandmeyer@610 171 // This is the decim factor for this stage, relative to input or prev. stage:
alexbrandmeyer@640 172 int decim = agc_coeffs.decimation;
alexbrandmeyer@610 173 // This is the decim phase of this stage (do work on phase 0 only):
alexbrandmeyer@640 174 int decim_phase = agc_state.decim_phase + 1;
alexbrandmeyer@609 175 decim_phase = decim_phase % decim;
alexbrandmeyer@640 176 agc_state.decim_phase = decim_phase;
alexbrandmeyer@610 177 // Here we accumulate input for this stage from the previous stage:
alexbrandmeyer@640 178 agc_state.input_accum += agc_in;
alexbrandmeyer@610 179 // We don't do anything if it's not the right decim_phase.
alexbrandmeyer@610 180 if (decim_phase == 0) {
alexbrandmeyer@610 181 // Now we do lots of work, at the decimated rate.
alexbrandmeyer@610 182 // These are the decimated inputs for this stage, which will be further
alexbrandmeyer@610 183 // decimated at the next stage.
alexbrandmeyer@640 184 agc_in = agc_state.input_accum / decim;
alexbrandmeyer@610 185 // This resets the accumulator.
alexbrandmeyer@643 186 agc_state.input_accum = ArrayX::Zero(num_channels_);
alexbrandmeyer@626 187 if (stage < (agc_coeffs_.size() - 1)) {
alexbrandmeyer@610 188 // Now we recurse to evaluate the next stage(s).
alexbrandmeyer@610 189 updated = AGCRecurse(stage + 1, agc_in);
alexbrandmeyer@610 190 // Afterwards we add its output to this stage input, whether it updated or
alexbrandmeyer@610 191 // not.
alexbrandmeyer@640 192 agc_in += agc_coeffs.agc_stage_gain *
alexbrandmeyer@640 193 agc_state_[stage + 1].agc_memory;
alexbrandmeyer@609 194 }
alexbrandmeyer@610 195 // This performs a first-order recursive smoothing filter update, in time.
alexbrandmeyer@640 196 agc_state.agc_memory += agc_coeffs.agc_epsilon *
alexbrandmeyer@640 197 (agc_in - agc_state.agc_memory);
alexbrandmeyer@640 198 AGCSpatialSmooth(agc_coeffs_[stage], &agc_state.agc_memory);
alexbrandmeyer@626 199 updated = true;
alexbrandmeyer@609 200 } else {
alexbrandmeyer@609 201 updated = false;
alexbrandmeyer@609 202 }
alexbrandmeyer@609 203 return updated;
alexbrandmeyer@609 204 }
alexbrandmeyer@609 205
alexbrandmeyer@637 206 void Ear::AGCSpatialSmooth(const AGCCoeffs& agc_coeffs,
alexbrandmeyer@643 207 ArrayX* stage_state) {
alexbrandmeyer@643 208 int num_iterations = agc_coeffs.agc_spatial_iterations;
alexbrandmeyer@609 209 bool use_fir;
alexbrandmeyer@643 210 use_fir = (num_iterations < 4) ? true : false;
alexbrandmeyer@609 211 if (use_fir) {
alexbrandmeyer@640 212 FPType fir_coeffs_left = agc_coeffs.agc_spatial_fir_left;
alexbrandmeyer@640 213 FPType fir_coeffs_mid = agc_coeffs.agc_spatial_fir_mid;
alexbrandmeyer@640 214 FPType fir_coeffs_right = agc_coeffs.agc_spatial_fir_right;
alexbrandmeyer@643 215 ArrayX ss_tap1(num_channels_);
alexbrandmeyer@643 216 ArrayX ss_tap2(num_channels_);
alexbrandmeyer@643 217 ArrayX ss_tap3(num_channels_);
alexbrandmeyer@643 218 ArrayX ss_tap4(num_channels_);
alexbrandmeyer@640 219 int n_taps = agc_coeffs.agc_spatial_n_taps;
alexbrandmeyer@610 220 // This initializes the first two taps of stage state, which are used for
alexbrandmeyer@610 221 // both possible cases.
alexbrandmeyer@637 222 ss_tap1(0) = (*stage_state)(0);
alexbrandmeyer@643 223 ss_tap1.block(1, 0, num_channels_ - 1, 1) =
alexbrandmeyer@643 224 stage_state->block(0, 0, num_channels_ - 1, 1);
alexbrandmeyer@643 225 ss_tap2(num_channels_ - 1) = (*stage_state)(num_channels_ - 1);
alexbrandmeyer@643 226 ss_tap2.block(0, 0, num_channels_ - 1, 1) =
alexbrandmeyer@643 227 stage_state->block(1, 0, num_channels_ - 1, 1);
alexbrandmeyer@609 228 switch (n_taps) {
alexbrandmeyer@609 229 case 3:
alexbrandmeyer@637 230 *stage_state = (fir_coeffs_left * ss_tap1) +
alexbrandmeyer@637 231 (fir_coeffs_mid * *stage_state) + (fir_coeffs_right * ss_tap2);
alexbrandmeyer@609 232 break;
alexbrandmeyer@609 233 case 5:
alexbrandmeyer@610 234 // Now we initialize last two taps of stage state, which are only used
alexbrandmeyer@610 235 // for the 5-tap case.
alexbrandmeyer@637 236 ss_tap3(0) = (*stage_state)(0);
alexbrandmeyer@637 237 ss_tap3(1) = (*stage_state)(1);
alexbrandmeyer@643 238 ss_tap3.block(2, 0, num_channels_ - 2, 1) =
alexbrandmeyer@643 239 stage_state->block(0, 0, num_channels_ - 2, 1);
alexbrandmeyer@643 240 ss_tap4(num_channels_ - 2) = (*stage_state)(num_channels_ - 1);
alexbrandmeyer@643 241 ss_tap4(num_channels_ - 1) = (*stage_state)(num_channels_ - 2);
alexbrandmeyer@643 242 ss_tap4.block(0, 0, num_channels_ - 2, 1) =
alexbrandmeyer@643 243 stage_state->block(2, 0, num_channels_ - 2, 1);
alexbrandmeyer@637 244 *stage_state = (fir_coeffs_left * (ss_tap3 + ss_tap1)) +
alexbrandmeyer@637 245 (fir_coeffs_mid * *stage_state) +
alexbrandmeyer@637 246 (fir_coeffs_right * (ss_tap2 + ss_tap4));
alexbrandmeyer@609 247 break;
alexbrandmeyer@609 248 default:
ronw@628 249 assert(true && "Bad n_taps in AGCSpatialSmooth; should be 3 or 5.");
alexbrandmeyer@611 250 break;
alexbrandmeyer@609 251 }
alexbrandmeyer@609 252 } else {
alexbrandmeyer@640 253 AGCSmoothDoubleExponential(agc_coeffs.agc_pole_z1, agc_coeffs.agc_pole_z2,
alexbrandmeyer@640 254 stage_state);
alexbrandmeyer@609 255 }
alexbrandmeyer@609 256 }
alexbrandmeyer@609 257
alexbrandmeyer@643 258 void Ear::AGCSmoothDoubleExponential(const FPType pole_z1,
alexbrandmeyer@643 259 const FPType pole_z2,
alexbrandmeyer@643 260 ArrayX* stage_state) {
alexbrandmeyer@643 261 int32_t num_points = stage_state->size();
alexbrandmeyer@610 262 FPType input;
alexbrandmeyer@611 263 FPType state = 0.0;
alexbrandmeyer@626 264 // TODO (alexbrandmeyer): I'm assuming one dimensional input for now, but this
alexbrandmeyer@610 265 // should be verified with Dick for the final version
alexbrandmeyer@643 266 for (int i = num_points - 11; i < num_points; ++i) {
alexbrandmeyer@637 267 input = (*stage_state)(i);
alexbrandmeyer@610 268 state = state + (1 - pole_z1) * (input - state);
alexbrandmeyer@610 269 }
alexbrandmeyer@643 270 for (int i = num_points - 1; i > -1; --i) {
alexbrandmeyer@637 271 input = (*stage_state)(i);
alexbrandmeyer@610 272 state = state + (1 - pole_z2) * (input - state);
alexbrandmeyer@610 273 }
alexbrandmeyer@643 274 for (int i = 0; i < num_points; ++i) {
alexbrandmeyer@637 275 input = (*stage_state)(i);
alexbrandmeyer@610 276 state = state + (1 - pole_z1) * (input - state);
alexbrandmeyer@637 277 (*stage_state)(i) = state;
alexbrandmeyer@610 278 }
alexbrandmeyer@609 279 }
alexbrandmeyer@610 280
alexbrandmeyer@643 281 ArrayX Ear::StageGValue(const ArrayX& undamping) {
alexbrandmeyer@643 282 ArrayX r = car_coeffs_.r1_coeffs + car_coeffs_.zr_coeffs * undamping;
alexbrandmeyer@640 283 return (1 - 2 * r * car_coeffs_.a0_coeffs + (r * r)) /
alexbrandmeyer@640 284 (1 - 2 * r * car_coeffs_.a0_coeffs + car_coeffs_.h_coeffs * r *
alexbrandmeyer@640 285 car_coeffs_.c0_coeffs + (r * r));
alexbrandmeyer@637 286 }