view trunk/carfac/ear.cc @ 704:e9855b95cd04

Small cleanup of eigen usage in SAI implementation.
author ronw@google.com
date Tue, 16 Jul 2013 19:56:11 +0000
parents 2d432ff51f64
children
line wrap: on
line source
//
//  ear.cc
//  CARFAC Open Source C++ Library
//
//  Created by Alex Brandmeyer on 5/10/13.
//
// This C++ file is part of an implementation of Lyon's cochlear model:
// "Cascade of Asymmetric Resonators with Fast-Acting Compression"
// to supplement Lyon's upcoming book "Human and Machine Hearing"
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "ear.h"

#include <assert.h>

#include "carfac_util.h"

Ear::Ear(const int num_channels, const CARCoeffs& car_coeffs,
         const IHCCoeffs& ihc_coeffs,
         const std::vector<AGCCoeffs>& agc_coeffs) {
  Redesign(num_channels, car_coeffs, ihc_coeffs, agc_coeffs);
}

void Ear::Redesign(const int num_channels, const CARCoeffs& car_coeffs,
                   const IHCCoeffs& ihc_coeffs,
                   const std::vector<AGCCoeffs>& agc_coeffs) {
  num_channels_ = num_channels;
  car_coeffs_ = car_coeffs;
  ihc_coeffs_ = ihc_coeffs;
  agc_coeffs_ = agc_coeffs;
  Reset();
}

void Ear::Reset() {
  InitCARState();
  InitIHCState();
  InitAGCState();
}

void Ear::InitCARState() {
  car_state_.z1_memory.setZero(num_channels_);
  car_state_.z2_memory.setZero(num_channels_);
  car_state_.za_memory.setZero(num_channels_);
  car_state_.zb_memory = car_coeffs_.zr_coeffs;
  car_state_.dzb_memory.setZero(num_channels_);
  car_state_.zy_memory.setZero(num_channels_);
  car_state_.g_memory = car_coeffs_.g0_coeffs;
  car_state_.dg_memory.setZero(num_channels_);
}

void Ear::InitIHCState() {
  ihc_state_.ihc_accum = ArrayX::Zero(num_channels_);
  if (!ihc_coeffs_.just_half_wave_rectify) {
    ihc_state_.ac_coupler.setZero(num_channels_);
    ihc_state_.lpf1_state.setConstant(num_channels_, ihc_coeffs_.rest_output);
    ihc_state_.lpf2_state.setConstant(num_channels_, ihc_coeffs_.rest_output);
    if (ihc_coeffs_.one_capacitor) {
      ihc_state_.cap1_voltage.setConstant(num_channels_, ihc_coeffs_.rest_cap1);
    } else {
      ihc_state_.cap1_voltage.setConstant(num_channels_, ihc_coeffs_.rest_cap1);
      ihc_state_.cap2_voltage.setConstant(num_channels_, ihc_coeffs_.rest_cap2);
    }
  }
}

void Ear::InitAGCState() {
  int n_agc_stages = agc_coeffs_.size();
  agc_state_.resize(n_agc_stages);
  for (AGCState& stage_state : agc_state_) {
    stage_state.decim_phase = 0;
    stage_state.agc_memory.setZero(num_channels_);
    stage_state.input_accum.setZero(num_channels_);
  }
}

void Ear::CARStep(const FPType input) {
  // Interpolates g.
  car_state_.g_memory = car_state_.g_memory + car_state_.dg_memory;
  // Calculates the AGC interpolation state.
  car_state_.zb_memory = car_state_.zb_memory + car_state_.dzb_memory;
  // This updates the nonlinear function of 'velocity' along with zA, which is
  // a delay of z2.
  ArrayX nonlinear_fun(num_channels_);
  ArrayX velocities = car_state_.z2_memory - car_state_.za_memory;
  OHCNonlinearFunction(velocities, &nonlinear_fun);
  // Here, zb_memory_ * nonlinear_fun is "undamping" delta r.
  ArrayX r = car_coeffs_.r1_coeffs + (car_state_.zb_memory * nonlinear_fun);
  car_state_.za_memory = car_state_.z2_memory;
  // Here we reduce the CAR state by r and rotate with the fixed cos/sin coeffs.
  ArrayX z1  = r * ((car_coeffs_.a0_coeffs * car_state_.z1_memory) -
                    (car_coeffs_.c0_coeffs * car_state_.z2_memory));
  car_state_.z2_memory = r *
    ((car_coeffs_.c0_coeffs * car_state_.z1_memory) +
     (car_coeffs_.a0_coeffs * car_state_.z2_memory));
  car_state_.zy_memory = car_coeffs_.h_coeffs * car_state_.z2_memory;
  // This section ripples the input-output path, to avoid delay...
  // It's the only part that doesn't get computed "in parallel":
  FPType in_out = input;
  for (int channel = 0; channel < num_channels_; channel++) {
    z1(channel) = z1(channel) + in_out;
    // This performs the ripple, and saves the final channel outputs in zy.
    in_out = car_state_.g_memory(channel) *
      (in_out + car_state_.zy_memory(channel));
    car_state_.zy_memory(channel) = in_out;
  }
  car_state_.z1_memory = z1;
}

// We start with a quadratic nonlinear function, and limit it via a
// rational function. This makes the result go to zero at high
// absolute velocities, so it will do nothing there.
void Ear::OHCNonlinearFunction(const ArrayX& velocities,
                               ArrayX* nonlinear_fun) const {
  *nonlinear_fun = (1 + ((velocities * car_coeffs_.velocity_scale) +
                         car_coeffs_.v_offset).square()).inverse();
}

// This step is a one sample-time update of the inner-hair-cell (IHC) model,
// including the detection nonlinearity and either one or two capacitor state
// variables.
void Ear::IHCStep(const ArrayX& car_out) {
  ArrayX ac_diff = car_out - ihc_state_.ac_coupler;
  ihc_state_.ac_coupler = ihc_state_.ac_coupler +
    (ihc_coeffs_.ac_coeff * ac_diff);
  if (ihc_coeffs_.just_half_wave_rectify) {
    ArrayX output(num_channels_);
    for (int channel = 0; channel < num_channels_; ++channel) {
      FPType a = (ac_diff(channel) > 0.0) ? ac_diff(channel) : 0.0;
      output(channel) = (a < 2) ? a : 2;
    }
    ihc_state_.ihc_out = output;
  } else {
    ArrayX conductance = CARFACDetect(ac_diff);
    if (ihc_coeffs_.one_capacitor) {
      ihc_state_.ihc_out = conductance * ihc_state_.cap1_voltage;
      ihc_state_.cap1_voltage = ihc_state_.cap1_voltage -
        (ihc_state_.ihc_out * ihc_coeffs_.out1_rate) +
        ((1 - ihc_state_.cap1_voltage) * ihc_coeffs_.in1_rate);
    } else {
      ihc_state_.ihc_out = conductance * ihc_state_.cap2_voltage;
      ihc_state_.cap1_voltage = ihc_state_.cap1_voltage -
        ((ihc_state_.cap1_voltage - ihc_state_.cap2_voltage)
         * ihc_coeffs_.out1_rate) + ((1 - ihc_state_.cap1_voltage) *
                                      ihc_coeffs_.in1_rate);
      ihc_state_.cap2_voltage = ihc_state_.cap2_voltage -
        (ihc_state_.ihc_out * ihc_coeffs_.out2_rate) +
        ((ihc_state_.cap1_voltage - ihc_state_.cap2_voltage)
         * ihc_coeffs_.in2_rate);
    }
    // Smooth the output twice using an LPF.
    ihc_state_.ihc_out *= ihc_coeffs_.output_gain;
    ihc_state_.lpf1_state += ihc_coeffs_.lpf_coeff *
      (ihc_state_.ihc_out - ihc_state_.lpf1_state);
    ihc_state_.lpf2_state += ihc_coeffs_.lpf_coeff *
      (ihc_state_.lpf1_state - ihc_state_.lpf2_state);
    ihc_state_.ihc_out = ihc_state_.lpf2_state - ihc_coeffs_.rest_output;
  }
  ihc_state_.ihc_accum += ihc_state_.ihc_out;
}

bool Ear::AGCStep(const ArrayX& ihc_out) {
  int stage = 0;
  int num_stages = agc_coeffs_[0].num_agc_stages;
  FPType detect_scale = agc_coeffs_[num_stages - 1].detect_scale;
  bool updated = AGCRecurse(stage, detect_scale * ihc_out);
  return updated;
}

bool Ear::AGCRecurse(const int stage, ArrayX agc_in) {
  bool updated = true;
  const AGCCoeffs& agc_coeffs = agc_coeffs_[stage];
  AGCState& agc_state = agc_state_[stage];
  // This is the decim factor for this stage, relative to input or prev. stage:
  int decim = agc_coeffs.decimation;
  // This is the decim phase of this stage (do work on phase 0 only):
  int decim_phase = agc_state.decim_phase + 1;
  decim_phase = decim_phase % decim;
  agc_state.decim_phase = decim_phase;
  // Here we accumulate input for this stage from the previous stage:
  agc_state.input_accum += agc_in;
  // We don't do anything if it's not the right decim_phase.
  if (decim_phase == 0) {
    // Now we do lots of work, at the decimated rate.
    // These are the decimated inputs for this stage, which will be further
    // decimated at the next stage.
    agc_in = agc_state.input_accum / decim;
    // This resets the accumulator.
    agc_state.input_accum = ArrayX::Zero(num_channels_);
    if (stage < (agc_coeffs_.size() - 1)) {
      // Now we recurse to evaluate the next stage(s).
      updated = AGCRecurse(stage + 1, agc_in);
      // Afterwards we add its output to this stage input, whether it updated or
      // not.
      agc_in += agc_coeffs.agc_stage_gain *
        agc_state_[stage + 1].agc_memory;
    }
    // This performs a first-order recursive smoothing filter update, in time.
    agc_state.agc_memory += agc_coeffs.agc_epsilon *
      (agc_in - agc_state.agc_memory);
    AGCSpatialSmooth(agc_coeffs_[stage], &agc_state.agc_memory);
    updated = true;
  } else {
    updated = false;
  }
  return updated;
}

void Ear::AGCSpatialSmooth(const AGCCoeffs& agc_coeffs,
                           ArrayX* stage_state) const {
  int num_iterations = agc_coeffs.agc_spatial_iterations;
  bool use_fir;
  use_fir = (num_iterations < 4) ? true : false;
  if (use_fir) {
    FPType fir_coeffs_left = agc_coeffs.agc_spatial_fir_left;
    FPType fir_coeffs_mid = agc_coeffs.agc_spatial_fir_mid;
    FPType fir_coeffs_right = agc_coeffs.agc_spatial_fir_right;
    ArrayX ss_tap1(num_channels_);
    ArrayX ss_tap2(num_channels_);
    ArrayX ss_tap3(num_channels_);
    ArrayX ss_tap4(num_channels_);
    int n_taps = agc_coeffs.agc_spatial_n_taps;
    // This initializes the first two taps of stage state, which are used for
    // both possible cases.
    ss_tap1(0) = (*stage_state)(0);
    ss_tap1.block(1, 0, num_channels_ - 1, 1) =
      stage_state->block(0, 0, num_channels_ - 1, 1);
    ss_tap2(num_channels_ - 1) = (*stage_state)(num_channels_ - 1);
    ss_tap2.block(0, 0, num_channels_ - 1, 1) =
      stage_state->block(1, 0, num_channels_ - 1, 1);
    switch (n_taps) {
      case 3:
        *stage_state = (fir_coeffs_left * ss_tap1) +
          (fir_coeffs_mid * *stage_state) + (fir_coeffs_right * ss_tap2);
        break;
      case 5:
        // Now we initialize last two taps of stage state, which are only used
        // for the 5-tap case.
        ss_tap3(0) = (*stage_state)(0);
        ss_tap3(1) = (*stage_state)(1);
        ss_tap3.block(2, 0, num_channels_ - 2, 1) =
          stage_state->block(0, 0, num_channels_ - 2, 1);
        ss_tap4(num_channels_ - 2) = (*stage_state)(num_channels_ - 1);
        ss_tap4(num_channels_ - 1) = (*stage_state)(num_channels_ - 2);
        ss_tap4.block(0, 0, num_channels_ - 2, 1) =
        stage_state->block(2, 0, num_channels_ - 2, 1);
        *stage_state = (fir_coeffs_left * (ss_tap3 + ss_tap1)) +
          (fir_coeffs_mid * *stage_state) +
          (fir_coeffs_right * (ss_tap2 + ss_tap4));
        break;
      default:
        assert(true && "Bad n_taps in AGCSpatialSmooth; should be 3 or 5.");
        break;
    }
  } else {
    AGCSmoothDoubleExponential(agc_coeffs.agc_pole_z1, agc_coeffs.agc_pole_z2,
                               stage_state);
  }
}

void Ear::AGCSmoothDoubleExponential(const FPType pole_z1,
                                     const FPType pole_z2,
                                     ArrayX* stage_state) const {
  int32_t num_points = stage_state->size();
  FPType input;
  FPType state = 0.0;
  // TODO(alexbrandmeyer): I'm assuming one dimensional input for now, but this
  // should be verified with Dick for the final version
  for (int i = num_points - 11; i < num_points; ++i) {
    input = (*stage_state)(i);
    state = state + (1 - pole_z1) * (input - state);
  }
  for (int i = num_points - 1; i > -1; --i) {
    input = (*stage_state)(i);
    state = state + (1 - pole_z2) * (input - state);
  }
  for (int i = 0; i < num_points; ++i) {
    input = (*stage_state)(i);
    state = state + (1 - pole_z1) * (input - state);
    (*stage_state)(i) = state;
  }
}

ArrayX Ear::StageGValue(const ArrayX& undamping) const {
  ArrayX r = car_coeffs_.r1_coeffs + car_coeffs_.zr_coeffs * undamping;
  return (1 - 2 * r * car_coeffs_.a0_coeffs + (r * r)) /
    (1 - 2 * r * car_coeffs_.a0_coeffs + car_coeffs_.h_coeffs * r *
     car_coeffs_.c0_coeffs + (r * r));
}