annotate tests/TestResampler.cpp @ 209:ccd2019190bf msvc

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