annotate carfac/ear.cc @ 645:3f01a136c537

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