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"
|
alexbrandmeyer@609
|
24 void CARFAC::Design(int n_ears, long fs, CARParams car_params,
|
alexbrandmeyer@610
|
25 IHCParams ihc_params, AGCParams agc_params) {
|
alexbrandmeyer@609
|
26 n_ears_ = n_ears;
|
alexbrandmeyer@609
|
27 fs_ = fs;
|
alexbrandmeyer@609
|
28 ears_ = new Ear[n_ears_];
|
alexbrandmeyer@610
|
29
|
alexbrandmeyer@610
|
30 n_ch_ = 0;
|
alexbrandmeyer@610
|
31 FPType pole_hz = car_params.first_pole_theta_ * fs / (2 * PI);
|
alexbrandmeyer@610
|
32 while (pole_hz > car_params.min_pole_hz_) {
|
alexbrandmeyer@610
|
33 n_ch_++;
|
alexbrandmeyer@610
|
34 pole_hz = pole_hz - car_params.erb_per_step_ *
|
alexbrandmeyer@610
|
35 ERBHz(pole_hz, car_params.erb_break_freq_, car_params.erb_q_);
|
alexbrandmeyer@609
|
36 }
|
alexbrandmeyer@610
|
37 FloatArray pole_freqs(n_ch_);
|
alexbrandmeyer@610
|
38 pole_hz = car_params.first_pole_theta_ * fs / (2 * PI);
|
alexbrandmeyer@610
|
39 for(int ch=0;ch < n_ch_; ch++) {
|
alexbrandmeyer@610
|
40 pole_freqs(ch) = pole_hz;
|
alexbrandmeyer@610
|
41 pole_hz = pole_hz - car_params.erb_per_step_ *
|
alexbrandmeyer@610
|
42 ERBHz(pole_hz, car_params.erb_break_freq_, car_params.erb_q_);
|
alexbrandmeyer@610
|
43 }
|
alexbrandmeyer@610
|
44 max_channels_per_octave_ = log(2) / log(pole_freqs(0) / pole_freqs(1));
|
alexbrandmeyer@610
|
45 // Once we have the basic information about the pole frequencies and the
|
alexbrandmeyer@610
|
46 // number of channels, we initialize the ear(s).
|
alexbrandmeyer@610
|
47 for (int i = 0; i < n_ears_; i++) {
|
alexbrandmeyer@610
|
48 ears_[i].InitEar(n_ch_, fs_, pole_freqs, car_params, ihc_params, agc_params);
|
alexbrandmeyer@610
|
49 }
|
alexbrandmeyer@609
|
50 }
|
alexbrandmeyer@609
|
51
|
alexbrandmeyer@610
|
52 CARFACOutput CARFAC::Run(FloatArray2d sound_data, bool open_loop) {
|
alexbrandmeyer@610
|
53 // We initialize one output object to store the final output.
|
alexbrandmeyer@609
|
54 CARFACOutput *output = new CARFACOutput();
|
alexbrandmeyer@610
|
55 // A second object is used to store the output for the individual segments.
|
alexbrandmeyer@609
|
56 CARFACOutput *seg_output = new CARFACOutput();
|
alexbrandmeyer@609
|
57 int n_audio_channels = int(sound_data.cols());
|
alexbrandmeyer@610
|
58 long seg_len = 441; // We use a fixed segment length for now.
|
alexbrandmeyer@609
|
59 long n_timepoints = sound_data.rows();
|
alexbrandmeyer@610
|
60 long n_segs = ceil((n_timepoints * 1.0) / seg_len);
|
alexbrandmeyer@609
|
61 output->InitOutput(n_audio_channels, n_ch_, n_timepoints);
|
alexbrandmeyer@609
|
62 seg_output->InitOutput(n_audio_channels, n_ch_, seg_len);
|
alexbrandmeyer@610
|
63 // These values store the start and endpoints for each segment
|
alexbrandmeyer@610
|
64 long start;
|
alexbrandmeyer@610
|
65 long length = seg_len;
|
alexbrandmeyer@610
|
66 // This section loops over the individual audio segments.
|
alexbrandmeyer@610
|
67 for (long 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@610
|
74 // Once we've determined the start point and segment length, we run the
|
alexbrandmeyer@610
|
75 // CARFAC model on the current segment.
|
alexbrandmeyer@610
|
76 RunSegment(sound_data.block(start, 0, length, n_audio_channels),
|
alexbrandmeyer@610
|
77 seg_output, open_loop);
|
alexbrandmeyer@610
|
78 // Afterwards we merge the output for the current segment into the larger
|
alexbrandmeyer@610
|
79 // output structure for the entire audio file.
|
alexbrandmeyer@609
|
80 output->MergeOutput(*seg_output, start, length);
|
alexbrandmeyer@609
|
81 }
|
alexbrandmeyer@609
|
82 return *output;
|
alexbrandmeyer@609
|
83 }
|
alexbrandmeyer@609
|
84
|
alexbrandmeyer@610
|
85 void CARFAC::RunSegment(FloatArray2d sound_data, CARFACOutput *seg_output,
|
alexbrandmeyer@610
|
86 bool open_loop) {
|
alexbrandmeyer@610
|
87 // The number of timepoints is determined from the length of the audio
|
alexbrandmeyer@610
|
88 // segment.
|
alexbrandmeyer@609
|
89 long n_timepoints = sound_data.rows();
|
alexbrandmeyer@610
|
90 // The number of ears is equal to the number of audio channels. This could
|
alexbrandmeyer@610
|
91 // potentially be removed since we already know the n_ears_ during the design
|
alexbrandmeyer@610
|
92 // stage.
|
alexbrandmeyer@609
|
93 int n_ears = int(sound_data.cols());
|
alexbrandmeyer@610
|
94 // A nested loop structure is used to iterate through the individual samples
|
alexbrandmeyer@610
|
95 // for each ear (audio channel).
|
alexbrandmeyer@610
|
96 bool updated; // This variable is used by the AGC stage.
|
alexbrandmeyer@610
|
97 for (long i = 0; i < n_timepoints; i++) {
|
alexbrandmeyer@610
|
98 for (int j = 0; j < n_ears; j++) {
|
alexbrandmeyer@610
|
99 // This stores the audio sample currently being processed.
|
alexbrandmeyer@610
|
100 FPType input = sound_data(i, j);
|
alexbrandmeyer@610
|
101 // Now we apply the three stages of the model in sequence to the current
|
alexbrandmeyer@610
|
102 // audio sample.
|
alexbrandmeyer@609
|
103 FloatArray car_out = ears_[j].CARStep(input);
|
alexbrandmeyer@609
|
104 FloatArray ihc_out = ears_[j].IHCStep(car_out);
|
alexbrandmeyer@610
|
105 updated = ears_[j].AGCStep(ihc_out);
|
alexbrandmeyer@610
|
106 // These lines assign the output of the model for the current sample
|
alexbrandmeyer@610
|
107 // to the appropriate data members of the current ear in the output
|
alexbrandmeyer@610
|
108 // object.
|
alexbrandmeyer@610
|
109 seg_output->ears_[j].nap_.block(0, i, n_ch_, 1) = ihc_out;
|
alexbrandmeyer@610
|
110 // TODO alexbrandmeyer: Check with Dick to determine the C++ strategy for
|
alexbrandmeyer@610
|
111 // storing optional output structures.
|
alexbrandmeyer@610
|
112 seg_output->ears_[j].bm_.block(0, i, n_ch_, 1) = car_out;
|
alexbrandmeyer@610
|
113 seg_output->ears_[j].ohc_.block(0, 1, n_ch_, 1) =
|
alexbrandmeyer@610
|
114 ears_[j].ReturnZAMemory();
|
alexbrandmeyer@610
|
115 seg_output->ears_[j].agc_.block(0, i, n_ch_, 1) =
|
alexbrandmeyer@610
|
116 ears_[j].ReturnZBMemory();
|
alexbrandmeyer@610
|
117 }
|
alexbrandmeyer@610
|
118 if (updated && n_ears > 1) { CrossCouple(); }
|
alexbrandmeyer@610
|
119 if (! open_loop) { CloseAGCLoop(); }
|
alexbrandmeyer@610
|
120 }
|
alexbrandmeyer@610
|
121 }
|
alexbrandmeyer@610
|
122
|
alexbrandmeyer@610
|
123 void CARFAC::CrossCouple() {
|
alexbrandmeyer@610
|
124 for (int stage = 0; stage < ears_[0].ReturnAGCNStages(); stage++) {
|
alexbrandmeyer@610
|
125 if (ears_[0].ReturnAGCStateDecimPhase(stage) > 0) {
|
alexbrandmeyer@610
|
126 break;
|
alexbrandmeyer@610
|
127 } else {
|
alexbrandmeyer@610
|
128 FPType mix_coeff = ears_[0].ReturnAGCMixCoeff(stage);
|
alexbrandmeyer@610
|
129 if (mix_coeff > 0) {
|
alexbrandmeyer@610
|
130 FloatArray stage_state;
|
alexbrandmeyer@610
|
131 FloatArray this_stage_values = FloatArray::Zero(n_ch_);
|
alexbrandmeyer@610
|
132 for (int ear = 0; ear < n_ears_; ear++) {
|
alexbrandmeyer@610
|
133 stage_state = ears_[ear].ReturnAGCStateMemory(stage);
|
alexbrandmeyer@610
|
134 this_stage_values += stage_state;
|
alexbrandmeyer@610
|
135 }
|
alexbrandmeyer@610
|
136 this_stage_values /= n_ears_;
|
alexbrandmeyer@610
|
137 for (int ear = 0; ear < n_ears_; ear++) {
|
alexbrandmeyer@610
|
138 stage_state = ears_[ear].ReturnAGCStateMemory(stage);
|
alexbrandmeyer@610
|
139 ears_[ear].SetAGCStateMemory(stage,
|
alexbrandmeyer@610
|
140 stage_state + mix_coeff *
|
alexbrandmeyer@610
|
141 (this_stage_values - stage_state));
|
alexbrandmeyer@610
|
142 }
|
alexbrandmeyer@610
|
143 }
|
alexbrandmeyer@609
|
144 }
|
alexbrandmeyer@609
|
145 }
|
alexbrandmeyer@609
|
146 }
|
alexbrandmeyer@610
|
147
|
alexbrandmeyer@610
|
148 void CARFAC::CloseAGCLoop() {
|
alexbrandmeyer@610
|
149 for (int ear = 0; ear < n_ears_; ear++) {
|
alexbrandmeyer@610
|
150 FloatArray undamping = 1 - ears_[ear].ReturnAGCStateMemory(1);
|
alexbrandmeyer@610
|
151 // This updates the target stage gain for the new damping.
|
alexbrandmeyer@610
|
152 ears_[ear].SetCARStateDZBMemory(ears_[ear].ReturnZRCoeffs() * undamping -
|
alexbrandmeyer@610
|
153 ears_[ear].ReturnZBMemory() /
|
alexbrandmeyer@610
|
154 ears_[ear].ReturnAGCDecimation(1));
|
alexbrandmeyer@610
|
155 ears_[ear].SetCARStateDGMemory((ears_[ear].StageGValue(undamping) -
|
alexbrandmeyer@610
|
156 ears_[ear].ReturnGMemory()) /
|
alexbrandmeyer@610
|
157 ears_[ear].ReturnAGCDecimation(1));
|
alexbrandmeyer@610
|
158 }
|
alexbrandmeyer@610
|
159 }
|