# HG changeset patch # User Chris Cannam # Date 1434109279 -3600 # Node ID 5fab8e4f5f19515cd3b9c6b4650df0e5f8d2fabb # Parent dcf54a6964d04fb4556d925ce62989a681113555 Start making the FFT model tests into proper tests diff -r dcf54a6964d0 -r 5fab8e4f5f19 data/fft/FFTDataServer.cpp --- a/data/fft/FFTDataServer.cpp Thu Jun 11 09:09:11 2015 +0100 +++ b/data/fft/FFTDataServer.cpp Fri Jun 12 12:41:19 2015 +0100 @@ -1270,10 +1270,10 @@ sv_frame_t startFrame = m_windowIncrement * sv_frame_t(x); sv_frame_t endFrame = startFrame + m_windowSize; - if (m_windowIncrement != winsize) { - startFrame -= (winsize - m_windowIncrement); - endFrame -= (winsize - m_windowIncrement); - } + // FFT windows are centred at the respective audio sample frame, + // so the first one is centred at 0 + startFrame -= winsize / 2; + endFrame -= winsize / 2; #ifdef DEBUG_FFT_SERVER_FILL std::cerr << "FFTDataServer::fillColumn: requesting frames " diff -r dcf54a6964d0 -r 5fab8e4f5f19 data/model/test/MockWaveModel.cpp --- a/data/model/test/MockWaveModel.cpp Thu Jun 11 09:09:11 2015 +0100 +++ b/data/model/test/MockWaveModel.cpp Fri Jun 12 12:41:19 2015 +0100 @@ -17,10 +17,10 @@ using namespace std; -MockWaveModel::MockWaveModel(vector sorts, int length) +MockWaveModel::MockWaveModel(vector sorts, int length, int pad) { for (auto sort: sorts) { - m_data.push_back(generate(sort, length)); + m_data.push_back(generate(sort, length, pad)); } } @@ -61,10 +61,14 @@ } vector -MockWaveModel::generate(Sort sort, int length) const +MockWaveModel::generate(Sort sort, int length, int pad) const { vector data; + for (int i = 0; i < pad; ++i) { + data.push_back(0.f); + } + for (int i = 0; i < length; ++i) { double v = 0.0; @@ -80,6 +84,10 @@ data.push_back(float(v)); } + for (int i = 0; i < pad; ++i) { + data.push_back(0.f); + } + return data; } diff -r dcf54a6964d0 -r 5fab8e4f5f19 data/model/test/MockWaveModel.h --- a/data/model/test/MockWaveModel.h Thu Jun 11 09:09:11 2015 +0100 +++ b/data/model/test/MockWaveModel.h Fri Jun 12 12:41:19 2015 +0100 @@ -33,8 +33,9 @@ Q_OBJECT public: - /** One Sort per channel! Length is in samples */ - MockWaveModel(std::vector sorts, int length); + /** One Sort per channel! Length is in samples, and is in addition + * to "pad" number of zero samples at the start and end */ + MockWaveModel(std::vector sorts, int length, int pad); virtual float getValueMinimum() const { return -1.f; } virtual float getValueMaximum() const { return 1.f; } @@ -58,7 +59,7 @@ private: std::vector > m_data; - std::vector generate(Sort sort, int length) const; + std::vector generate(Sort sort, int length, int pad) const; }; #endif diff -r dcf54a6964d0 -r 5fab8e4f5f19 data/model/test/TestFFTModel.h --- a/data/model/test/TestFFTModel.h Thu Jun 11 09:09:11 2015 +0100 +++ b/data/model/test/TestFFTModel.h Fri Jun 12 12:41:19 2015 +0100 @@ -26,6 +26,7 @@ #include #include +#include using namespace std; @@ -33,26 +34,85 @@ { Q_OBJECT +private: + void test(DenseTimeValueModel *model, + WindowType window, int windowSize, int windowIncrement, int fftSize, + int columnNo, vector>> expectedValues, + int expectedWidth) { + for (int ch = 0; in_range_for(expectedValues, ch); ++ch) { + for (int polar = 0; polar <= 1; ++polar) { + FFTModel fftm(model, ch, window, windowSize, windowIncrement, + fftSize, bool(polar)); + QCOMPARE(fftm.getWidth(), expectedWidth); + int hs1 = fftSize/2 + 1; + QCOMPARE(fftm.getHeight(), hs1); + vector reals(hs1 + 1, 0.f); + vector imags(hs1 + 1, 0.f); + reals[hs1] = 999.f; // overrun guards + imags[hs1] = 999.f; + fftm.getValuesAt(columnNo, &reals[0], &imags[0]); + for (int i = 0; i < hs1; ++i) { + float eRe = expectedValues[ch][i].real(); + float eIm = expectedValues[ch][i].imag(); + if (reals[i] != eRe || imags[i] != eIm) { + cerr << "ERROR: output is not as expected for column " + << i << " in channel " << ch << " (polar store = " + << polar << ")" << endl; + cerr << "expected : "; + for (int j = 0; j < hs1; ++j) { + cerr << expectedValues[ch][j] << " "; + } + cerr << "\nactual : "; + for (int j = 0; j < hs1; ++j) { + cerr << complex(reals[j], imags[j]) << " "; + } + cerr << endl; + } + QCOMPARE(reals[i], eRe); + QCOMPARE(imags[i], eIm); + } + QCOMPARE(reals[hs1], 999.f); + QCOMPARE(imags[hs1], 999.f); + } + } + } + private slots: - void example() { - MockWaveModel mwm({ DC }, 16); - FFTModel fftm(&mwm, 0, RectangularWindow, 8, 8, 8, false); - float reals[6], imags[6]; - reals[5] = 999.f; // overrun guards - imags[5] = 999.f; - fftm.getValuesAt(0, reals, imags); - cerr << "reals: " << reals[0] << "," << reals[1] << "," << reals[2] << "," << reals[3] << "," << reals[4] << endl; - cerr << "imags: " << imags[0] << "," << imags[1] << "," << imags[2] << "," << imags[3] << "," << imags[4] << endl; - QCOMPARE(reals[0], 4.f); // rectangular window scales by 0.5 - QCOMPARE(reals[1], 0.f); - QCOMPARE(reals[2], 0.f); - QCOMPARE(reals[3], 0.f); - QCOMPARE(reals[4], 0.f); - QCOMPARE(reals[5], 999.f); - QCOMPARE(imags[5], 999.f); - imags[5] = 0.f; - COMPARE_ALL_TO_F(imags, 0.f); + // NB. FFTModel columns are centred on the sample frame, and in + // particular this means column 0 is centred at sample 0 (i.e. it + // contains only half the window-size worth of real samples, the + // others are 0-valued from before the origin). Generally in + // these tests we are padding our signal with half a window of + // zeros, in order that the result for column 0 is all zeros + // (rather than something with a step in it that is harder to + // reason about the FFT of) and the results for subsequent columns + // are those of our expected signal. + + void dc_simple_rect() { + MockWaveModel mwm({ DC }, 16, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 0, + { { {}, {}, {}, {}, {} } }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 1, + { { { 4.f, 0.f }, {}, {}, {}, {} } }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 2, + { { { 4.f, 0.f }, {}, {}, {}, {} } }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 3, + { { { }, {}, {}, {}, {} } }, 4); + } + + void dc_simple_hann() { + // The Hann window function is a simple sinusoid with period + // equal to twice the window size, and it halves the DC energy + MockWaveModel mwm({ DC }, 16, 4); + test(&mwm, HanningWindow, 8, 8, 8, 0, + { { {}, {}, {}, {}, {} } }, 4); + test(&mwm, HanningWindow, 8, 8, 8, 1, + { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 4); + test(&mwm, HanningWindow, 8, 8, 8, 2, + { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 4); + test(&mwm, HanningWindow, 8, 8, 8, 3, + { { { }, {}, {}, {}, {} } }, 4); } };