annotate test/TestResampler.cpp @ 196:da283326bcd3 tip master

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