alexbrandmeyer@626
|
1 //
|
alexbrandmeyer@626
|
2 // carfac_test.cc
|
alexbrandmeyer@626
|
3 // CARFAC Open Source C++ Library
|
alexbrandmeyer@626
|
4 //
|
alexbrandmeyer@626
|
5 // Created by Alex Brandmeyer on 5/22/13.
|
alexbrandmeyer@626
|
6 //
|
alexbrandmeyer@626
|
7 // This C++ file is part of an implementation of Lyon's cochlear model:
|
alexbrandmeyer@626
|
8 // "Cascade of Asymmetric Resonators with Fast-Acting Compression"
|
alexbrandmeyer@626
|
9 // to supplement Lyon's upcoming book "Human and Machine Hearing"
|
alexbrandmeyer@626
|
10 //
|
alexbrandmeyer@626
|
11 // Licensed under the Apache License, Version 2.0 (the "License");
|
alexbrandmeyer@626
|
12 // you may not use this file except in compliance with the License.
|
alexbrandmeyer@626
|
13 // You may obtain a copy of the License at
|
alexbrandmeyer@626
|
14 //
|
alexbrandmeyer@626
|
15 // http://www.apache.org/licenses/LICENSE-2.0
|
alexbrandmeyer@626
|
16 //
|
alexbrandmeyer@626
|
17 // Unless required by applicable law or agreed to in writing, software
|
alexbrandmeyer@626
|
18 // distributed under the License is distributed on an "AS IS" BASIS,
|
alexbrandmeyer@626
|
19 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
alexbrandmeyer@626
|
20 // See the License for the specific language governing permissions and
|
alexbrandmeyer@626
|
21 // limitations under the License.
|
alexbrandmeyer@626
|
22
|
ronw@647
|
23 #include "carfac.h"
|
ronw@647
|
24
|
ronw@646
|
25 #include <fstream>
|
alexbrandmeyer@636
|
26 #include <string>
|
alexbrandmeyer@640
|
27 #include <vector>
|
ronw@642
|
28
|
ronw@642
|
29 #include "gtest/gtest.h"
|
alexbrandmeyer@643
|
30
|
ronw@646
|
31 #include "agc.h"
|
alexbrandmeyer@643
|
32 #include "car.h"
|
ronw@647
|
33 #include "carfac_output.h"
|
alexbrandmeyer@643
|
34 #include "common.h"
|
ronw@646
|
35 #include "ihc.h"
|
alexbrandmeyer@637
|
36
|
ronw@652
|
37 using std::deque;
|
alexbrandmeyer@636
|
38 using std::ifstream;
|
alexbrandmeyer@636
|
39 using std::ofstream;
|
ronw@646
|
40 using std::string;
|
ronw@646
|
41 using std::vector;
|
alexbrandmeyer@636
|
42
|
ronw@652
|
43 // Location of the text files produced by 'CARFAC_GenerateTestData.m' for
|
ronw@652
|
44 // comparing the ouput of the Matlab implementation with the one used here.
|
ronw@652
|
45 static const char* kTestSourceDir = "./test_data/";
|
ronw@630
|
46
|
ronw@652
|
47 // Writes the CARFAC NAP output to a text file.
|
ronw@652
|
48 void WriteNAPOutput(const CARFACOutput& output, const string& filename,
|
ronw@646
|
49 int ear) {
|
alexbrandmeyer@637
|
50 string fullfile = kTestSourceDir + filename;
|
alexbrandmeyer@636
|
51 ofstream ofile(fullfile.c_str());
|
alexbrandmeyer@643
|
52 int32_t num_timepoints = output.nap().size();
|
alexbrandmeyer@637
|
53 int channels = output.nap()[0][0].size();
|
alexbrandmeyer@636
|
54 if (ofile.is_open()) {
|
alexbrandmeyer@643
|
55 for (int32_t i = 0; i < num_timepoints; ++i) {
|
alexbrandmeyer@636
|
56 for (int j = 0; j < channels; ++j) {
|
alexbrandmeyer@637
|
57 ofile << output.nap()[i][ear](j);
|
ronw@646
|
58 if (j < channels - 1) {
|
alexbrandmeyer@636
|
59 ofile << " ";
|
alexbrandmeyer@636
|
60 }
|
alexbrandmeyer@636
|
61 }
|
alexbrandmeyer@636
|
62 ofile << "\n";
|
alexbrandmeyer@636
|
63 }
|
alexbrandmeyer@636
|
64 }
|
alexbrandmeyer@636
|
65 ofile.close();
|
alexbrandmeyer@636
|
66 }
|
alexbrandmeyer@636
|
67
|
ronw@652
|
68 // Reads a size rows vector of size columns Container objects from a
|
ronw@652
|
69 // multi-column text file generated by the Matlab version of CARFAC.
|
ronw@652
|
70 template <typename Container = ArrayX, bool ColMajor = true>
|
ronw@654
|
71 vector<Container> Load2dTestData(const string& filename, int rows,
|
ronw@654
|
72 int columns) {
|
alexbrandmeyer@637
|
73 string fullfile = kTestSourceDir + filename;
|
alexbrandmeyer@636
|
74 ifstream file(fullfile.c_str());
|
ronw@652
|
75 vector<Container> output;
|
ronw@652
|
76 if (ColMajor) {
|
ronw@652
|
77 output.assign(rows, Container(columns));
|
ronw@652
|
78 } else {
|
ronw@652
|
79 output.assign(columns, Container(rows));
|
alexbrandmeyer@626
|
80 }
|
alexbrandmeyer@626
|
81 if (file.is_open()) {
|
alexbrandmeyer@626
|
82 for (int i = 0; i < rows; ++i) {
|
alexbrandmeyer@626
|
83 for (int j = 0; j < columns; ++j) {
|
ronw@652
|
84 if (ColMajor) {
|
ronw@652
|
85 file >> output[i][j];
|
ronw@652
|
86 } else {
|
ronw@652
|
87 file >> output[j][i];
|
ronw@652
|
88 }
|
alexbrandmeyer@626
|
89 }
|
alexbrandmeyer@626
|
90 }
|
alexbrandmeyer@626
|
91 }
|
alexbrandmeyer@636
|
92 file.close();
|
alexbrandmeyer@626
|
93 return output;
|
alexbrandmeyer@626
|
94 }
|
alexbrandmeyer@626
|
95
|
ronw@652
|
96 // Reads a two dimensional vector of audio data from a text file
|
ronw@652
|
97 // containing the output of the Matlab wavread() function.
|
alexbrandmeyer@636
|
98 vector<vector<float>> Load2dAudioVector(string filename, int timepoints,
|
ronw@654
|
99 int num_channels) {
|
ronw@654
|
100 return Load2dTestData<vector<float>, false>(filename, timepoints,
|
ronw@654
|
101 num_channels);
|
ronw@652
|
102 }
|
ronw@652
|
103
|
ronw@652
|
104 class CARFACTest : public testing::Test {
|
ronw@652
|
105 protected:
|
ronw@654
|
106 deque<vector<ArrayX>> LoadTestData(const string& basename,
|
ronw@654
|
107 int num_samples,
|
ronw@654
|
108 int num_ears,
|
ronw@654
|
109 int num_channels) const {
|
ronw@654
|
110 deque<vector<ArrayX>> test_data(num_samples, vector<ArrayX>(num_ears));
|
ronw@654
|
111 for (int ear = 0; ear < num_ears; ++ear) {
|
ronw@652
|
112 string filename = basename + std::to_string(ear + 1) + ".txt";
|
ronw@654
|
113 vector<ArrayX> data = Load2dTestData(filename, num_samples, num_channels);
|
ronw@654
|
114 for (int i = 0; i < num_samples; ++i) {
|
ronw@652
|
115 test_data[i][ear] = data[i];
|
ronw@652
|
116 }
|
ronw@652
|
117 }
|
ronw@652
|
118 return test_data;
|
alexbrandmeyer@626
|
119 }
|
ronw@652
|
120
|
ronw@652
|
121 void AssertCARFACOutputNear(const deque<vector<ArrayX>>& expected,
|
ronw@652
|
122 const deque<vector<ArrayX>>& actual,
|
ronw@654
|
123 int num_samples,
|
ronw@654
|
124 int num_ears,
|
ronw@654
|
125 int num_channels) const {
|
ronw@654
|
126 for (int timepoint = 0; timepoint < num_samples; ++timepoint) {
|
ronw@654
|
127 for (int ear = 0; ear < num_ears; ++ear) {
|
ronw@654
|
128 for (int channel = 0; channel < num_channels; ++channel) {
|
ronw@652
|
129 const float kPrecisionLevel = 1.0e-7;
|
ronw@652
|
130 ASSERT_NEAR(expected[timepoint][ear](channel),
|
ronw@652
|
131 actual[timepoint][ear](channel),
|
ronw@652
|
132 kPrecisionLevel);
|
ronw@652
|
133 }
|
alexbrandmeyer@626
|
134 }
|
alexbrandmeyer@626
|
135 }
|
alexbrandmeyer@626
|
136 }
|
ronw@652
|
137
|
ronw@652
|
138 CARParams car_params_;
|
ronw@652
|
139 IHCParams ihc_params_;
|
ronw@652
|
140 AGCParams agc_params_;
|
ronw@652
|
141 };
|
ronw@652
|
142
|
ronw@652
|
143 TEST_F(CARFACTest, BinauralData) {
|
ronw@654
|
144 const int kNumSamples = 882;
|
ronw@654
|
145 const int kNumEars = 2;
|
ronw@654
|
146 const int kNumChannels = 71;
|
ronw@652
|
147 vector<vector<float>> sound_data =
|
ronw@654
|
148 Load2dAudioVector("file_signal_binaural_test.txt", kNumSamples, kNumEars);
|
ronw@654
|
149 CARFAC carfac(kNumEars, 22050, car_params_, ihc_params_, agc_params_);
|
ronw@652
|
150 CARFACOutput output(true, true, false, false);
|
ronw@652
|
151 const bool kOpenLoop = false;
|
ronw@652
|
152 const int length = sound_data[0].size();
|
ronw@652
|
153 carfac.RunSegment(sound_data, 0, length, kOpenLoop, &output);
|
ronw@652
|
154
|
ronw@652
|
155 // TODO(ronw): Don't unconditionally overwrite files that are
|
ronw@652
|
156 // checked in to the repository on every test run.
|
ronw@652
|
157 WriteNAPOutput(output, "cpp_nap_output_1_binaural_test.txt", 0);
|
ronw@652
|
158 WriteNAPOutput(output, "cpp_nap_output_2_binaural_test.txt", 1);
|
ronw@652
|
159
|
ronw@652
|
160 deque<vector<ArrayX>> expected_nap =
|
ronw@654
|
161 LoadTestData("binaural_test_nap", kNumSamples, kNumEars, kNumChannels);
|
ronw@652
|
162 AssertCARFACOutputNear(expected_nap, output.nap(),
|
ronw@654
|
163 kNumSamples, kNumEars, kNumChannels);
|
ronw@652
|
164 deque<vector<ArrayX>> expected_bm =
|
ronw@654
|
165 LoadTestData("binaural_test_bm", kNumSamples, kNumEars, kNumChannels);
|
ronw@652
|
166 AssertCARFACOutputNear(expected_bm, output.bm(),
|
ronw@654
|
167 kNumSamples, kNumEars, kNumChannels);
|
alexbrandmeyer@626
|
168 }
|
alexbrandmeyer@626
|
169
|
ronw@652
|
170 TEST_F(CARFACTest, LongBinauralData) {
|
ronw@654
|
171 const int kNumSamples = 2000;
|
ronw@654
|
172 const int kNumEars = 2;
|
ronw@654
|
173 const int kNumChannels = 83;
|
ronw@652
|
174 vector<vector<float>> sound_data =
|
ronw@654
|
175 Load2dAudioVector("file_signal_long_test.txt", kNumSamples, kNumEars);
|
ronw@654
|
176 CARFAC carfac(kNumEars, 44100, car_params_, ihc_params_, agc_params_);
|
ronw@652
|
177 CARFACOutput output(true, true, false, false);
|
ronw@641
|
178 const bool kOpenLoop = false;
|
ronw@641
|
179 const int length = sound_data[0].size();
|
ronw@652
|
180 carfac.RunSegment(sound_data, 0, length, kOpenLoop, &output);
|
ronw@652
|
181
|
ronw@652
|
182 // TODO(ronw): Don't unconditionally overwrite files that are
|
ronw@652
|
183 // checked in to the repository on every test run.
|
ronw@652
|
184 WriteNAPOutput(output, "cpp_nap_output_1_long_test.txt", 0);
|
ronw@652
|
185 WriteNAPOutput(output, "cpp_nap_output_2_long_test.txt", 1);
|
ronw@652
|
186
|
ronw@652
|
187 deque<vector<ArrayX>> expected_nap =
|
ronw@654
|
188 LoadTestData("long_test_nap", kNumSamples, kNumEars, kNumChannels);
|
ronw@652
|
189 AssertCARFACOutputNear(expected_nap, output.nap(),
|
ronw@654
|
190 kNumSamples, kNumEars, kNumChannels);
|
ronw@652
|
191 deque<vector<ArrayX>> expected_bm =
|
ronw@654
|
192 LoadTestData("long_test_bm", kNumSamples, kNumEars, kNumChannels);
|
ronw@652
|
193 AssertCARFACOutputNear(expected_bm, output.bm(),
|
ronw@654
|
194 kNumSamples, kNumEars, kNumChannels);
|
alexbrandmeyer@626
|
195 }
|