annotate tests/TestResampler.cpp @ 510:2adcd94c2079

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