Mercurial > hg > aimc
comparison trunk/carfac/carfac_test.cc @ 668:933cf18d9a59
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 | |
children | a9694d0bb55a |
comparison
equal
deleted
inserted
replaced
667:9b719047eca5 | 668:933cf18d9a59 |
---|---|
1 // | |
2 // carfac_test.cc | |
3 // CARFAC Open Source C++ Library | |
4 // | |
5 // Created by Alex Brandmeyer on 5/22/13. | |
6 // | |
7 // This C++ file is part of an implementation of Lyon's cochlear model: | |
8 // "Cascade of Asymmetric Resonators with Fast-Acting Compression" | |
9 // to supplement Lyon's upcoming book "Human and Machine Hearing" | |
10 // | |
11 // Licensed under the Apache License, Version 2.0 (the "License"); | |
12 // you may not use this file except in compliance with the License. | |
13 // You may obtain a copy of the License at | |
14 // | |
15 // http://www.apache.org/licenses/LICENSE-2.0 | |
16 // | |
17 // Unless required by applicable law or agreed to in writing, software | |
18 // distributed under the License is distributed on an "AS IS" BASIS, | |
19 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
20 // See the License for the specific language governing permissions and | |
21 // limitations under the License. | |
22 | |
23 #include "carfac_test.h" | |
24 // Three helper functions are defined here for loading the test data generated | |
25 // by the Matlab version of CARFAC. | |
26 // This loads one-dimensional FloatArrays from single-column text files. | |
27 FloatArray LoadTestData(const std::string filename, const int number_points) { | |
28 std::string fullfile = TEST_SRC_DIR + filename; | |
29 std::ifstream file(fullfile.c_str()); | |
30 FPType myarray[number_points]; | |
31 FloatArray output(number_points); | |
32 if (file.is_open()) { | |
33 for (int i = 0; i < number_points; ++i) { | |
34 file >> myarray[i]; | |
35 output(i) = myarray[i]; | |
36 } | |
37 } | |
38 return output; | |
39 } | |
40 | |
41 // This loads two-dimensional FloatArrays from multi-column text files. | |
42 std::vector<FloatArray> Load2dTestData(const std::string filename, const int rows, | |
43 const int columns) { | |
44 std::string fullfile = TEST_SRC_DIR + filename; | |
45 std::ifstream file(fullfile.c_str()); | |
46 FPType myarray[rows][columns]; | |
47 std::vector<FloatArray> output; | |
48 output.resize(rows); | |
49 for (auto& timepoint : output) { | |
50 timepoint.resize(columns); | |
51 } | |
52 if (file.is_open()) { | |
53 for (int i = 0; i < rows; ++i) { | |
54 for (int j = 0; j < columns; ++j) { | |
55 file >> myarray[i][j]; | |
56 output[i](j) = myarray[i][j]; | |
57 } | |
58 } | |
59 } | |
60 return output; | |
61 } | |
62 | |
63 // This loads two dimensional vectors of audio data using data generated in | |
64 // Matlab using the wavread() function. | |
65 std::vector<std::vector<float>> Load2dAudioVector(std::string filename, | |
66 int timepoints, | |
67 int channels) { | |
68 std::string fullfile = TEST_SRC_DIR + filename; | |
69 std::ifstream file(fullfile.c_str()); | |
70 std::vector<std::vector<float>> output; | |
71 output.resize(channels); | |
72 for (auto& channel : output) { | |
73 channel.resize(timepoints); | |
74 } | |
75 if (file.is_open()) { | |
76 for (int i = 0; i < timepoints; ++i) { | |
77 for (int j = 0; j < channels; ++j) { | |
78 file >> output[j][i]; | |
79 } | |
80 } | |
81 } | |
82 return output; | |
83 } | |
84 | |
85 // The first test verifies that the resulting CAR coefficients are the same as | |
86 // in Matlab when using the default CAR parameter set. | |
87 TEST(CARFACTest, CARCoeffs_Test){ | |
88 // These initialze the CAR Params and Coeffs objects needed for this test. | |
89 CARParams car_params; | |
90 CARCoeffs car_coeffs; | |
91 FPType fs = 22050.0; | |
92 // We calculate the pole frequencies and number of channels in the same way | |
93 // as in the CARFAC 'Design' method. | |
94 int n_ch = 0; | |
95 FPType pole_hz = car_params.first_pole_theta_ * fs / (2 * PI); | |
96 while (pole_hz > car_params.min_pole_hz_) { | |
97 n_ch++; | |
98 pole_hz = pole_hz - car_params.erb_per_step_ * | |
99 ERBHz(pole_hz, car_params.erb_break_freq_, car_params.erb_q_); | |
100 } | |
101 FloatArray pole_freqs(n_ch); | |
102 pole_hz = car_params.first_pole_theta_ * fs / (2 * PI); | |
103 for (int ch = 0; ch < n_ch; ++ch) { | |
104 pole_freqs(ch) = pole_hz; | |
105 pole_hz = pole_hz - car_params.erb_per_step_ * | |
106 ERBHz(pole_hz, car_params.erb_break_freq_, car_params.erb_q_); | |
107 } | |
108 // This initializes the CAR coeffecients object and runs the design method. | |
109 car_coeffs.Design(car_params, 22050, pole_freqs); | |
110 // Now we go through each set of coefficients to verify that the values are | |
111 // the same as in MATLAB. | |
112 std::string filename; | |
113 FloatArray output; | |
114 | |
115 ASSERT_EQ(car_coeffs.v_offset_, 0.04); | |
116 ASSERT_EQ(car_coeffs.velocity_scale_, 0.1); | |
117 | |
118 filename = "r1_coeffs.txt"; | |
119 output = LoadTestData(filename, n_ch); | |
120 for (int i = 0; i < n_ch; ++i) { | |
121 ASSERT_NEAR(output(i), car_coeffs.r1_coeffs_(i), PRECISION_LEVEL); | |
122 } | |
123 | |
124 filename = "a0_coeffs.txt"; | |
125 output = LoadTestData(filename, n_ch); | |
126 for (int i = 0; i < n_ch; ++i) { | |
127 ASSERT_NEAR(output(i), car_coeffs.a0_coeffs_(i), PRECISION_LEVEL); | |
128 } | |
129 | |
130 filename = "c0_coeffs.txt"; | |
131 output = LoadTestData(filename, n_ch); | |
132 for (int i = 0; i < n_ch; ++i) { | |
133 ASSERT_NEAR(output(i), car_coeffs.c0_coeffs_(i), PRECISION_LEVEL); | |
134 } | |
135 | |
136 filename = "zr_coeffs.txt"; | |
137 output = LoadTestData(filename, n_ch); | |
138 for (int i = 0; i < n_ch; ++i) { | |
139 ASSERT_NEAR(output(i), car_coeffs.zr_coeffs_(i), PRECISION_LEVEL); | |
140 } | |
141 | |
142 filename = "h_coeffs.txt"; | |
143 output = LoadTestData(filename, n_ch); | |
144 for (int i = 0; i < n_ch; ++i) { | |
145 ASSERT_NEAR(output(i), car_coeffs.h_coeffs_(i), PRECISION_LEVEL); | |
146 } | |
147 | |
148 filename = "g0_coeffs.txt"; | |
149 output = LoadTestData(filename, n_ch); | |
150 for (int i = 0; i < n_ch; ++i) { | |
151 ASSERT_NEAR(output(i), car_coeffs.g0_coeffs_(i), PRECISION_LEVEL); | |
152 } | |
153 } | |
154 | |
155 // The second test verifies that the IHC coefficient calculations result in the | |
156 // same set of values as in the Matlab version of the CARFAC. | |
157 TEST(CARFACTest, IHCCoeffs_Test){ | |
158 IHCParams ihc_params; | |
159 IHCCoeffs ihc_coeffs; | |
160 FPType fs = 22050.0; | |
161 ihc_coeffs.Design(ihc_params, fs); | |
162 | |
163 std::string filename = "ihc_coeffs.txt"; | |
164 FloatArray output = LoadTestData(filename, 9); | |
165 | |
166 // The sequence of the individual coefficients is determined using the | |
167 // CARFAC_GenerateTestData() function in the Matlab version, with all of the | |
168 // parameters placed in a single output file for convenience. | |
169 bool just_hwr = output(0); | |
170 FPType lpf_coeff = output(1); | |
171 FPType out_rate = output(2); | |
172 FPType in_rate = output(3); | |
173 bool one_cap = output(4); | |
174 FPType output_gain = output(5); | |
175 FPType rest_output = output(6); | |
176 FPType rest_cap = output(7); | |
177 FPType ac_coeff = output(8); | |
178 | |
179 // Once we have the Matlab values initialized, we can compare them to the | |
180 // output of the IHCCoeffs 'Design' method. | |
181 ASSERT_EQ(just_hwr, ihc_coeffs.just_hwr_); | |
182 ASSERT_NEAR(lpf_coeff, ihc_coeffs.lpf_coeff_, PRECISION_LEVEL); | |
183 ASSERT_NEAR(out_rate, ihc_coeffs.out1_rate_, PRECISION_LEVEL); | |
184 ASSERT_NEAR(in_rate, ihc_coeffs.in1_rate_, PRECISION_LEVEL); | |
185 ASSERT_EQ(one_cap, ihc_coeffs.one_cap_); | |
186 ASSERT_NEAR(output_gain, ihc_coeffs.output_gain_, PRECISION_LEVEL); | |
187 ASSERT_NEAR(rest_output, ihc_coeffs.rest_output_, PRECISION_LEVEL); | |
188 ASSERT_NEAR(rest_cap, ihc_coeffs.rest_cap1_, PRECISION_LEVEL); | |
189 ASSERT_NEAR(ac_coeff, ihc_coeffs.ac_coeff_, PRECISION_LEVEL); | |
190 } | |
191 | |
192 | |
193 TEST(CARFACTest, AGCCoeffs_Test) { | |
194 AGCParams agc_params; | |
195 std::vector<AGCCoeffs> agc_coeffs; | |
196 std::vector<FloatArray> output; | |
197 output.resize(agc_params.n_stages_); | |
198 std::string filename = "agc_coeffs_1.txt"; | |
199 output[0] = LoadTestData(filename, 14); | |
200 filename = "agc_coeffs_2.txt"; | |
201 output[1] = LoadTestData(filename, 14); | |
202 filename = "agc_coeffs_3.txt"; | |
203 output[2] = LoadTestData(filename, 14); | |
204 filename = "agc_coeffs_4.txt"; | |
205 output[3] = LoadTestData(filename, 14); | |
206 agc_coeffs.resize(agc_params.n_stages_); | |
207 // We initialize the AGC stages in the same was as in Ear::Init. | |
208 FPType fs = 22050.0; | |
209 FPType previous_stage_gain = 0.0; | |
210 FPType decim = 1.0; | |
211 for (int stage = 0; stage < agc_params.n_stages_; ++stage) { | |
212 agc_coeffs[stage].Design(agc_params, stage, fs, previous_stage_gain, decim); | |
213 previous_stage_gain = agc_coeffs[stage].agc_gain_; | |
214 decim = agc_coeffs[stage].decim_; | |
215 } | |
216 // Now we run through the individual coefficients and verify that they're the | |
217 // same as in Matlab. | |
218 for (int stage = 0; stage < agc_params.n_stages_; ++stage) { | |
219 int n_agc_stages = output[stage](1); | |
220 FPType agc_stage_gain = output[stage](2); | |
221 int decimation = output[stage](3); | |
222 FPType agc_epsilon = output[stage](4); | |
223 FPType agc_polez1 = output[stage](5); | |
224 FPType agc_polez2 = output[stage](6); | |
225 int agc_spatial_iterations = output[stage](7); | |
226 FPType agc_spatial_fir_1 = output[stage](8); | |
227 FPType agc_spatial_fir_2 = output[stage](9); | |
228 FPType agc_spatial_fir_3 = output[stage](10); | |
229 int agc_spatial_n_taps = output[stage](11); | |
230 FPType agc_mix_coeffs = output[stage](12); | |
231 FPType detect_scale = output[stage](13); | |
232 | |
233 ASSERT_EQ(n_agc_stages, agc_coeffs[stage].n_agc_stages_); | |
234 ASSERT_NEAR(agc_stage_gain, agc_coeffs[stage].agc_stage_gain_, | |
235 PRECISION_LEVEL); | |
236 ASSERT_EQ(decimation, agc_coeffs[stage].decimation_); | |
237 ASSERT_NEAR(agc_epsilon, agc_coeffs[stage].agc_epsilon_, PRECISION_LEVEL); | |
238 ASSERT_NEAR(agc_polez1, agc_coeffs[stage].agc_pole_z1_, PRECISION_LEVEL); | |
239 ASSERT_NEAR(agc_polez2, agc_coeffs[stage].agc_pole_z2_, PRECISION_LEVEL); | |
240 ASSERT_EQ(agc_spatial_iterations, | |
241 agc_coeffs[stage].agc_spatial_iterations_); | |
242 ASSERT_NEAR(agc_spatial_fir_1, agc_coeffs[stage].agc_spatial_fir_[0], | |
243 PRECISION_LEVEL); | |
244 ASSERT_NEAR(agc_spatial_fir_2, agc_coeffs[stage].agc_spatial_fir_[1], | |
245 PRECISION_LEVEL); | |
246 ASSERT_EQ(agc_spatial_n_taps, | |
247 agc_coeffs[stage].agc_spatial_n_taps_); | |
248 ASSERT_NEAR(agc_spatial_fir_3, agc_coeffs[stage].agc_spatial_fir_[2], | |
249 PRECISION_LEVEL); | |
250 ASSERT_NEAR(agc_mix_coeffs, agc_coeffs[stage].agc_mix_coeffs_, | |
251 PRECISION_LEVEL); | |
252 | |
253 // The last stage will have the correct detect_scale_ value on the basis of | |
254 // the total gain accumlated over the stages. | |
255 if (stage == agc_params.n_stages_ - 1) { | |
256 ASSERT_NEAR(detect_scale, agc_coeffs[stage].detect_scale_, | |
257 PRECISION_LEVEL); | |
258 } | |
259 } | |
260 } | |
261 | |
262 // This test verifies the output of the C++ code relative to that of the Matlab | |
263 // version using a single segment (441 samples) of audio from the "plan.wav" | |
264 // file. The single-channel audio data and different output matrices from Matlab | |
265 // are stored in text files and then read into 2d Eigen arrays (for now, this | |
266 // should be changed to a vector of FloatArrays... TODO (alexbrandmeyer)). For | |
267 // reference, see the CARFAC_GenerateTestData() function in the Matlab branch | |
268 // of the repository. | |
269 // | |
270 // A single Ear object is used along with the code from CARFAC.RunSegment() to | |
271 // evaluate the output of the CAR and IHC steps on a sample by sample basis | |
272 // relative to the output read in from Matlab. The test passes with 11 degrees | |
273 // of precision, with the Matlab data stored using 12 decimals. | |
274 // | |
275 // TODO (alexbrandmeyer): A subseqent version of this test will operate directly | |
276 // on the CARFACOutput structure and will evaluate binaural data. | |
277 TEST(CARFACTest, Monaural_Output_Test) { | |
278 std::string filename = "monaural_test_nap.txt"; | |
279 std::vector<FloatArray> nap = Load2dTestData(filename, 441, 71); | |
280 filename = "monaural_test_bm.txt"; | |
281 std::vector<FloatArray> bm = Load2dTestData(filename, 441, 71); | |
282 filename = "monaural_test_ohc.txt"; | |
283 std::vector<FloatArray> ohc = Load2dTestData(filename, 441, 71); | |
284 filename = "monaural_test_agc.txt"; | |
285 std::vector<FloatArray> agc = Load2dTestData(filename, 441, 71); | |
286 filename = "file_signal_monaural_test.txt"; | |
287 std::vector<std::vector<float>> sound_data = Load2dAudioVector(filename, 441, | |
288 1); | |
289 // The number of timepoints is determined from the length of the audio | |
290 // segment. | |
291 int32_t n_timepoints = sound_data[0].size(); | |
292 | |
293 CARParams car_params; | |
294 IHCParams ihc_params; | |
295 AGCParams agc_params; | |
296 FPType fs = 22050.0; | |
297 int n_ch = 0; | |
298 FPType pole_hz = car_params.first_pole_theta_ * fs / (2 * PI); | |
299 while (pole_hz > car_params.min_pole_hz_) { | |
300 n_ch++; | |
301 pole_hz = pole_hz - car_params.erb_per_step_ * | |
302 ERBHz(pole_hz, car_params.erb_break_freq_, car_params.erb_q_); | |
303 } | |
304 FloatArray pole_freqs(n_ch); | |
305 pole_hz = car_params.first_pole_theta_ * fs / (2 * PI); | |
306 for (int ch = 0; ch < n_ch; ++ch) { | |
307 pole_freqs(ch) = pole_hz; | |
308 pole_hz = pole_hz - car_params.erb_per_step_ * | |
309 ERBHz(pole_hz, car_params.erb_break_freq_, car_params.erb_q_); | |
310 } | |
311 | |
312 // This initializes the CARFAC object and runs the design method. | |
313 Ear ear; | |
314 ear.InitEar(n_ch, fs, pole_freqs, car_params, ihc_params, agc_params); | |
315 | |
316 CARFACOutput seg_output; | |
317 seg_output.InitOutput(1, n_ch, n_timepoints); | |
318 | |
319 // A nested loop structure is used to iterate through the individual samples | |
320 // for each ear (audio channel). | |
321 FloatArray car_out(n_ch); | |
322 FloatArray ihc_out(n_ch); | |
323 FloatArray matlab_car_out(n_ch); | |
324 FloatArray matlab_ihc_out(n_ch); | |
325 bool updated; // This variable is used by the AGC stage. | |
326 for (int32_t i = 0; i < n_timepoints; ++i) { | |
327 int j = 0; | |
328 // First we create a reference to the current Ear object. | |
329 // This stores the audio sample currently being processed. | |
330 FPType input = sound_data[j][i]; | |
331 // Now we apply the three stages of the model in sequence to the current | |
332 // audio sample. | |
333 ear.CARStep(input, &car_out); | |
334 matlab_car_out = bm[i]; | |
335 // This step verifies that the ouput of the CAR step is the same at each | |
336 // timepoint and channel as that of the Matlab version. | |
337 for (int channel = 0; channel < n_ch; ++channel) { | |
338 FPType a = matlab_car_out(channel); | |
339 FPType b = car_out(channel); | |
340 ASSERT_NEAR(a, b, PRECISION_LEVEL); | |
341 } | |
342 ear.IHCStep(car_out, &ihc_out); | |
343 matlab_ihc_out = nap[i]; | |
344 // This step verifies that the ouput of the IHC step is the same at each | |
345 // timepoint and channel as that of the Matlab version. | |
346 for (int channel = 0; channel < n_ch; ++channel) { | |
347 FPType a = matlab_ihc_out(channel); | |
348 FPType b = ihc_out(channel); | |
349 ASSERT_NEAR(a, b, PRECISION_LEVEL); | |
350 } | |
351 | |
352 updated = ear.AGCStep(ihc_out); | |
353 // These lines assign the output of the model for the current sample | |
354 // to the appropriate data members of the current ear in the output | |
355 // object. | |
356 seg_output.StoreNAPOutput(i, j, ihc_out); | |
357 seg_output.StoreBMOutput(i, j, car_out); | |
358 seg_output.StoreOHCOutput(i, j, ear.za_memory()); | |
359 seg_output.StoreAGCOutput(i, j, ear.zb_memory()); | |
360 if (updated) { | |
361 FloatArray undamping = 1 - ear.agc_memory(0); | |
362 // This updates the target stage gain for the new damping. | |
363 ear.set_dzb_memory((ear.zr_coeffs() * undamping - ear.zb_memory()) / | |
364 ear.agc_decimation(0)); | |
365 ear.set_dg_memory((ear.StageGValue(undamping) - ear.g_memory()) / | |
366 ear.agc_decimation(0)); | |
367 } | |
368 } | |
369 } |