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 } |