annotate carfac/carfac.cc @ 626:586b0677aae8

Fourth revision of Alex Brandmeyer's C++ implementation. Fixed more style issues, changed AGC structures to vectors, replaced FloatArray2d with vector<FloatArray>, implemented first tests using GTest to verify coefficients and monaural output against Matlab values (stored in aimc/carfac/test_data/). To run tests, change the path stored in carfac_test.h in TEST_SRC_DIR. Added CARFAC_GenerateTestData to the Matlab branch, fixed stage indexing in CARFAC_Cross_Couple.m to reflect changes in AGCCoeffs and AGCState structs.
author alexbrandmeyer
date Wed, 22 May 2013 21:30:02 +0000
parents f72ad5807857
children 27f2d9b76075
rev   line source
alexbrandmeyer@609 1 //
alexbrandmeyer@609 2 // carfac.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
alexbrandmeyer@609 23 #include "carfac.h"
ronw@625 24
alexbrandmeyer@626 25 void CARFAC::Design(const int n_ears, const FPType fs,
alexbrandmeyer@626 26 const CARParams& car_params, const IHCParams& ihc_params,
alexbrandmeyer@626 27 const AGCParams& agc_params) {
alexbrandmeyer@609 28 n_ears_ = n_ears;
alexbrandmeyer@609 29 fs_ = fs;
alexbrandmeyer@611 30 ears_.resize(n_ears_);
alexbrandmeyer@610 31
alexbrandmeyer@610 32 n_ch_ = 0;
alexbrandmeyer@610 33 FPType pole_hz = car_params.first_pole_theta_ * fs / (2 * PI);
alexbrandmeyer@610 34 while (pole_hz > car_params.min_pole_hz_) {
alexbrandmeyer@626 35 ++n_ch_;
alexbrandmeyer@610 36 pole_hz = pole_hz - car_params.erb_per_step_ *
alexbrandmeyer@610 37 ERBHz(pole_hz, car_params.erb_break_freq_, car_params.erb_q_);
alexbrandmeyer@609 38 }
alexbrandmeyer@626 39 pole_freqs_.resize(n_ch_);
alexbrandmeyer@610 40 pole_hz = car_params.first_pole_theta_ * fs / (2 * PI);
alexbrandmeyer@626 41 for (int ch = 0; ch < n_ch_; ++ch) {
alexbrandmeyer@626 42 pole_freqs_(ch) = pole_hz;
alexbrandmeyer@610 43 pole_hz = pole_hz - car_params.erb_per_step_ *
alexbrandmeyer@610 44 ERBHz(pole_hz, car_params.erb_break_freq_, car_params.erb_q_);
alexbrandmeyer@610 45 }
alexbrandmeyer@626 46 max_channels_per_octave_ = log(2) / log(pole_freqs_(0) / pole_freqs_(1));
alexbrandmeyer@610 47 // Once we have the basic information about the pole frequencies and the
alexbrandmeyer@610 48 // number of channels, we initialize the ear(s).
alexbrandmeyer@626 49 for (auto& ear : ears_) {
alexbrandmeyer@626 50 ear.InitEar(n_ch_, fs_, pole_freqs_, car_params, ihc_params,
alexbrandmeyer@626 51 agc_params);
alexbrandmeyer@610 52 }
alexbrandmeyer@609 53 }
alexbrandmeyer@609 54
alexbrandmeyer@626 55 CARFACOutput CARFAC::Run(const std::vector<std::vector<float>>& sound_data) {
alexbrandmeyer@610 56 // We initialize one output object to store the final output.
alexbrandmeyer@626 57 CARFACOutput seg_output;
alexbrandmeyer@626 58 int n_audio_channels = sound_data.size();
alexbrandmeyer@611 59 int32_t seg_len = 441; // We use a fixed segment length for now.
alexbrandmeyer@626 60 int32_t n_timepoints = sound_data[0].size();
alexbrandmeyer@611 61 int32_t n_segs = ceil((n_timepoints * 1.0) / seg_len);
alexbrandmeyer@626 62 seg_output.InitOutput(n_audio_channels, n_ch_, n_timepoints);
alexbrandmeyer@610 63 // These values store the start and endpoints for each segment
alexbrandmeyer@611 64 int32_t start;
alexbrandmeyer@611 65 int32_t length = seg_len;
alexbrandmeyer@610 66 // This section loops over the individual audio segments.
alexbrandmeyer@626 67 for (int32_t i = 0; i < n_segs; ++i) {
alexbrandmeyer@610 68 // For each segment we calculate the start point and the segment length.
alexbrandmeyer@610 69 start = i * seg_len;
alexbrandmeyer@610 70 if (i == n_segs - 1) {
alexbrandmeyer@610 71 // The last segment can be shorter than the rest.
alexbrandmeyer@610 72 length = n_timepoints - start;
alexbrandmeyer@609 73 }
alexbrandmeyer@626 74 std::vector<std::vector<float>> segment_data;
alexbrandmeyer@626 75 segment_data.resize(n_audio_channels);
alexbrandmeyer@626 76 for (int channel = 0; channel < n_audio_channels; ++channel) {
alexbrandmeyer@626 77 segment_data[channel].resize(length);
alexbrandmeyer@626 78 for (int32_t timepoint = 0; timepoint < length; ++timepoint) {
alexbrandmeyer@626 79 segment_data[channel][timepoint] =
alexbrandmeyer@626 80 sound_data[channel][start + timepoint];
alexbrandmeyer@626 81 }
alexbrandmeyer@626 82 }
alexbrandmeyer@610 83 // Once we've determined the start point and segment length, we run the
alexbrandmeyer@610 84 // CARFAC model on the current segment.
alexbrandmeyer@626 85 RunSegment(segment_data, start,
alexbrandmeyer@626 86 length, &seg_output, true);
alexbrandmeyer@610 87 // Afterwards we merge the output for the current segment into the larger
alexbrandmeyer@610 88 // output structure for the entire audio file.
alexbrandmeyer@609 89 }
alexbrandmeyer@626 90 return seg_output;
alexbrandmeyer@609 91 }
alexbrandmeyer@609 92
alexbrandmeyer@626 93 void CARFAC::RunSegment(const std::vector<std::vector<float>>& sound_data,
alexbrandmeyer@626 94 const int32_t start, const int32_t length,
alexbrandmeyer@626 95 CARFACOutput* seg_output, const bool open_loop) {
alexbrandmeyer@626 96 // The number of ears is equal to the number of audio channels. This could
alexbrandmeyer@626 97 // potentially be removed since we already know the n_ears_ during the design
alexbrandmeyer@626 98 // stage.
alexbrandmeyer@626 99 int n_ears = sound_data.size();
alexbrandmeyer@610 100 // The number of timepoints is determined from the length of the audio
alexbrandmeyer@610 101 // segment.
alexbrandmeyer@626 102 int32_t n_timepoints = sound_data[0].size();
alexbrandmeyer@610 103 // A nested loop structure is used to iterate through the individual samples
alexbrandmeyer@610 104 // for each ear (audio channel).
alexbrandmeyer@611 105 FloatArray car_out(n_ch_);
alexbrandmeyer@611 106 FloatArray ihc_out(n_ch_);
alexbrandmeyer@626 107 bool updated; // This variable is used by the AGC stage.
alexbrandmeyer@626 108 for (int32_t i = 0; i < n_timepoints; ++i) {
alexbrandmeyer@626 109 for (int j = 0; j < n_ears; ++j) {
alexbrandmeyer@626 110 // First we create a reference to the current Ear object.
alexbrandmeyer@626 111 Ear& ear = ears_[j];
alexbrandmeyer@610 112 // This stores the audio sample currently being processed.
alexbrandmeyer@626 113 FPType input = sound_data[j][i];
alexbrandmeyer@610 114 // Now we apply the three stages of the model in sequence to the current
alexbrandmeyer@610 115 // audio sample.
alexbrandmeyer@626 116 ear.CARStep(input, &car_out);
alexbrandmeyer@626 117 ear.IHCStep(car_out, &ihc_out);
alexbrandmeyer@626 118 updated = ear.AGCStep(ihc_out);
alexbrandmeyer@610 119 // These lines assign the output of the model for the current sample
alexbrandmeyer@610 120 // to the appropriate data members of the current ear in the output
alexbrandmeyer@610 121 // object.
alexbrandmeyer@626 122 seg_output->StoreNAPOutput(start + i, j, ihc_out);
alexbrandmeyer@626 123 seg_output->StoreBMOutput(start + i, j, car_out);
alexbrandmeyer@626 124 seg_output->StoreOHCOutput(start + i, j, ear.za_memory());
alexbrandmeyer@626 125 seg_output->StoreAGCOutput(start + i, j, ear.zb_memory());
alexbrandmeyer@610 126 }
alexbrandmeyer@626 127 if (updated && n_ears > 1) {
alexbrandmeyer@626 128 CrossCouple();
alexbrandmeyer@626 129 }
alexbrandmeyer@626 130 if (! open_loop) {
alexbrandmeyer@626 131 CloseAGCLoop();
alexbrandmeyer@626 132 }
alexbrandmeyer@610 133 }
alexbrandmeyer@610 134 }
alexbrandmeyer@610 135
alexbrandmeyer@610 136 void CARFAC::CrossCouple() {
alexbrandmeyer@626 137 for (int stage = 0; stage < ears_[0].agc_nstages(); ++stage) {
alexbrandmeyer@626 138 if (ears_[0].agc_decim_phase(stage) > 0) {
alexbrandmeyer@610 139 break;
alexbrandmeyer@610 140 } else {
alexbrandmeyer@626 141 FPType mix_coeff = ears_[0].agc_mix_coeff(stage);
alexbrandmeyer@610 142 if (mix_coeff > 0) {
alexbrandmeyer@610 143 FloatArray stage_state;
alexbrandmeyer@610 144 FloatArray this_stage_values = FloatArray::Zero(n_ch_);
alexbrandmeyer@626 145 for (auto& ear : ears_) {
alexbrandmeyer@626 146 stage_state = ear.agc_memory(stage);
alexbrandmeyer@610 147 this_stage_values += stage_state;
alexbrandmeyer@610 148 }
alexbrandmeyer@610 149 this_stage_values /= n_ears_;
alexbrandmeyer@626 150 for (auto& ear : ears_) {
alexbrandmeyer@626 151 stage_state = ear.agc_memory(stage);
alexbrandmeyer@626 152 ear.set_agc_memory(stage, stage_state + mix_coeff *
alexbrandmeyer@626 153 (this_stage_values - stage_state));
alexbrandmeyer@610 154 }
alexbrandmeyer@610 155 }
alexbrandmeyer@609 156 }
alexbrandmeyer@609 157 }
alexbrandmeyer@609 158 }
alexbrandmeyer@610 159
alexbrandmeyer@610 160 void CARFAC::CloseAGCLoop() {
alexbrandmeyer@626 161 for (auto& ear: ears_) {
alexbrandmeyer@626 162 FloatArray undamping = 1 - ear.agc_memory(0);
alexbrandmeyer@610 163 // This updates the target stage gain for the new damping.
alexbrandmeyer@626 164 ear.set_dzb_memory((ear.zr_coeffs() * undamping - ear.zb_memory()) /
alexbrandmeyer@626 165 ear.agc_decimation(0));
alexbrandmeyer@626 166 ear.set_dg_memory((ear.StageGValue(undamping) - ear.g_memory()) /
alexbrandmeyer@626 167 ear.agc_decimation(0));
alexbrandmeyer@610 168 }
alexbrandmeyer@626 169 }