alexbrandmeyer@609: // alexbrandmeyer@609: // carfac.cc alexbrandmeyer@609: // CARFAC Open Source C++ Library alexbrandmeyer@609: // alexbrandmeyer@609: // Created by Alex Brandmeyer on 5/10/13. alexbrandmeyer@609: // alexbrandmeyer@609: // This C++ file is part of an implementation of Lyon's cochlear model: alexbrandmeyer@609: // "Cascade of Asymmetric Resonators with Fast-Acting Compression" alexbrandmeyer@609: // to supplement Lyon's upcoming book "Human and Machine Hearing" alexbrandmeyer@609: // alexbrandmeyer@609: // Licensed under the Apache License, Version 2.0 (the "License"); alexbrandmeyer@609: // you may not use this file except in compliance with the License. alexbrandmeyer@609: // You may obtain a copy of the License at alexbrandmeyer@609: // alexbrandmeyer@609: // http://www.apache.org/licenses/LICENSE-2.0 alexbrandmeyer@609: // alexbrandmeyer@609: // Unless required by applicable law or agreed to in writing, software alexbrandmeyer@609: // distributed under the License is distributed on an "AS IS" BASIS, alexbrandmeyer@609: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. alexbrandmeyer@609: // See the License for the specific language governing permissions and alexbrandmeyer@609: // limitations under the License. alexbrandmeyer@640: alexbrandmeyer@636: #include alexbrandmeyer@609: #include "carfac.h" alexbrandmeyer@636: using std::vector; ronw@625: alexbrandmeyer@626: void CARFAC::Design(const int n_ears, const FPType fs, alexbrandmeyer@626: const CARParams& car_params, const IHCParams& ihc_params, alexbrandmeyer@626: const AGCParams& agc_params) { alexbrandmeyer@609: n_ears_ = n_ears; alexbrandmeyer@609: fs_ = fs; alexbrandmeyer@611: ears_.resize(n_ears_); alexbrandmeyer@610: n_ch_ = 0; alexbrandmeyer@640: FPType pole_hz = car_params.first_pole_theta * fs / (2 * kPi); alexbrandmeyer@640: while (pole_hz > car_params.min_pole_hz) { alexbrandmeyer@626: ++n_ch_; alexbrandmeyer@640: pole_hz = pole_hz - car_params.erb_per_step * alexbrandmeyer@640: ERBHz(pole_hz, car_params.erb_break_freq, car_params.erb_q); alexbrandmeyer@609: } alexbrandmeyer@626: pole_freqs_.resize(n_ch_); alexbrandmeyer@640: pole_hz = car_params.first_pole_theta * fs / (2 * kPi); alexbrandmeyer@626: for (int ch = 0; ch < n_ch_; ++ch) { alexbrandmeyer@626: pole_freqs_(ch) = pole_hz; alexbrandmeyer@640: pole_hz = pole_hz - car_params.erb_per_step * alexbrandmeyer@640: ERBHz(pole_hz, car_params.erb_break_freq, car_params.erb_q); alexbrandmeyer@610: } alexbrandmeyer@626: max_channels_per_octave_ = log(2) / log(pole_freqs_(0) / pole_freqs_(1)); alexbrandmeyer@636: CARCoeffs car_coeffs; alexbrandmeyer@636: IHCCoeffs ihc_coeffs; alexbrandmeyer@636: std::vector agc_coeffs; alexbrandmeyer@636: DesignCARCoeffs(car_params, fs, pole_freqs_, &car_coeffs); alexbrandmeyer@636: DesignIHCCoeffs(ihc_params, fs, &ihc_coeffs); alexbrandmeyer@636: // This code initializes the coefficients for each of the AGC stages. alexbrandmeyer@636: DesignAGCCoeffs(agc_params, fs, &agc_coeffs); alexbrandmeyer@636: // Once we have the coefficient structure we can design the ears. alexbrandmeyer@626: for (auto& ear : ears_) { alexbrandmeyer@637: ear.InitEar(n_ch_, fs_, car_coeffs, ihc_coeffs, alexbrandmeyer@636: agc_coeffs); alexbrandmeyer@610: } alexbrandmeyer@609: } alexbrandmeyer@609: alexbrandmeyer@636: void CARFAC::RunSegment(const vector>& sound_data, alexbrandmeyer@626: const int32_t start, const int32_t length, alexbrandmeyer@636: const bool open_loop, CARFACOutput* seg_output) { alexbrandmeyer@610: // A nested loop structure is used to iterate through the individual samples alexbrandmeyer@610: // for each ear (audio channel). alexbrandmeyer@626: bool updated; // This variable is used by the AGC stage. alexbrandmeyer@636: for (int32_t i = 0; i < length; ++i) { alexbrandmeyer@636: for (int j = 0; j < n_ears_; ++j) { alexbrandmeyer@626: // First we create a reference to the current Ear object. alexbrandmeyer@626: Ear& ear = ears_[j]; alexbrandmeyer@610: // This stores the audio sample currently being processed. alexbrandmeyer@636: FPType input = sound_data[j][start+i]; alexbrandmeyer@610: // Now we apply the three stages of the model in sequence to the current alexbrandmeyer@610: // audio sample. alexbrandmeyer@637: ear.CARStep(input); alexbrandmeyer@637: ear.IHCStep(ear.car_out()); alexbrandmeyer@637: updated = ear.AGCStep(ear.ihc_out()); alexbrandmeyer@610: } alexbrandmeyer@637: seg_output->StoreOutput(ears_); alexbrandmeyer@636: if (updated) { alexbrandmeyer@636: if (n_ears_ > 1) { alexbrandmeyer@636: CrossCouple(); alexbrandmeyer@636: } alexbrandmeyer@636: if (! open_loop) { alexbrandmeyer@636: CloseAGCLoop(); alexbrandmeyer@636: } alexbrandmeyer@626: } alexbrandmeyer@610: } alexbrandmeyer@610: } alexbrandmeyer@610: alexbrandmeyer@610: void CARFAC::CrossCouple() { alexbrandmeyer@626: for (int stage = 0; stage < ears_[0].agc_nstages(); ++stage) { alexbrandmeyer@626: if (ears_[0].agc_decim_phase(stage) > 0) { alexbrandmeyer@610: break; alexbrandmeyer@610: } else { alexbrandmeyer@626: FPType mix_coeff = ears_[0].agc_mix_coeff(stage); alexbrandmeyer@610: if (mix_coeff > 0) { alexbrandmeyer@610: FloatArray stage_state; alexbrandmeyer@610: FloatArray this_stage_values = FloatArray::Zero(n_ch_); alexbrandmeyer@626: for (auto& ear : ears_) { alexbrandmeyer@626: stage_state = ear.agc_memory(stage); alexbrandmeyer@610: this_stage_values += stage_state; alexbrandmeyer@610: } alexbrandmeyer@610: this_stage_values /= n_ears_; alexbrandmeyer@626: for (auto& ear : ears_) { alexbrandmeyer@626: stage_state = ear.agc_memory(stage); alexbrandmeyer@626: ear.set_agc_memory(stage, stage_state + mix_coeff * alexbrandmeyer@626: (this_stage_values - stage_state)); alexbrandmeyer@610: } alexbrandmeyer@610: } alexbrandmeyer@609: } alexbrandmeyer@609: } alexbrandmeyer@609: } alexbrandmeyer@610: alexbrandmeyer@610: void CARFAC::CloseAGCLoop() { alexbrandmeyer@626: for (auto& ear: ears_) { alexbrandmeyer@626: FloatArray undamping = 1 - ear.agc_memory(0); alexbrandmeyer@610: // This updates the target stage gain for the new damping. alexbrandmeyer@626: ear.set_dzb_memory((ear.zr_coeffs() * undamping - ear.zb_memory()) / alexbrandmeyer@626: ear.agc_decimation(0)); alexbrandmeyer@626: ear.set_dg_memory((ear.StageGValue(undamping) - ear.g_memory()) / alexbrandmeyer@626: ear.agc_decimation(0)); alexbrandmeyer@610: } alexbrandmeyer@636: } alexbrandmeyer@636: alexbrandmeyer@636: void CARFAC::DesignCARCoeffs(const CARParams& car_params, const FPType fs, alexbrandmeyer@636: const FloatArray& pole_freqs, alexbrandmeyer@636: CARCoeffs* car_coeffs) { alexbrandmeyer@636: n_ch_ = pole_freqs.size(); alexbrandmeyer@640: car_coeffs->velocity_scale = car_params.velocity_scale; alexbrandmeyer@640: car_coeffs->v_offset = car_params.v_offset; alexbrandmeyer@640: car_coeffs->r1_coeffs.resize(n_ch_); alexbrandmeyer@640: car_coeffs->a0_coeffs.resize(n_ch_); alexbrandmeyer@640: car_coeffs->c0_coeffs.resize(n_ch_); alexbrandmeyer@640: car_coeffs->h_coeffs.resize(n_ch_); alexbrandmeyer@640: car_coeffs->g0_coeffs.resize(n_ch_); alexbrandmeyer@640: FPType f = car_params.zero_ratio * car_params.zero_ratio - 1.0; alexbrandmeyer@637: FloatArray theta = pole_freqs * ((2.0 * kPi) / fs); alexbrandmeyer@640: car_coeffs->c0_coeffs = theta.sin(); alexbrandmeyer@640: car_coeffs->a0_coeffs = theta.cos(); alexbrandmeyer@640: FPType ff = car_params.high_f_damping_compression; alexbrandmeyer@637: FloatArray x = theta / kPi; alexbrandmeyer@640: car_coeffs->zr_coeffs = kPi * (x - (ff * (x*x*x))); alexbrandmeyer@640: FPType max_zeta = car_params.max_zeta; alexbrandmeyer@640: FPType min_zeta = car_params.min_zeta; alexbrandmeyer@640: car_coeffs->r1_coeffs = (1.0 - (car_coeffs->zr_coeffs * max_zeta)); alexbrandmeyer@636: FloatArray erb_freqs(n_ch_); alexbrandmeyer@636: for (int ch=0; ch < n_ch_; ++ch) { alexbrandmeyer@640: erb_freqs(ch) = ERBHz(pole_freqs(ch), car_params.erb_break_freq, alexbrandmeyer@640: car_params.erb_q); alexbrandmeyer@636: } alexbrandmeyer@636: FloatArray min_zetas = min_zeta + (0.25 * ((erb_freqs / pole_freqs) - alexbrandmeyer@636: min_zeta)); alexbrandmeyer@640: car_coeffs->zr_coeffs *= max_zeta - min_zetas; alexbrandmeyer@640: car_coeffs->h_coeffs = car_coeffs->c0_coeffs * f; alexbrandmeyer@636: FloatArray relative_undamping = FloatArray::Ones(n_ch_); alexbrandmeyer@640: FloatArray r = car_coeffs->r1_coeffs + (car_coeffs->zr_coeffs * alexbrandmeyer@636: relative_undamping); alexbrandmeyer@640: car_coeffs->g0_coeffs = (1.0 - (2.0 * r * car_coeffs->a0_coeffs) + (r*r)) / alexbrandmeyer@640: (1 - (2 * r * car_coeffs->a0_coeffs) + alexbrandmeyer@640: (car_coeffs->h_coeffs * r * car_coeffs->c0_coeffs) + (r*r)); alexbrandmeyer@636: } alexbrandmeyer@636: alexbrandmeyer@636: void CARFAC::DesignIHCCoeffs(const IHCParams& ihc_params, const FPType fs, alexbrandmeyer@636: IHCCoeffs* ihc_coeffs) { alexbrandmeyer@640: if (ihc_params.just_half_wave_rectify) { alexbrandmeyer@640: ihc_coeffs->just_half_wave_rectify = ihc_params.just_half_wave_rectify; alexbrandmeyer@636: } else { alexbrandmeyer@636: // This section calculates conductance values using two pre-defined scalars. alexbrandmeyer@636: FloatArray x(1); alexbrandmeyer@636: FPType conduct_at_10, conduct_at_0; alexbrandmeyer@636: x(0) = 10.0; alexbrandmeyer@636: x = CARFACDetect(x); alexbrandmeyer@636: conduct_at_10 = x(0); alexbrandmeyer@636: x(0) = 0.0; alexbrandmeyer@636: x = CARFACDetect(x); alexbrandmeyer@636: conduct_at_0 = x(0); alexbrandmeyer@640: if (ihc_params.one_capacitor) { alexbrandmeyer@636: FPType ro = 1 / conduct_at_10 ; alexbrandmeyer@640: FPType c = ihc_params.tau1_out / ro; alexbrandmeyer@640: FPType ri = ihc_params.tau1_in / c; alexbrandmeyer@636: FPType saturation_output = 1 / ((2 * ro) + ri); alexbrandmeyer@636: FPType r0 = 1 / conduct_at_0; alexbrandmeyer@636: FPType current = 1 / (ri + r0); alexbrandmeyer@640: ihc_coeffs->cap1_voltage = 1 - (current * ri); alexbrandmeyer@640: ihc_coeffs->just_half_wave_rectify = false; alexbrandmeyer@640: ihc_coeffs->lpf_coeff = 1 - exp( -1 / (ihc_params.tau_lpf * fs)); alexbrandmeyer@640: ihc_coeffs->out1_rate = ro / (ihc_params.tau1_out * fs); alexbrandmeyer@640: ihc_coeffs->in1_rate = 1 / (ihc_params.tau1_in * fs); alexbrandmeyer@640: ihc_coeffs->one_capacitor = ihc_params.one_capacitor; alexbrandmeyer@640: ihc_coeffs->output_gain = 1 / (saturation_output - current); alexbrandmeyer@640: ihc_coeffs->rest_output = current / (saturation_output - current); alexbrandmeyer@640: ihc_coeffs->rest_cap1 = ihc_coeffs->cap1_voltage; alexbrandmeyer@636: } else { alexbrandmeyer@636: FPType ro = 1 / conduct_at_10; alexbrandmeyer@640: FPType c2 = ihc_params.tau2_out / ro; alexbrandmeyer@640: FPType r2 = ihc_params.tau2_in / c2; alexbrandmeyer@640: FPType c1 = ihc_params.tau1_out / r2; alexbrandmeyer@640: FPType r1 = ihc_params.tau1_in / c1; alexbrandmeyer@636: FPType saturation_output = 1 / (2 * ro + r2 + r1); alexbrandmeyer@636: FPType r0 = 1 / conduct_at_0; alexbrandmeyer@636: FPType current = 1 / (r1 + r2 + r0); alexbrandmeyer@640: ihc_coeffs->cap1_voltage = 1 - (current * r1); alexbrandmeyer@640: ihc_coeffs->cap2_voltage = ihc_coeffs->cap1_voltage - (current * r2); alexbrandmeyer@640: ihc_coeffs->just_half_wave_rectify = false; alexbrandmeyer@640: ihc_coeffs->lpf_coeff = 1 - exp(-1 / (ihc_params.tau_lpf * fs)); alexbrandmeyer@640: ihc_coeffs->out1_rate = 1 / (ihc_params.tau1_out * fs); alexbrandmeyer@640: ihc_coeffs->in1_rate = 1 / (ihc_params.tau1_in * fs); alexbrandmeyer@640: ihc_coeffs->out2_rate = ro / (ihc_params.tau2_out * fs); alexbrandmeyer@640: ihc_coeffs->in2_rate = 1 / (ihc_params.tau2_in * fs); alexbrandmeyer@640: ihc_coeffs->one_capacitor = ihc_params.one_capacitor; alexbrandmeyer@640: ihc_coeffs->output_gain = 1 / (saturation_output - current); alexbrandmeyer@640: ihc_coeffs->rest_output = current / (saturation_output - current); alexbrandmeyer@640: ihc_coeffs->rest_cap1 = ihc_coeffs->cap1_voltage; alexbrandmeyer@640: ihc_coeffs->rest_cap2 = ihc_coeffs->cap2_voltage; alexbrandmeyer@636: } alexbrandmeyer@636: } alexbrandmeyer@640: ihc_coeffs->ac_coeff = 2 * kPi * ihc_params.ac_corner_hz / fs; alexbrandmeyer@636: } alexbrandmeyer@636: alexbrandmeyer@636: void CARFAC::DesignAGCCoeffs(const AGCParams& agc_params, const FPType fs, alexbrandmeyer@636: vector* agc_coeffs) { alexbrandmeyer@640: agc_coeffs->resize(agc_params.n_stages); alexbrandmeyer@636: FPType previous_stage_gain = 0.0; alexbrandmeyer@636: FPType decim = 1.0; alexbrandmeyer@640: for (int stage = 0; stage < agc_params.n_stages; ++stage) { alexbrandmeyer@636: AGCCoeffs& agc_coeff = agc_coeffs->at(stage); alexbrandmeyer@640: agc_coeff.n_agc_stages = agc_params.n_stages; alexbrandmeyer@640: agc_coeff.agc_stage_gain = agc_params.agc_stage_gain; alexbrandmeyer@640: vector agc1_scales = agc_params.agc1_scales; alexbrandmeyer@640: vector agc2_scales = agc_params.agc2_scales; alexbrandmeyer@640: vector time_constants = agc_params.time_constants; alexbrandmeyer@640: FPType mix_coeff = agc_params.agc_mix_coeff; alexbrandmeyer@640: agc_coeff.decimation = agc_params.decimation[stage]; alexbrandmeyer@636: FPType total_dc_gain = previous_stage_gain; alexbrandmeyer@636: // Here we calculate the parameters for the current stage. alexbrandmeyer@636: FPType tau = time_constants[stage]; alexbrandmeyer@640: agc_coeff.decim = decim; alexbrandmeyer@640: agc_coeff.decim *= agc_coeff.decimation; alexbrandmeyer@640: agc_coeff.agc_epsilon = 1 - exp((-1 * agc_coeff.decim) / (tau * fs)); alexbrandmeyer@640: FPType n_times = tau * (fs / agc_coeff.decim); alexbrandmeyer@636: FPType delay = (agc2_scales[stage] - agc1_scales[stage]) / n_times; alexbrandmeyer@636: FPType spread_sq = (pow(agc1_scales[stage], 2) + alexbrandmeyer@636: pow(agc2_scales[stage], 2)) / n_times; alexbrandmeyer@636: FPType u = 1 + (1 / spread_sq); alexbrandmeyer@636: FPType p = u - sqrt(pow(u, 2) - 1); alexbrandmeyer@636: FPType dp = delay * (1 - (2 * p) + (p*p)) / 2; alexbrandmeyer@640: agc_coeff.agc_pole_z1 = p - dp; alexbrandmeyer@640: agc_coeff.agc_pole_z2 = p + dp; alexbrandmeyer@636: int n_taps = 0; alexbrandmeyer@636: bool fir_ok = false; alexbrandmeyer@636: int n_iterations = 1; alexbrandmeyer@636: // This section initializes the FIR coeffs settings at each stage. alexbrandmeyer@636: FPType fir_left, fir_mid, fir_right; alexbrandmeyer@636: while (! fir_ok) { alexbrandmeyer@636: switch (n_taps) { alexbrandmeyer@636: case 0: alexbrandmeyer@636: n_taps = 3; alexbrandmeyer@636: break; alexbrandmeyer@636: case 3: alexbrandmeyer@636: n_taps = 5; alexbrandmeyer@636: break; alexbrandmeyer@636: case 5: alexbrandmeyer@636: n_iterations++; alexbrandmeyer@636: assert(n_iterations < 16 && alexbrandmeyer@636: "Too many iterations needed in AGC spatial smoothing."); alexbrandmeyer@636: break; alexbrandmeyer@636: default: alexbrandmeyer@636: assert(true && "Bad n_taps; should be 3 or 5."); alexbrandmeyer@636: break; alexbrandmeyer@636: } alexbrandmeyer@636: // The smoothing function is a space-domain smoothing, but it considered alexbrandmeyer@636: // here by analogy to time-domain smoothing, which is why its potential alexbrandmeyer@636: // off-centeredness is called a delay. Since it's a smoothing filter, it alexbrandmeyer@636: // is also analogous to a discrete probability distribution (a p.m.f.), alexbrandmeyer@636: // with mean corresponding to delay and variance corresponding to squared alexbrandmeyer@636: // spatial spread (in samples, or channels, and the square thereof, alexbrandmeyer@636: // respecitively). Here we design a filter implementation's coefficient alexbrandmeyer@636: // via the method of moment matching, so we get the intended delay and alexbrandmeyer@636: // spread, and don't worry too much about the shape of the distribution, alexbrandmeyer@636: // which will be some kind of blob not too far from Gaussian if we run alexbrandmeyer@636: // several FIR iterations. alexbrandmeyer@636: FPType delay_variance = spread_sq / n_iterations; alexbrandmeyer@636: FPType mean_delay = delay / n_iterations; alexbrandmeyer@636: FPType a, b; alexbrandmeyer@636: switch (n_taps) { alexbrandmeyer@636: case 3: alexbrandmeyer@636: a = (delay_variance + (mean_delay*mean_delay) - mean_delay) / 2.0; alexbrandmeyer@636: b = (delay_variance + (mean_delay*mean_delay) + mean_delay) / 2.0; alexbrandmeyer@636: fir_left = a; alexbrandmeyer@636: fir_mid = 1 - a - b; alexbrandmeyer@636: fir_right = b; alexbrandmeyer@636: fir_ok = fir_mid >= 0.2 ? true : false; alexbrandmeyer@636: break; alexbrandmeyer@636: case 5: alexbrandmeyer@636: a = (((delay_variance + (mean_delay*mean_delay)) * 2.0/5.0) - alexbrandmeyer@636: (mean_delay * 2.0/3.0)) / 2.0; alexbrandmeyer@636: b = (((delay_variance + (mean_delay*mean_delay)) * 2.0/5.0) + alexbrandmeyer@636: (mean_delay * 2.0/3.0)) / 2.0; alexbrandmeyer@636: fir_left = a / 2.0; alexbrandmeyer@636: fir_mid = 1 - a - b; alexbrandmeyer@636: fir_right = b / 2.0; alexbrandmeyer@636: fir_ok = fir_mid >= 0.1 ? true : false; alexbrandmeyer@636: break; alexbrandmeyer@636: default: alexbrandmeyer@636: assert(true && "Bad n_taps; should be 3 or 5."); alexbrandmeyer@636: break; alexbrandmeyer@636: } alexbrandmeyer@636: } alexbrandmeyer@636: // Once we have the FIR design for this stage we can assign it to the alexbrandmeyer@636: // appropriate data members. alexbrandmeyer@640: agc_coeff.agc_spatial_iterations = n_iterations; alexbrandmeyer@640: agc_coeff.agc_spatial_n_taps = n_taps; alexbrandmeyer@640: agc_coeff.agc_spatial_fir_left = fir_left; alexbrandmeyer@640: agc_coeff.agc_spatial_fir_mid = fir_mid; alexbrandmeyer@640: agc_coeff.agc_spatial_fir_right = fir_right; alexbrandmeyer@640: total_dc_gain += pow(agc_coeff.agc_stage_gain, stage); alexbrandmeyer@640: agc_coeff.agc_mix_coeffs = stage == 0 ? 0 : mix_coeff / alexbrandmeyer@640: (tau * (fs / agc_coeff.decim)); alexbrandmeyer@640: agc_coeff.agc_gain = total_dc_gain; alexbrandmeyer@640: agc_coeff.detect_scale = 1 / total_dc_gain; alexbrandmeyer@640: previous_stage_gain = agc_coeff.agc_gain; alexbrandmeyer@640: decim = agc_coeff.decim; alexbrandmeyer@636: } ronw@641: }