Mercurial > hg > constant-q-cpp
comparison test/TestResampler.cpp @ 131:6b13f9c694a8
Start introducing the tests
author | Chris Cannam <c.cannam@qmul.ac.uk> |
---|---|
date | Mon, 19 May 2014 10:21:51 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
130:1e33f719dde1 | 131:6b13f9c694a8 |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 #include "dsp/Resampler.h" | |
4 | |
5 #include "dsp/Window.h" | |
6 #include "dsp/FFT.h" | |
7 | |
8 #include <iostream> | |
9 | |
10 #include <cmath> | |
11 | |
12 #define BOOST_TEST_DYN_LINK | |
13 #define BOOST_TEST_MAIN | |
14 | |
15 #include <boost/test/unit_test.hpp> | |
16 | |
17 BOOST_AUTO_TEST_SUITE(TestResampler) | |
18 | |
19 using std::cout; | |
20 using std::endl; | |
21 using std::vector; | |
22 | |
23 void | |
24 testResamplerOneShot(int sourceRate, | |
25 int targetRate, | |
26 int n, | |
27 double *in, | |
28 int m, | |
29 double *expected, | |
30 int skip) | |
31 { | |
32 vector<double> resampled = Resampler::resample(sourceRate, targetRate, | |
33 in, n); | |
34 if (skip == 0) { | |
35 BOOST_CHECK_EQUAL(resampled.size(), m); | |
36 } | |
37 for (int i = 0; i < m; ++i) { | |
38 BOOST_CHECK_SMALL(resampled[i + skip] - expected[i], 1e-6); | |
39 } | |
40 } | |
41 | |
42 void | |
43 testResampler(int sourceRate, | |
44 int targetRate, | |
45 int n, | |
46 double *in, | |
47 int m, | |
48 double *expected) | |
49 { | |
50 // Here we provide the input in chunks (of varying size) | |
51 | |
52 Resampler r(sourceRate, targetRate); | |
53 int latency = r.getLatency(); | |
54 | |
55 int m1 = m + latency; | |
56 int n1 = int((m1 * sourceRate) / targetRate); | |
57 | |
58 double *inPadded = new double[n1]; | |
59 double *outPadded = new double[m1]; | |
60 | |
61 for (int i = 0; i < n1; ++i) { | |
62 if (i < n) inPadded[i] = in[i]; | |
63 else inPadded[i] = 0.0; | |
64 } | |
65 | |
66 for (int i = 0; i < m1; ++i) { | |
67 outPadded[i] = -999.0; | |
68 } | |
69 | |
70 int chunkSize = 1; | |
71 int got = 0; | |
72 int i = 0; | |
73 | |
74 while (true) { | |
75 got += r.process(inPadded + i, outPadded + got, chunkSize); | |
76 i = i + chunkSize; | |
77 chunkSize = chunkSize + 1; | |
78 if (i >= n1) { | |
79 break; | |
80 } else if (i + chunkSize >= n1) { | |
81 chunkSize = n1 - i; | |
82 } else if (chunkSize > 15) { | |
83 chunkSize = 1; | |
84 } | |
85 } | |
86 | |
87 BOOST_CHECK_EQUAL(got, m1); | |
88 | |
89 for (int i = latency; i < m1; ++i) { | |
90 BOOST_CHECK_SMALL(outPadded[i] - expected[i-latency], 1e-8); | |
91 } | |
92 | |
93 delete[] outPadded; | |
94 delete[] inPadded; | |
95 } | |
96 | |
97 BOOST_AUTO_TEST_CASE(sameRateOneShot) | |
98 { | |
99 double d[] = { 0, 0.1, -0.3, -0.4, -0.3, 0, 0.5, 0.2, 0.8, -0.1 }; | |
100 testResamplerOneShot(4, 4, 10, d, 10, d, 0); | |
101 } | |
102 | |
103 BOOST_AUTO_TEST_CASE(sameRate) | |
104 { | |
105 double d[] = { 0, 0.1, -0.3, -0.4, -0.3, 0, 0.5, 0.2, 0.8, -0.1 }; | |
106 testResampler(4, 4, 10, d, 10, d); | |
107 } | |
108 | |
109 BOOST_AUTO_TEST_CASE(interpolatedMisc) | |
110 { | |
111 // Interpolating any signal by N should give a signal in which | |
112 // every Nth sample is the original signal | |
113 double in[] = { 0, 0.1, -0.3, -0.4, -0.3, 0, 0.5, 0.2, 0.8, -0.1 }; | |
114 int n = sizeof(in)/sizeof(in[0]); | |
115 for (int factor = 2; factor < 10; ++factor) { | |
116 vector<double> out = Resampler::resample(6, 6 * factor, in, n); | |
117 for (int i = 0; i < n; ++i) { | |
118 BOOST_CHECK_SMALL(out[i * factor] - in[i], 1e-5); | |
119 } | |
120 } | |
121 } | |
122 | |
123 BOOST_AUTO_TEST_CASE(interpolatedSine) | |
124 { | |
125 // Interpolating a sinusoid should give us a sinusoid, once we've | |
126 // dropped the first few samples | |
127 double in[1000]; | |
128 double out[2000]; | |
129 for (int i = 0; i < 1000; ++i) { | |
130 in[i] = sin(i * M_PI / 2.0); | |
131 } | |
132 for (int i = 0; i < 2000; ++i) { | |
133 out[i] = sin(i * M_PI / 4.0); | |
134 } | |
135 testResamplerOneShot(8, 16, 1000, in, 200, out, 512); | |
136 } | |
137 | |
138 BOOST_AUTO_TEST_CASE(decimatedSine) | |
139 { | |
140 // Decimating a sinusoid should give us a sinusoid, once we've | |
141 // dropped the first few samples | |
142 double in[2000]; | |
143 double out[1000]; | |
144 for (int i = 0; i < 2000; ++i) { | |
145 in[i] = sin(i * M_PI / 8.0); | |
146 } | |
147 for (int i = 0; i < 1000; ++i) { | |
148 out[i] = sin(i * M_PI / 4.0); | |
149 } | |
150 testResamplerOneShot(16, 8, 2000, in, 200, out, 256); | |
151 } | |
152 | |
153 double | |
154 measureSinFreq(const vector<double> &v, int rate, int countCycles) | |
155 { | |
156 int n = v.size(); | |
157 int firstPeak = -1; | |
158 int lastPeak = -1; | |
159 int nPeaks = 0; | |
160 // count +ve peaks | |
161 for (int i = v.size()/4; i + 1 < n; ++i) { | |
162 // allow some fuzz | |
163 int x0 = int(10000 * v[i-1]); | |
164 int x1 = int(10000 * v[i]); | |
165 int x2 = int(10000 * v[i+1]); | |
166 if (x1 > 0 && x1 > x0 && x1 >= x2) { | |
167 if (firstPeak < 0) firstPeak = i; | |
168 lastPeak = i; | |
169 ++nPeaks; | |
170 if (nPeaks == countCycles) break; | |
171 } | |
172 } | |
173 int nCycles = nPeaks - 1; | |
174 if (nCycles <= 0) return 0.0; | |
175 double cycle = double(lastPeak - firstPeak) / nCycles; | |
176 // cout << "lastPeak = " << lastPeak << ", firstPeak = " << firstPeak << ", dist = " << lastPeak - firstPeak << ", nCycles = " << nCycles << ", cycle = " << cycle << endl; | |
177 return rate / cycle; | |
178 } | |
179 | |
180 void | |
181 testSinFrequency(int freq, | |
182 int sourceRate, | |
183 int targetRate) | |
184 { | |
185 // Resampling a sinusoid and then resampling back should give us a | |
186 // sinusoid of the same frequency as we started with | |
187 | |
188 int nCycles = 500; | |
189 | |
190 int duration = int(nCycles * float(sourceRate) / float(freq)); | |
191 // cout << "freq = " << freq << ", sourceRate = " << sourceRate << ", targetRate = " << targetRate << ", duration = " << duration << endl; | |
192 | |
193 vector<double> in(duration, 0); | |
194 for (int i = 0; i < duration; ++i) { | |
195 in[i] = sin(i * M_PI * 2.0 * freq / sourceRate); | |
196 } | |
197 | |
198 vector<double> out = Resampler::resample(sourceRate, targetRate, | |
199 in.data(), in.size()); | |
200 | |
201 vector<double> back = Resampler::resample(targetRate, sourceRate, | |
202 out.data(), out.size()); | |
203 | |
204 BOOST_CHECK_EQUAL(in.size(), back.size()); | |
205 | |
206 double inFreq = measureSinFreq(in, sourceRate, nCycles / 2); | |
207 double backFreq = measureSinFreq(back, sourceRate, nCycles / 2); | |
208 | |
209 BOOST_CHECK_SMALL(inFreq - backFreq, 1e-8); | |
210 } | |
211 | |
212 // In each of the following we use a frequency that has an exact cycle | |
213 // length in samples at the lowest sample rate, so that we can easily | |
214 // rule out errors in measuring the cycle length after resampling. If | |
215 // the resampler gets its input or output rate wrong, that will show | |
216 // up no matter what the test signal's initial frequency is. | |
217 | |
218 BOOST_AUTO_TEST_CASE(downUp2) | |
219 { | |
220 testSinFrequency(441, 44100, 22050); | |
221 } | |
222 | |
223 BOOST_AUTO_TEST_CASE(downUp5) | |
224 { | |
225 testSinFrequency(300, 15000, 3000); | |
226 } | |
227 | |
228 BOOST_AUTO_TEST_CASE(downUp16) | |
229 { | |
230 testSinFrequency(300, 48000, 3000); | |
231 } | |
232 | |
233 BOOST_AUTO_TEST_CASE(upDown2) | |
234 { | |
235 testSinFrequency(441, 44100, 88200); | |
236 } | |
237 | |
238 BOOST_AUTO_TEST_CASE(upDown5) | |
239 { | |
240 testSinFrequency(300, 3000, 15000); | |
241 } | |
242 | |
243 BOOST_AUTO_TEST_CASE(upDown16) | |
244 { | |
245 testSinFrequency(300, 3000, 48000); | |
246 } | |
247 | |
248 vector<double> | |
249 squareWave(int rate, double freq, int n) | |
250 { | |
251 //!!! todo: hoist, test | |
252 vector<double> v(n, 0.0); | |
253 for (int h = 0; h < (rate/4)/freq; ++h) { | |
254 double m = h * 2 + 1; | |
255 double scale = 1.0 / m; | |
256 for (int i = 0; i < n; ++i) { | |
257 double s = scale * sin((i * 2.0 * M_PI * m * freq) / rate); | |
258 v[i] += s; | |
259 } | |
260 } | |
261 return v; | |
262 } | |
263 | |
264 void | |
265 testSpectrum(int inrate, int outrate) | |
266 { | |
267 // One second of a square wave | |
268 int freq = 500; | |
269 | |
270 vector<double> square = | |
271 squareWave(inrate, freq, inrate); | |
272 | |
273 vector<double> maybeSquare = | |
274 Resampler::resample(inrate, outrate, square.data(), square.size()); | |
275 | |
276 BOOST_CHECK_EQUAL(maybeSquare.size(), outrate); | |
277 | |
278 Window<double>(HanningWindow, inrate).cut(square.data()); | |
279 Window<double>(HanningWindow, outrate).cut(maybeSquare.data()); | |
280 | |
281 // forward magnitude with size inrate, outrate | |
282 | |
283 vector<double> inSpectrum(inrate, 0.0); | |
284 FFTReal(inrate).forwardMagnitude(square.data(), inSpectrum.data()); | |
285 for (int i = 0; i < (int)inSpectrum.size(); ++i) { | |
286 inSpectrum[i] /= inrate; | |
287 } | |
288 | |
289 vector<double> outSpectrum(outrate, 0.0); | |
290 FFTReal(outrate).forwardMagnitude(maybeSquare.data(), outSpectrum.data()); | |
291 for (int i = 0; i < (int)outSpectrum.size(); ++i) { | |
292 outSpectrum[i] /= outrate; | |
293 } | |
294 | |
295 // Don't compare bins any higher than 96% of Nyquist freq of lower sr | |
296 int lengthOfInterest = (inrate < outrate ? inrate : outrate) / 2; | |
297 lengthOfInterest = lengthOfInterest - (lengthOfInterest / 25); | |
298 | |
299 for (int i = 0; i < lengthOfInterest; ++i) { | |
300 BOOST_CHECK_SMALL(inSpectrum[i] - outSpectrum[i], 1e-7); | |
301 } | |
302 } | |
303 /* | |
304 BOOST_AUTO_TEST_CASE(spectrum) | |
305 { | |
306 int rates[] = { 8000, 22050, 44100, 48000 }; | |
307 for (int i = 0; i < (int)(sizeof(rates)/sizeof(rates[0])); ++i) { | |
308 for (int j = 0; j < (int)(sizeof(rates)/sizeof(rates[0])); ++j) { | |
309 testSpectrum(rates[i], rates[j]); | |
310 } | |
311 } | |
312 } | |
313 */ | |
314 BOOST_AUTO_TEST_SUITE_END() | |
315 |