Chris@366: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@366: Chris@366: #include "cq/CQSpectrogram.h" Chris@366: Chris@366: #include "dsp/Window.h" Chris@366: Chris@366: #include Chris@366: #include Chris@366: #include Chris@366: Chris@366: using std::vector; Chris@366: using std::cerr; Chris@366: using std::endl; Chris@366: Chris@366: #define BOOST_TEST_DYN_LINK Chris@366: #define BOOST_TEST_MAIN Chris@366: Chris@366: #include Chris@366: Chris@366: BOOST_AUTO_TEST_SUITE(TestCQFrequency) Chris@366: Chris@366: // The principle here is to feed a single windowed sinusoid into a Chris@366: // small CQ transform and check that the output has its peak bin at Chris@366: // the correct frequency. Chris@366: Chris@366: // Set up fs/2 = 50, frequency range 10 -> 40 i.e. 2 octaves, fixed Chris@366: // duration of 2 seconds Chris@366: static const double sampleRate = 100; Chris@366: static const double cqmin = 11.8921; Chris@366: static const double cqmax = 40; Chris@366: static const double bpo = 4; Chris@366: static const int duration = sampleRate * 2; Chris@366: Chris@366: // Threshold below which to ignore a column completely Chris@366: static const double threshold = 0.08; Chris@366: Chris@366: int Chris@366: binForFrequency(double freq) Chris@366: { Chris@366: int bin = (bpo * 2) - round(bpo * log2(freq / cqmin)) - 1; Chris@366: return bin; Chris@366: } Chris@366: Chris@366: void Chris@366: checkCQFreqColumn(int i, vector column, Chris@366: double freq, CQSpectrogram::Interpolation interp) Chris@366: { Chris@366: double maxval = 0.0; Chris@366: int maxidx = -1; Chris@366: int height = column.size(); Chris@366: Chris@366: int nonZeroHeight = ((i % 2 == 1) ? height/2 : height); Chris@366: Chris@366: for (int j = 0; j < nonZeroHeight; ++j) { Chris@366: if (j == 0 || column[j] > maxval) { Chris@366: maxval = column[j]; Chris@366: maxidx = j; Chris@366: } Chris@366: } Chris@366: Chris@366: int expected = binForFrequency(freq); Chris@366: if (maxval < threshold) { Chris@366: return; // ignore these columns at start and end Chris@366: } else if (expected < nonZeroHeight && maxidx != expected) { Chris@366: cerr << "ERROR: In column " << i << " with interpolation " << interp Chris@366: << ", maximum value for frequency " << freq Chris@366: << "\n found at index " << maxidx Chris@366: << " (expected index " << expected << ")" << endl; Chris@366: cerr << "column contains: "; Chris@366: for (int j = 0; j < height; ++j) { Chris@366: cerr << column[j] << " "; Chris@366: } Chris@366: cerr << endl; Chris@366: BOOST_CHECK_EQUAL(maxidx, expected); Chris@366: } Chris@366: } Chris@366: Chris@366: void Chris@366: testCQFrequencyWith(CQParameters params, Chris@366: CQSpectrogram::Interpolation interp, Chris@366: double freq) Chris@366: { Chris@366: CQSpectrogram cq(params, interp); Chris@366: Chris@366: BOOST_CHECK_EQUAL(cq.getBinsPerOctave(), bpo); Chris@366: BOOST_CHECK_EQUAL(cq.getOctaves(), 2); Chris@366: BOOST_CHECK_CLOSE(cq.getBinFrequency(0), 40, 1e-10); Chris@366: BOOST_CHECK_CLOSE(cq.getBinFrequency(4), 20, 1e-10); Chris@366: BOOST_CHECK_CLOSE(cq.getBinFrequency(7), cqmin, 1e-3); Chris@366: Chris@366: vector input; Chris@366: for (int i = 0; i < duration; ++i) { Chris@366: input.push_back(sin((i * 2 * M_PI * freq) / sampleRate)); Chris@366: } Chris@366: Window(HanningWindow, duration).cut(input.data()); Chris@366: Chris@366: CQSpectrogram::RealBlock output = cq.process(input); Chris@366: CQSpectrogram::RealBlock rest = cq.getRemainingOutput(); Chris@366: output.insert(output.end(), rest.begin(), rest.end()); Chris@366: Chris@366: BOOST_CHECK_EQUAL(output[0].size(), Chris@366: cq.getBinsPerOctave() * cq.getOctaves()); Chris@366: Chris@366: for (int i = 0; i < int(output.size()); ++i) { Chris@366: checkCQFreqColumn(i, output[i], freq, interp); Chris@366: } Chris@366: } Chris@366: Chris@366: void Chris@366: testCQFrequency(double freq) Chris@366: { Chris@366: vector interpolationTypes; Chris@366: interpolationTypes.push_back(CQSpectrogram::InterpolateZeros); Chris@366: interpolationTypes.push_back(CQSpectrogram::InterpolateHold); Chris@366: interpolationTypes.push_back(CQSpectrogram::InterpolateLinear); Chris@366: Chris@366: for (int k = 0; k < int(interpolationTypes.size()); ++k) { Chris@366: CQSpectrogram::Interpolation interp = interpolationTypes[k]; Chris@366: CQParameters params(sampleRate, cqmin, cqmax, bpo); Chris@366: testCQFrequencyWith(params, interp, freq); Chris@366: } Chris@366: } Chris@366: Chris@366: BOOST_AUTO_TEST_CASE(freq_11) { testCQFrequency(11); } Chris@366: BOOST_AUTO_TEST_CASE(freq_17) { testCQFrequency(17); } Chris@366: BOOST_AUTO_TEST_CASE(freq_24) { testCQFrequency(24); } Chris@366: BOOST_AUTO_TEST_CASE(freq_27) { testCQFrequency(27); } Chris@366: BOOST_AUTO_TEST_CASE(freq_33) { testCQFrequency(33); } Chris@366: BOOST_AUTO_TEST_CASE(freq_40) { testCQFrequency(40); } Chris@366: Chris@366: BOOST_AUTO_TEST_SUITE_END() Chris@366: