annotate carfac/ear.cc @ 637:efc5b1b54f63

Sixth revision of Alex Brandmeyer's C++ implementation. Only small changes in response to Lyon's comments from r285. Note: I tried to use a consistent indentation with two spaces, but also preserving parenthetical structure to make reading the longer equations easier. Please advise if this is OK. Additional documentation and tests with non-standard parameter sets will be added in a subsequent revision.
author alexbrandmeyer
date Tue, 28 May 2013 15:54:54 +0000
parents 27f2d9b76075
children d08c02c8e26f
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@610 26 // The 'InitEar' function takes a set of model parameters and initializes the
alexbrandmeyer@610 27 // design coefficients and model state variables needed for running the model
alexbrandmeyer@626 28 // on a single audio channel.
alexbrandmeyer@637 29 void Ear::InitEar(const int n_ch, const FPType fs, const CARCoeffs& car_coeffs,
alexbrandmeyer@637 30 const IHCCoeffs& ihc_coeffs,
alexbrandmeyer@637 31 const std::vector<AGCCoeffs>& agc_coeffs) {
alexbrandmeyer@610 32 // The first section of code determines the number of channels that will be
alexbrandmeyer@610 33 // used in the model on the basis of the sample rate and the CAR parameters
alexbrandmeyer@610 34 // that have been passed to this function.
alexbrandmeyer@610 35 n_ch_ = n_ch;
alexbrandmeyer@636 36 car_coeffs_ = car_coeffs;
alexbrandmeyer@636 37 ihc_coeffs_ = ihc_coeffs;
alexbrandmeyer@636 38 agc_coeffs_ = agc_coeffs;
alexbrandmeyer@610 39 // Once the coefficients have been determined, we can initialize the state
alexbrandmeyer@610 40 // variables that will be used during runtime.
alexbrandmeyer@610 41 InitCARState();
alexbrandmeyer@610 42 InitIHCState();
alexbrandmeyer@610 43 InitAGCState();
alexbrandmeyer@609 44 }
alexbrandmeyer@609 45
alexbrandmeyer@610 46 void Ear::InitCARState() {
alexbrandmeyer@636 47 car_state_.z1_memory_.setZero(n_ch_);
alexbrandmeyer@636 48 car_state_.z2_memory_.setZero(n_ch_);
alexbrandmeyer@636 49 car_state_.za_memory_.setZero(n_ch_);
alexbrandmeyer@610 50 car_state_.zb_memory_ = car_coeffs_.zr_coeffs_;
alexbrandmeyer@636 51 car_state_.dzb_memory_.setZero(n_ch_);
alexbrandmeyer@636 52 car_state_.zy_memory_.setZero(n_ch_);
alexbrandmeyer@610 53 car_state_.g_memory_ = car_coeffs_.g0_coeffs_;
alexbrandmeyer@636 54 car_state_.dg_memory_.setZero(n_ch_);
alexbrandmeyer@610 55 }
alexbrandmeyer@610 56
alexbrandmeyer@610 57 void Ear::InitIHCState() {
alexbrandmeyer@610 58 ihc_state_.ihc_accum_ = FloatArray::Zero(n_ch_);
alexbrandmeyer@636 59 if (! ihc_coeffs_.just_half_wave_rectify_) {
alexbrandmeyer@636 60 ihc_state_.ac_coupler_.setZero(n_ch_);
alexbrandmeyer@636 61 ihc_state_.lpf1_state_.setConstant(n_ch_, ihc_coeffs_.rest_output_);
alexbrandmeyer@636 62 ihc_state_.lpf2_state_.setConstant(n_ch_, ihc_coeffs_.rest_output_);
alexbrandmeyer@636 63 if (ihc_coeffs_.one_capacitor_) {
alexbrandmeyer@636 64 ihc_state_.cap1_voltage_.setConstant(n_ch_, ihc_coeffs_.rest_cap1_);
alexbrandmeyer@610 65 } else {
alexbrandmeyer@636 66 ihc_state_.cap1_voltage_.setConstant(n_ch_, ihc_coeffs_.rest_cap1_);
alexbrandmeyer@636 67 ihc_state_.cap2_voltage_.setConstant(n_ch_, ihc_coeffs_.rest_cap2_);
alexbrandmeyer@610 68 }
alexbrandmeyer@610 69 }
alexbrandmeyer@610 70 }
alexbrandmeyer@610 71
alexbrandmeyer@610 72 void Ear::InitAGCState() {
alexbrandmeyer@626 73 int n_agc_stages = agc_coeffs_.size();
alexbrandmeyer@626 74 agc_state_.resize(n_agc_stages);
alexbrandmeyer@636 75 for (auto& stage_state : agc_state_) {
alexbrandmeyer@636 76 stage_state.decim_phase_ = 0;
alexbrandmeyer@636 77 stage_state.agc_memory_.setZero(n_ch_);
alexbrandmeyer@636 78 stage_state.input_accum_.setZero(n_ch_);
alexbrandmeyer@610 79 }
alexbrandmeyer@610 80 }
alexbrandmeyer@610 81
alexbrandmeyer@637 82 void Ear::CARStep(const FPType input) {
alexbrandmeyer@626 83 // This interpolates g.
alexbrandmeyer@626 84 car_state_.g_memory_ = car_state_.g_memory_ + car_state_.dg_memory_;
alexbrandmeyer@610 85 // This calculates the AGC interpolation state.
alexbrandmeyer@626 86 car_state_.zb_memory_ = car_state_.zb_memory_ + car_state_.dzb_memory_;
alexbrandmeyer@610 87 // This updates the nonlinear function of 'velocity' along with zA, which is
alexbrandmeyer@610 88 // a delay of z2.
alexbrandmeyer@626 89 FloatArray nonlinear_fun(n_ch_);
alexbrandmeyer@626 90 FloatArray velocities = car_state_.z2_memory_ - car_state_.za_memory_;
alexbrandmeyer@626 91 OHCNonlinearFunction(velocities, &nonlinear_fun);
alexbrandmeyer@626 92 // Here, zb_memory_ * nonlinear_fun is "undamping" delta r.
alexbrandmeyer@626 93 FloatArray r = car_coeffs_.r1_coeffs_ + (car_state_.zb_memory_ *
alexbrandmeyer@626 94 nonlinear_fun);
alexbrandmeyer@626 95 car_state_.za_memory_ = car_state_.z2_memory_;
alexbrandmeyer@610 96 // Here we reduce the CAR state by r and rotate with the fixed cos/sin coeffs.
alexbrandmeyer@626 97 FloatArray z1 = r * ((car_coeffs_.a0_coeffs_ * car_state_.z1_memory_) -
alexbrandmeyer@626 98 (car_coeffs_.c0_coeffs_ * car_state_.z2_memory_));
alexbrandmeyer@626 99 car_state_.z2_memory_ = r *
alexbrandmeyer@637 100 ((car_coeffs_.c0_coeffs_ * car_state_.z1_memory_) +
alexbrandmeyer@637 101 (car_coeffs_.a0_coeffs_ * car_state_.z2_memory_));
alexbrandmeyer@626 102 car_state_.zy_memory_ = car_coeffs_.h_coeffs_ * car_state_.z2_memory_;
alexbrandmeyer@610 103 // This section ripples the input-output path, to avoid delay...
alexbrandmeyer@610 104 // It's the only part that doesn't get computed "in parallel":
alexbrandmeyer@626 105 FPType in_out = input;
alexbrandmeyer@610 106 for (int ch = 0; ch < n_ch_; ch++) {
alexbrandmeyer@609 107 z1(ch) = z1(ch) + in_out;
alexbrandmeyer@610 108 // This performs the ripple, and saves the final channel outputs in zy.
alexbrandmeyer@626 109 in_out = car_state_.g_memory_(ch) * (in_out + car_state_.zy_memory_(ch));
alexbrandmeyer@626 110 car_state_.zy_memory_(ch) = in_out;
alexbrandmeyer@609 111 }
alexbrandmeyer@609 112 car_state_.z1_memory_ = z1;
alexbrandmeyer@609 113 }
alexbrandmeyer@609 114
alexbrandmeyer@610 115 // We start with a quadratic nonlinear function, and limit it via a
alexbrandmeyer@610 116 // rational function. This makes the result go to zero at high
alexbrandmeyer@609 117 // absolute velocities, so it will do nothing there.
alexbrandmeyer@626 118 void Ear::OHCNonlinearFunction(const FloatArray& velocities,
alexbrandmeyer@626 119 FloatArray* nonlinear_fun) {
alexbrandmeyer@626 120 *nonlinear_fun = (1 + ((velocities * car_coeffs_.velocity_scale_) +
alexbrandmeyer@626 121 car_coeffs_.v_offset_).square()).inverse();
alexbrandmeyer@609 122 }
alexbrandmeyer@609 123
alexbrandmeyer@610 124 // This step is a one sample-time update of the inner-hair-cell (IHC) model,
alexbrandmeyer@610 125 // including the detection nonlinearity and either one or two capacitor state
alexbrandmeyer@610 126 // variables.
alexbrandmeyer@637 127 void Ear::IHCStep(const FloatArray& car_out) {
alexbrandmeyer@626 128 FloatArray ac_diff = car_out - ihc_state_.ac_coupler_;
alexbrandmeyer@609 129 ihc_state_.ac_coupler_ = ihc_state_.ac_coupler_ +
alexbrandmeyer@637 130 (ihc_coeffs_.ac_coeff_ * ac_diff);
alexbrandmeyer@636 131 if (ihc_coeffs_.just_half_wave_rectify_) {
alexbrandmeyer@626 132 FloatArray output(n_ch_);
alexbrandmeyer@626 133 for (int ch = 0; ch < n_ch_; ++ch) {
alexbrandmeyer@626 134 FPType a = (ac_diff(ch) > 0.0) ? ac_diff(ch) : 0.0;
alexbrandmeyer@626 135 output(ch) = (a < 2) ? a : 2;
alexbrandmeyer@609 136 }
alexbrandmeyer@637 137 ihc_state_.ihc_out_ = output;
alexbrandmeyer@609 138 } else {
alexbrandmeyer@626 139 FloatArray conductance = CARFACDetect(ac_diff);
alexbrandmeyer@636 140 if (ihc_coeffs_.one_capacitor_) {
alexbrandmeyer@637 141 ihc_state_.ihc_out_ = conductance * ihc_state_.cap1_voltage_;
alexbrandmeyer@609 142 ihc_state_.cap1_voltage_ = ihc_state_.cap1_voltage_ -
alexbrandmeyer@637 143 (ihc_state_.ihc_out_ * ihc_coeffs_.out1_rate_) +
alexbrandmeyer@637 144 ((1 - ihc_state_.cap1_voltage_) * ihc_coeffs_.in1_rate_);
alexbrandmeyer@609 145 } else {
alexbrandmeyer@637 146 ihc_state_.ihc_out_ = conductance * ihc_state_.cap2_voltage_;
alexbrandmeyer@609 147 ihc_state_.cap1_voltage_ = ihc_state_.cap1_voltage_ -
alexbrandmeyer@637 148 ((ihc_state_.cap1_voltage_ - ihc_state_.cap2_voltage_)
alexbrandmeyer@637 149 * ihc_coeffs_.out1_rate_) + ((1 - ihc_state_.cap1_voltage_) *
alexbrandmeyer@637 150 ihc_coeffs_.in1_rate_);
alexbrandmeyer@609 151 ihc_state_.cap2_voltage_ = ihc_state_.cap2_voltage_ -
alexbrandmeyer@637 152 (ihc_state_.ihc_out_ * ihc_coeffs_.out2_rate_) +
alexbrandmeyer@637 153 ((ihc_state_.cap1_voltage_ - ihc_state_.cap2_voltage_)
alexbrandmeyer@637 154 * ihc_coeffs_.in2_rate_);
alexbrandmeyer@609 155 }
alexbrandmeyer@610 156 // Here we smooth the output twice using a LPF.
alexbrandmeyer@637 157 ihc_state_.ihc_out_ *= ihc_coeffs_.output_gain_;
alexbrandmeyer@636 158 ihc_state_.lpf1_state_ += ihc_coeffs_.lpf_coeff_ *
alexbrandmeyer@637 159 (ihc_state_.ihc_out_ - ihc_state_.lpf1_state_);
alexbrandmeyer@636 160 ihc_state_.lpf2_state_ += ihc_coeffs_.lpf_coeff_ *
alexbrandmeyer@637 161 (ihc_state_.lpf1_state_ - ihc_state_.lpf2_state_);
alexbrandmeyer@637 162 ihc_state_.ihc_out_ = ihc_state_.lpf2_state_ - ihc_coeffs_.rest_output_;
alexbrandmeyer@609 163 }
alexbrandmeyer@637 164 ihc_state_.ihc_accum_ += ihc_state_.ihc_out_;
alexbrandmeyer@609 165 }
alexbrandmeyer@609 166
alexbrandmeyer@626 167 bool Ear::AGCStep(const FloatArray& ihc_out) {
alexbrandmeyer@609 168 int stage = 0;
alexbrandmeyer@626 169 int n_stages = agc_coeffs_[0].n_agc_stages_;
alexbrandmeyer@626 170 FPType detect_scale = agc_coeffs_[n_stages - 1].detect_scale_;
alexbrandmeyer@626 171 bool updated = AGCRecurse(stage, detect_scale * ihc_out);
alexbrandmeyer@609 172 return updated;
alexbrandmeyer@609 173 }
alexbrandmeyer@609 174
alexbrandmeyer@626 175 bool Ear::AGCRecurse(const int stage, FloatArray agc_in) {
alexbrandmeyer@609 176 bool updated = true;
alexbrandmeyer@636 177 const auto& agc_coeffs = agc_coeffs_[stage];
alexbrandmeyer@636 178 auto& agc_state = agc_state_[stage];
alexbrandmeyer@610 179 // This is the decim factor for this stage, relative to input or prev. stage:
alexbrandmeyer@636 180 int decim = agc_coeffs.decimation_;
alexbrandmeyer@610 181 // This is the decim phase of this stage (do work on phase 0 only):
alexbrandmeyer@636 182 int decim_phase = agc_state.decim_phase_ + 1;
alexbrandmeyer@609 183 decim_phase = decim_phase % decim;
alexbrandmeyer@636 184 agc_state.decim_phase_ = decim_phase;
alexbrandmeyer@610 185 // Here we accumulate input for this stage from the previous stage:
alexbrandmeyer@636 186 agc_state.input_accum_ += agc_in;
alexbrandmeyer@610 187 // We don't do anything if it's not the right decim_phase.
alexbrandmeyer@610 188 if (decim_phase == 0) {
alexbrandmeyer@610 189 // Now we do lots of work, at the decimated rate.
alexbrandmeyer@610 190 // These are the decimated inputs for this stage, which will be further
alexbrandmeyer@610 191 // decimated at the next stage.
alexbrandmeyer@636 192 agc_in = agc_state.input_accum_ / decim;
alexbrandmeyer@610 193 // This resets the accumulator.
alexbrandmeyer@636 194 agc_state.input_accum_ = FloatArray::Zero(n_ch_);
alexbrandmeyer@626 195 if (stage < (agc_coeffs_.size() - 1)) {
alexbrandmeyer@610 196 // Now we recurse to evaluate the next stage(s).
alexbrandmeyer@610 197 updated = AGCRecurse(stage + 1, agc_in);
alexbrandmeyer@610 198 // Afterwards we add its output to this stage input, whether it updated or
alexbrandmeyer@610 199 // not.
alexbrandmeyer@636 200 agc_in += agc_coeffs.agc_stage_gain_ *
alexbrandmeyer@637 201 agc_state_[stage + 1].agc_memory_;
alexbrandmeyer@609 202 }
alexbrandmeyer@610 203 // This performs a first-order recursive smoothing filter update, in time.
alexbrandmeyer@637 204 agc_state.agc_memory_ += agc_coeffs.agc_epsilon_ *
alexbrandmeyer@637 205 (agc_in - agc_state.agc_memory_);
alexbrandmeyer@637 206 AGCSpatialSmooth(agc_coeffs_[stage], &agc_state.agc_memory_);
alexbrandmeyer@626 207 updated = true;
alexbrandmeyer@609 208 } else {
alexbrandmeyer@609 209 updated = false;
alexbrandmeyer@609 210 }
alexbrandmeyer@609 211 return updated;
alexbrandmeyer@609 212 }
alexbrandmeyer@609 213
alexbrandmeyer@637 214 void Ear::AGCSpatialSmooth(const AGCCoeffs& agc_coeffs,
alexbrandmeyer@637 215 FloatArray* stage_state) {
alexbrandmeyer@637 216 int n_iterations = agc_coeffs.agc_spatial_iterations_;
alexbrandmeyer@609 217 bool use_fir;
alexbrandmeyer@610 218 use_fir = (n_iterations < 4) ? true : false;
alexbrandmeyer@609 219 if (use_fir) {
alexbrandmeyer@637 220 FPType fir_coeffs_left = agc_coeffs.agc_spatial_fir_left_;
alexbrandmeyer@637 221 FPType fir_coeffs_mid = agc_coeffs.agc_spatial_fir_mid_;
alexbrandmeyer@637 222 FPType fir_coeffs_right = agc_coeffs.agc_spatial_fir_right_;
alexbrandmeyer@609 223 FloatArray ss_tap1(n_ch_);
alexbrandmeyer@609 224 FloatArray ss_tap2(n_ch_);
alexbrandmeyer@609 225 FloatArray ss_tap3(n_ch_);
alexbrandmeyer@609 226 FloatArray ss_tap4(n_ch_);
alexbrandmeyer@637 227 int n_taps = agc_coeffs.agc_spatial_n_taps_;
alexbrandmeyer@610 228 // This initializes the first two taps of stage state, which are used for
alexbrandmeyer@610 229 // both possible cases.
alexbrandmeyer@637 230 ss_tap1(0) = (*stage_state)(0);
alexbrandmeyer@637 231 ss_tap1.block(1, 0, n_ch_ - 1, 1) = stage_state->block(0, 0, n_ch_ - 1, 1);
alexbrandmeyer@637 232 ss_tap2(n_ch_ - 1) = (*stage_state)(n_ch_ - 1);
alexbrandmeyer@637 233 ss_tap2.block(0, 0, n_ch_ - 1, 1) = stage_state->block(1, 0, n_ch_ - 1, 1);
alexbrandmeyer@609 234 switch (n_taps) {
alexbrandmeyer@609 235 case 3:
alexbrandmeyer@637 236 *stage_state = (fir_coeffs_left * ss_tap1) +
alexbrandmeyer@637 237 (fir_coeffs_mid * *stage_state) + (fir_coeffs_right * ss_tap2);
alexbrandmeyer@609 238 break;
alexbrandmeyer@609 239 case 5:
alexbrandmeyer@610 240 // Now we initialize last two taps of stage state, which are only used
alexbrandmeyer@610 241 // for the 5-tap case.
alexbrandmeyer@637 242 ss_tap3(0) = (*stage_state)(0);
alexbrandmeyer@637 243 ss_tap3(1) = (*stage_state)(1);
alexbrandmeyer@610 244 ss_tap3.block(2, 0, n_ch_ - 2, 1) =
alexbrandmeyer@637 245 stage_state->block(0, 0, n_ch_ - 2, 1);
alexbrandmeyer@637 246 ss_tap4(n_ch_ - 2) = (*stage_state)(n_ch_ - 1);
alexbrandmeyer@637 247 ss_tap4(n_ch_ - 1) = (*stage_state)(n_ch_ - 2);
alexbrandmeyer@610 248 ss_tap4.block(0, 0, n_ch_ - 2, 1) =
alexbrandmeyer@637 249 stage_state->block(2, 0, n_ch_ - 2, 1);
alexbrandmeyer@637 250 *stage_state = (fir_coeffs_left * (ss_tap3 + ss_tap1)) +
alexbrandmeyer@637 251 (fir_coeffs_mid * *stage_state) +
alexbrandmeyer@637 252 (fir_coeffs_right * (ss_tap2 + ss_tap4));
alexbrandmeyer@609 253 break;
alexbrandmeyer@609 254 default:
ronw@628 255 assert(true && "Bad n_taps in AGCSpatialSmooth; should be 3 or 5.");
alexbrandmeyer@611 256 break;
alexbrandmeyer@609 257 }
alexbrandmeyer@609 258 } else {
alexbrandmeyer@637 259 AGCSmoothDoubleExponential(agc_coeffs.agc_pole_z1_,
alexbrandmeyer@637 260 agc_coeffs.agc_pole_z2_, stage_state);
alexbrandmeyer@609 261 }
alexbrandmeyer@609 262 }
alexbrandmeyer@609 263
alexbrandmeyer@637 264 void Ear::AGCSmoothDoubleExponential(const FPType pole_z1, const FPType pole_z2,
alexbrandmeyer@637 265 FloatArray* stage_state) {
alexbrandmeyer@637 266 int32_t n_pts = stage_state->size();
alexbrandmeyer@610 267 FPType input;
alexbrandmeyer@611 268 FPType state = 0.0;
alexbrandmeyer@626 269 // TODO (alexbrandmeyer): I'm assuming one dimensional input for now, but this
alexbrandmeyer@610 270 // should be verified with Dick for the final version
alexbrandmeyer@637 271 for (int i = n_pts - 11; i < n_pts; ++i) {
alexbrandmeyer@637 272 input = (*stage_state)(i);
alexbrandmeyer@610 273 state = state + (1 - pole_z1) * (input - state);
alexbrandmeyer@610 274 }
alexbrandmeyer@637 275 for (int i = n_pts - 1; i > -1; --i) {
alexbrandmeyer@637 276 input = (*stage_state)(i);
alexbrandmeyer@610 277 state = state + (1 - pole_z2) * (input - state);
alexbrandmeyer@610 278 }
alexbrandmeyer@637 279 for (int i = 0; i < n_pts; ++i) {
alexbrandmeyer@637 280 input = (*stage_state)(i);
alexbrandmeyer@610 281 state = state + (1 - pole_z1) * (input - state);
alexbrandmeyer@637 282 (*stage_state)(i) = state;
alexbrandmeyer@610 283 }
alexbrandmeyer@609 284 }
alexbrandmeyer@610 285
alexbrandmeyer@626 286 FloatArray Ear::StageGValue(const FloatArray& undamping) {
alexbrandmeyer@626 287 FloatArray r = car_coeffs_.r1_coeffs_ + car_coeffs_.zr_coeffs_ * undamping;
alexbrandmeyer@626 288 return (1 - 2 * r * car_coeffs_.a0_coeffs_ + (r * r)) /
alexbrandmeyer@637 289 (1 - 2 * r * car_coeffs_.a0_coeffs_ + car_coeffs_.h_coeffs_ * r *
alexbrandmeyer@637 290 car_coeffs_.c0_coeffs_ + (r * r));
alexbrandmeyer@637 291 }