Chris@0: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@0: Chris@0: #include "Resampler.h" Chris@0: Chris@6: #include "qm-dsp/base/Window.h" Chris@6: #include "qm-dsp/dsp/transforms/FFT.h" Chris@6: Chris@0: #include Chris@0: Chris@0: #include Chris@0: Chris@0: #define BOOST_TEST_DYN_LINK Chris@0: #define BOOST_TEST_MAIN Chris@0: Chris@0: #include Chris@0: Chris@0: BOOST_AUTO_TEST_SUITE(TestResampler) Chris@0: Chris@0: using std::cout; Chris@0: using std::endl; Chris@1: using std::vector; Chris@1: Chris@1: void Chris@1: testResamplerOneShot(int sourceRate, Chris@1: int targetRate, Chris@1: int n, Chris@1: double *in, Chris@1: int m, Chris@4: double *expected, Chris@4: int skip) Chris@1: { Chris@1: vector resampled = Resampler::resample(sourceRate, targetRate, Chris@1: in, n); Chris@4: if (skip == 0) { Chris@4: BOOST_CHECK_EQUAL(resampled.size(), m); Chris@4: } Chris@1: for (int i = 0; i < m; ++i) { Chris@11: BOOST_CHECK_SMALL(resampled[i + skip] - expected[i], 1e-6); Chris@1: } Chris@1: } Chris@0: Chris@0: void Chris@0: testResampler(int sourceRate, Chris@0: int targetRate, Chris@0: int n, Chris@0: double *in, Chris@0: int m, Chris@0: double *expected) Chris@0: { Chris@2: // Here we provide the input in chunks (of varying size) Chris@1: Chris@0: Resampler r(sourceRate, targetRate); Chris@0: int latency = r.getLatency(); Chris@0: Chris@0: int m1 = m + latency; Chris@0: int n1 = int((m1 * sourceRate) / targetRate); Chris@0: Chris@0: double *inPadded = new double[n1]; Chris@0: double *outPadded = new double[m1]; Chris@0: Chris@0: for (int i = 0; i < n1; ++i) { Chris@0: if (i < n) inPadded[i] = in[i]; Chris@0: else inPadded[i] = 0.0; Chris@0: } Chris@0: Chris@0: for (int i = 0; i < m1; ++i) { Chris@0: outPadded[i] = -999.0; Chris@0: } Chris@0: Chris@2: int chunkSize = 1; Chris@2: int got = 0; Chris@2: int i = 0; Chris@0: Chris@2: while (true) { Chris@2: got += r.process(inPadded + i, outPadded + got, chunkSize); Chris@2: i = i + chunkSize; Chris@2: chunkSize = chunkSize + 1; Chris@4: if (i >= n1) { Chris@2: break; Chris@2: } else if (i + chunkSize >= n1) { Chris@2: chunkSize = n1 - i; Chris@4: } else if (chunkSize > 15) { Chris@4: chunkSize = 1; Chris@2: } Chris@2: } Chris@2: Chris@4: BOOST_CHECK_EQUAL(got, m1); Chris@0: Chris@0: for (int i = latency; i < m1; ++i) { Chris@1: BOOST_CHECK_SMALL(outPadded[i] - expected[i-latency], 1e-8); Chris@0: } Chris@4: Chris@0: delete[] outPadded; Chris@0: delete[] inPadded; Chris@0: } Chris@11: Chris@3: BOOST_AUTO_TEST_CASE(sameRateOneShot) Chris@3: { Chris@3: double d[] = { 0, 0.1, -0.3, -0.4, -0.3, 0, 0.5, 0.2, 0.8, -0.1 }; Chris@4: testResamplerOneShot(4, 4, 10, d, 10, d, 0); Chris@3: } Chris@3: Chris@0: BOOST_AUTO_TEST_CASE(sameRate) Chris@0: { Chris@0: double d[] = { 0, 0.1, -0.3, -0.4, -0.3, 0, 0.5, 0.2, 0.8, -0.1 }; Chris@0: testResampler(4, 4, 10, d, 10, d); Chris@0: } Chris@0: Chris@4: BOOST_AUTO_TEST_CASE(interpolatedMisc) Chris@4: { Chris@4: // Interpolating any signal by N should give a signal in which Chris@4: // every Nth sample is the original signal Chris@4: double in[] = { 0, 0.1, -0.3, -0.4, -0.3, 0, 0.5, 0.2, 0.8, -0.1 }; Chris@4: int n = sizeof(in)/sizeof(in[0]); Chris@4: for (int factor = 2; factor < 10; ++factor) { Chris@4: vector out = Resampler::resample(6, 6 * factor, in, n); Chris@4: for (int i = 0; i < n; ++i) { Chris@4: BOOST_CHECK_SMALL(out[i * factor] - in[i], 1e-5); Chris@4: } Chris@4: } Chris@4: } Chris@4: Chris@4: BOOST_AUTO_TEST_CASE(interpolatedSine) Chris@4: { Chris@5: // Interpolating a sinusoid should give us a sinusoid, once we've Chris@5: // dropped the first few samples Chris@4: double in[1000]; Chris@4: double out[2000]; Chris@4: for (int i = 0; i < 1000; ++i) { Chris@4: in[i] = sin(i * M_PI / 2.0); Chris@4: } Chris@4: for (int i = 0; i < 2000; ++i) { Chris@4: out[i] = sin(i * M_PI / 4.0); Chris@4: } Chris@5: testResamplerOneShot(8, 16, 1000, in, 200, out, 512); Chris@5: } Chris@5: Chris@5: BOOST_AUTO_TEST_CASE(decimatedSine) Chris@5: { Chris@5: // Decimating a sinusoid should give us a sinusoid, once we've Chris@5: // dropped the first few samples Chris@5: double in[2000]; Chris@5: double out[1000]; Chris@5: for (int i = 0; i < 2000; ++i) { Chris@5: in[i] = sin(i * M_PI / 8.0); Chris@5: } Chris@5: for (int i = 0; i < 1000; ++i) { Chris@5: out[i] = sin(i * M_PI / 4.0); Chris@5: } Chris@5: testResamplerOneShot(16, 8, 2000, in, 200, out, 256); Chris@4: } Chris@11: Chris@6: vector Chris@7: squareWave(int rate, double freq, int n) Chris@6: { Chris@6: //!!! todo: hoist, test Chris@6: vector v(n, 0.0); Chris@6: for (int h = 0; h < (rate/4)/freq; ++h) { Chris@6: double m = h * 2 + 1; Chris@7: double scale = 1.0 / m; Chris@6: for (int i = 0; i < n; ++i) { Chris@7: double s = scale * sin((i * 2.0 * M_PI * m * freq) / rate); Chris@7: v[i] += s; Chris@6: } Chris@6: } Chris@6: return v; Chris@6: } Chris@6: Chris@6: void Chris@6: testSpectrum(int inrate, int outrate) Chris@6: { Chris@6: // One second of a square wave Chris@6: int freq = 500; Chris@6: Chris@6: vector square = Chris@6: squareWave(inrate, freq, inrate); Chris@6: Chris@6: vector maybeSquare = Chris@6: Resampler::resample(inrate, outrate, square.data(), square.size()); Chris@6: Chris@6: BOOST_CHECK_EQUAL(maybeSquare.size(), outrate); Chris@6: Chris@6: Window(HanningWindow, inrate).cut(square.data()); Chris@6: Window(HanningWindow, outrate).cut(maybeSquare.data()); Chris@6: Chris@6: // forward magnitude with size inrate, outrate Chris@6: Chris@6: vector inSpectrum(inrate, 0.0); Chris@6: FFTReal(inrate).forwardMagnitude(square.data(), inSpectrum.data()); Chris@11: for (int i = 0; i < (int)inSpectrum.size(); ++i) { Chris@7: inSpectrum[i] /= inrate; Chris@7: } Chris@6: Chris@6: vector outSpectrum(outrate, 0.0); Chris@6: FFTReal(outrate).forwardMagnitude(maybeSquare.data(), outSpectrum.data()); Chris@11: for (int i = 0; i < (int)outSpectrum.size(); ++i) { Chris@7: outSpectrum[i] /= outrate; Chris@7: } Chris@6: Chris@6: // Don't compare bins any higher than 99% of Nyquist freq of lower sr Chris@6: int lengthOfInterest = (inrate < outrate ? inrate : outrate) / 2; Chris@6: lengthOfInterest = lengthOfInterest - (lengthOfInterest / 100); Chris@6: Chris@6: for (int i = 0; i < lengthOfInterest; ++i) { Chris@6: BOOST_CHECK_SMALL(inSpectrum[i] - outSpectrum[i], 1e-7); Chris@6: } Chris@6: } Chris@6: Chris@6: BOOST_AUTO_TEST_CASE(spectrum) Chris@6: { Chris@6: int rates[] = { 8000, 22050, 44100, 48000 }; Chris@11: for (int i = 0; i < (int)(sizeof(rates)/sizeof(rates[0])); ++i) { Chris@11: for (int j = 0; j < (int)(sizeof(rates)/sizeof(rates[0])); ++j) { Chris@6: testSpectrum(rates[i], rates[j]); Chris@6: } Chris@6: } Chris@6: } Chris@6: Chris@0: BOOST_AUTO_TEST_SUITE_END() Chris@0: