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