annotate tests/TestResampler.cpp @ 397:fd207df9432e

Construct a currently-failing test on exact frequency in resampler (tracking down error in CQ)
author Chris Cannam <c.cannam@qmul.ac.uk>
date Sat, 10 May 2014 13:41:06 +0100
parents 88971211795c
children 333e27d1efa1
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,
c@375 25 int targetRate,
c@375 26 int n,
c@375 27 double *in,
c@375 28 int m,
c@375 29 double *expected,
c@375 30 int skip)
c@375 31 {
c@375 32 vector<double> resampled = Resampler::resample(sourceRate, targetRate,
c@375 33 in, n);
c@375 34 if (skip == 0) {
c@375 35 BOOST_CHECK_EQUAL(resampled.size(), m);
c@375 36 }
c@375 37 for (int i = 0; i < m; ++i) {
c@375 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,
c@375 44 int targetRate,
c@375 45 int n,
c@375 46 double *in,
c@375 47 int m,
c@375 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) {
c@375 62 if (i < n) inPadded[i] = in[i];
c@375 63 else inPadded[i] = 0.0;
c@375 64 }
c@375 65
c@375 66 for (int i = 0; i < m1; ++i) {
c@375 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) {
c@375 75 got += r.process(inPadded + i, outPadded + got, chunkSize);
c@375 76 i = i + chunkSize;
c@375 77 chunkSize = chunkSize + 1;
c@375 78 if (i >= n1) {
c@375 79 break;
c@375 80 } else if (i + chunkSize >= n1) {
c@375 81 chunkSize = n1 - i;
c@375 82 } else if (chunkSize > 15) {
c@375 83 chunkSize = 1;
c@375 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) {
c@375 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) {
c@375 116 vector<double> out = Resampler::resample(6, 6 * factor, in, n);
c@375 117 for (int i = 0; i < n; ++i) {
c@375 118 BOOST_CHECK_SMALL(out[i * factor] - in[i], 1e-5);
c@375 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) {
c@375 130 in[i] = sin(i * M_PI / 2.0);
c@375 131 }
c@375 132 for (int i = 0; i < 2000; ++i) {
c@375 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) {
c@375 145 in[i] = sin(i * M_PI / 8.0);
c@375 146 }
c@375 147 for (int i = 0; i < 1000; ++i) {
c@375 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@397 157 int firstCrossing = -1;
c@397 158 int lastCrossing = -1;
c@397 159 int nCrossings = 0;
c@397 160 // count -ve -> +ve transitions
c@397 161 for (int i = 0; i + 1 < n; ++i) {
c@397 162 if (v[i] <= 0.0 && v[i+1] > 0.0) {
c@397 163 if (firstCrossing < 0) firstCrossing = i;
c@397 164 lastCrossing = i;
c@397 165 ++nCrossings;
c@397 166 if (nCrossings == countCycles) break;
c@397 167 }
c@397 168 }
c@397 169 int nCycles = nCrossings - 1;
c@397 170 if (nCycles <= 0) return 0.0;
c@397 171 cout << "lastCrossing = " << lastCrossing << ", firstCrossing = " << firstCrossing << ", dist = " << lastCrossing - firstCrossing << ", nCycles = " << nCycles << endl;
c@397 172 double cycle = double(lastCrossing - firstCrossing) / nCycles;
c@397 173 return rate / cycle;
c@397 174 }
c@397 175
c@397 176 void
c@397 177 testSinFrequency(int freq,
c@397 178 int sourceRate,
c@397 179 int targetRate)
c@397 180 {
c@397 181 // Resampling a sinusoid and then resampling back should give us a
c@397 182 // sinusoid of the same frequency as we started with. Let's start
c@397 183 // with a few thousand cycles of it
c@397 184
c@397 185 int nCycles = 5000;
c@397 186
c@397 187 int duration = int(nCycles * float(sourceRate) / float(freq));
c@397 188 cout << "freq = " << freq << ", sourceRate = " << sourceRate << ", targetRate = " << targetRate << ", duration = " << duration << endl;
c@397 189
c@397 190 vector<double> in(duration, 0);
c@397 191 for (int i = 0; i < duration; ++i) {
c@397 192 in[i] = sin(i * M_PI * 2.0 * freq / sourceRate);
c@397 193 }
c@397 194
c@397 195 vector<double> out = Resampler::resample(sourceRate, targetRate,
c@397 196 in.data(), in.size());
c@397 197
c@397 198 vector<double> back = Resampler::resample(targetRate, sourceRate,
c@397 199 out.data(), out.size());
c@397 200
c@397 201 BOOST_CHECK_EQUAL(in.size(), back.size());
c@397 202
c@397 203 double inFreq = measureSinFreq(in, sourceRate, nCycles - 2);
c@397 204 double backFreq = measureSinFreq(back, sourceRate, nCycles - 2);
c@397 205
c@397 206 cout << "inFreq = " << inFreq << ", backFreq = " << backFreq << endl;
c@397 207
c@397 208 BOOST_CHECK_SMALL(inFreq - backFreq, 1e-8);
c@397 209
c@397 210 // for (int i = 0; i < int(in.size()); ++i) {
c@397 211 // BOOST_CHECK_SMALL(in[i] - back[i], 1e-6);
c@397 212 // }
c@397 213 }
c@397 214
c@397 215 BOOST_AUTO_TEST_CASE(downUp2)
c@397 216 {
c@397 217 testSinFrequency(440, 44100, 22050);
c@397 218 }
c@397 219
c@397 220 BOOST_AUTO_TEST_CASE(downUp16)
c@397 221 {
c@397 222 testSinFrequency(440, 48000, 3000);
c@397 223 }
c@397 224
c@397 225 BOOST_AUTO_TEST_CASE(upDown2)
c@397 226 {
c@397 227 testSinFrequency(440, 44100, 88200);
c@397 228 }
c@397 229
c@397 230 BOOST_AUTO_TEST_CASE(upDown16)
c@397 231 {
c@397 232 testSinFrequency(440, 3000, 48000);
c@397 233 }
c@397 234
c@375 235 vector<double>
c@375 236 squareWave(int rate, double freq, int n)
c@375 237 {
c@375 238 //!!! todo: hoist, test
c@375 239 vector<double> v(n, 0.0);
c@375 240 for (int h = 0; h < (rate/4)/freq; ++h) {
c@375 241 double m = h * 2 + 1;
c@375 242 double scale = 1.0 / m;
c@375 243 for (int i = 0; i < n; ++i) {
c@375 244 double s = scale * sin((i * 2.0 * M_PI * m * freq) / rate);
c@375 245 v[i] += s;
c@375 246 }
c@375 247 }
c@375 248 return v;
c@375 249 }
c@375 250
c@375 251 void
c@375 252 testSpectrum(int inrate, int outrate)
c@375 253 {
c@375 254 // One second of a square wave
c@375 255 int freq = 500;
c@375 256
c@375 257 vector<double> square =
c@375 258 squareWave(inrate, freq, inrate);
c@375 259
c@375 260 vector<double> maybeSquare =
c@375 261 Resampler::resample(inrate, outrate, square.data(), square.size());
c@375 262
c@375 263 BOOST_CHECK_EQUAL(maybeSquare.size(), outrate);
c@375 264
c@375 265 Window<double>(HanningWindow, inrate).cut(square.data());
c@375 266 Window<double>(HanningWindow, outrate).cut(maybeSquare.data());
c@375 267
c@375 268 // forward magnitude with size inrate, outrate
c@375 269
c@375 270 vector<double> inSpectrum(inrate, 0.0);
c@375 271 FFTReal(inrate).forwardMagnitude(square.data(), inSpectrum.data());
c@375 272 for (int i = 0; i < (int)inSpectrum.size(); ++i) {
c@375 273 inSpectrum[i] /= inrate;
c@375 274 }
c@375 275
c@375 276 vector<double> outSpectrum(outrate, 0.0);
c@375 277 FFTReal(outrate).forwardMagnitude(maybeSquare.data(), outSpectrum.data());
c@375 278 for (int i = 0; i < (int)outSpectrum.size(); ++i) {
c@375 279 outSpectrum[i] /= outrate;
c@375 280 }
c@375 281
c@381 282 // Don't compare bins any higher than 96% of Nyquist freq of lower sr
c@375 283 int lengthOfInterest = (inrate < outrate ? inrate : outrate) / 2;
c@381 284 lengthOfInterest = lengthOfInterest - (lengthOfInterest / 25);
c@375 285
c@375 286 for (int i = 0; i < lengthOfInterest; ++i) {
c@375 287 BOOST_CHECK_SMALL(inSpectrum[i] - outSpectrum[i], 1e-7);
c@375 288 }
c@375 289 }
c@375 290
c@375 291 BOOST_AUTO_TEST_CASE(spectrum)
c@375 292 {
c@375 293 int rates[] = { 8000, 22050, 44100, 48000 };
c@375 294 for (int i = 0; i < (int)(sizeof(rates)/sizeof(rates[0])); ++i) {
c@375 295 for (int j = 0; j < (int)(sizeof(rates)/sizeof(rates[0])); ++j) {
c@375 296 testSpectrum(rates[i], rates[j]);
c@375 297 }
c@375 298 }
c@375 299 }
c@375 300
c@375 301 BOOST_AUTO_TEST_SUITE_END()
c@375 302