Chris@1086: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@1086: Chris@1086: /* Chris@1086: Sonic Visualiser Chris@1086: An audio file viewer and annotation editor. Chris@1086: Centre for Digital Music, Queen Mary, University of London. Chris@1086: Chris@1086: This program is free software; you can redistribute it and/or Chris@1086: modify it under the terms of the GNU General Public License as Chris@1086: published by the Free Software Foundation; either version 2 of the Chris@1086: License, or (at your option) any later version. See the file Chris@1086: COPYING included with this distribution for more information. Chris@1086: */ Chris@1086: Chris@1086: #ifndef TEST_FFT_MODEL_H Chris@1086: #define TEST_FFT_MODEL_H Chris@1086: Chris@1086: #include "../FFTModel.h" Chris@1086: Chris@1086: #include "MockWaveModel.h" Chris@1086: Chris@1086: #include "Compares.h" Chris@1086: Chris@1086: #include Chris@1086: #include Chris@1086: #include Chris@1086: Chris@1086: #include Chris@1088: #include Chris@1086: Chris@1086: using namespace std; Chris@1086: Chris@1086: class TestFFTModel : public QObject Chris@1086: { Chris@1086: Q_OBJECT Chris@1086: Chris@1088: private: Chris@1088: void test(DenseTimeValueModel *model, Chris@1088: WindowType window, int windowSize, int windowIncrement, int fftSize, Chris@1088: int columnNo, vector>> expectedValues, Chris@1088: int expectedWidth) { Chris@1088: for (int ch = 0; in_range_for(expectedValues, ch); ++ch) { Chris@1091: FFTModel fftm(model, ch, window, windowSize, windowIncrement, fftSize); Chris@1091: QCOMPARE(fftm.getWidth(), expectedWidth); Chris@1091: int hs1 = fftSize/2 + 1; Chris@1091: QCOMPARE(fftm.getHeight(), hs1); Chris@1091: vector reals(hs1 + 1, 0.f); Chris@1091: vector imags(hs1 + 1, 0.f); Chris@1091: reals[hs1] = 999.f; // overrun guards Chris@1091: imags[hs1] = 999.f; Chris@1099: for (int stepThrough = 0; stepThrough <= 1; ++stepThrough) { Chris@1099: if (stepThrough) { Chris@1099: // Read through the columns in order instead of Chris@1099: // randomly accessing the one we want. This is to Chris@1099: // exercise the case where the FFT model saves Chris@1099: // part of each input frame and moves along by Chris@1099: // only the non-overlapping distance Chris@1099: for (int sc = 0; sc < columnNo; ++sc) { Chris@1099: fftm.getValuesAt(sc, &reals[0], &imags[0]); Chris@1088: } Chris@1099: } Chris@1088: fftm.getValuesAt(columnNo, &reals[0], &imags[0]); Chris@1088: for (int i = 0; i < hs1; ++i) { Chris@1088: float eRe = expectedValues[ch][i].real(); Chris@1088: float eIm = expectedValues[ch][i].imag(); Chris@1099: float thresh = 1e-5f; Chris@1099: if (abs(reals[i] - eRe) > thresh || Chris@1099: abs(imags[i] - eIm) > thresh) { Chris@1099: cerr << "ERROR: output is not as expected for column " Chris@1099: << i << " in channel " << ch << " (stepThrough = " Chris@1099: << stepThrough << ")" << endl; Chris@1088: cerr << "expected : "; Chris@1088: for (int j = 0; j < hs1; ++j) { Chris@1088: cerr << expectedValues[ch][j] << " "; Chris@1088: } Chris@1088: cerr << "\nactual : "; Chris@1088: for (int j = 0; j < hs1; ++j) { Chris@1088: cerr << complex(reals[j], imags[j]) << " "; Chris@1088: } Chris@1088: cerr << endl; Chris@1088: } Chris@1110: COMPARE_FUZZIER_F(reals[i], eRe); Chris@1110: COMPARE_FUZZIER_F(imags[i], eIm); Chris@1088: } Chris@1088: QCOMPARE(reals[hs1], 999.f); Chris@1088: QCOMPARE(imags[hs1], 999.f); Chris@1088: } Chris@1088: } Chris@1088: } Chris@1089: Chris@1086: private slots: Chris@1086: Chris@1088: // NB. FFTModel columns are centred on the sample frame, and in Chris@1088: // particular this means column 0 is centred at sample 0 (i.e. it Chris@1088: // contains only half the window-size worth of real samples, the Chris@1088: // others are 0-valued from before the origin). Generally in Chris@1088: // these tests we are padding our signal with half a window of Chris@1088: // zeros, in order that the result for column 0 is all zeros Chris@1088: // (rather than something with a step in it that is harder to Chris@1088: // reason about the FFT of) and the results for subsequent columns Chris@1088: // are those of our expected signal. Chris@1089: Chris@1088: void dc_simple_rect() { Chris@1088: MockWaveModel mwm({ DC }, 16, 4); Chris@1088: test(&mwm, RectangularWindow, 8, 8, 8, 0, Chris@1088: { { {}, {}, {}, {}, {} } }, 4); Chris@1088: test(&mwm, RectangularWindow, 8, 8, 8, 1, Chris@1088: { { { 4.f, 0.f }, {}, {}, {}, {} } }, 4); Chris@1088: test(&mwm, RectangularWindow, 8, 8, 8, 2, Chris@1088: { { { 4.f, 0.f }, {}, {}, {}, {} } }, 4); Chris@1088: test(&mwm, RectangularWindow, 8, 8, 8, 3, Chris@1089: { { {}, {}, {}, {}, {} } }, 4); Chris@1088: } Chris@1088: Chris@1088: void dc_simple_hann() { Chris@1088: // The Hann window function is a simple sinusoid with period Chris@1088: // equal to twice the window size, and it halves the DC energy Chris@1088: MockWaveModel mwm({ DC }, 16, 4); Chris@1088: test(&mwm, HanningWindow, 8, 8, 8, 0, Chris@1088: { { {}, {}, {}, {}, {} } }, 4); Chris@1088: test(&mwm, HanningWindow, 8, 8, 8, 1, Chris@1088: { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 4); Chris@1088: test(&mwm, HanningWindow, 8, 8, 8, 2, Chris@1088: { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 4); Chris@1088: test(&mwm, HanningWindow, 8, 8, 8, 3, Chris@1089: { { {}, {}, {}, {}, {} } }, 4); Chris@1088: } Chris@1088: Chris@1099: void dc_simple_hann_halfoverlap() { Chris@1099: MockWaveModel mwm({ DC }, 16, 4); Chris@1099: test(&mwm, HanningWindow, 8, 4, 8, 0, Chris@1099: { { {}, {}, {}, {}, {} } }, 7); Chris@1099: test(&mwm, HanningWindow, 8, 4, 8, 2, Chris@1099: { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 7); Chris@1099: test(&mwm, HanningWindow, 8, 4, 8, 3, Chris@1099: { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 7); Chris@1099: test(&mwm, HanningWindow, 8, 4, 8, 6, Chris@1099: { { {}, {}, {}, {}, {} } }, 7); Chris@1099: } Chris@1099: Chris@1089: void sine_simple_rect() { Chris@1089: MockWaveModel mwm({ Sine }, 16, 4); Chris@1091: // Sine: output is purely imaginary. Note the sign is flipped Chris@1091: // (normally the first half of the output would have negative Chris@1091: // sign for a sine starting at 0) because the model does an Chris@1091: // FFT shift to centre the phase Chris@1089: test(&mwm, RectangularWindow, 8, 8, 8, 0, Chris@1089: { { {}, {}, {}, {}, {} } }, 4); Chris@1089: test(&mwm, RectangularWindow, 8, 8, 8, 1, Chris@1089: { { {}, { 0.f, 2.f }, {}, {}, {} } }, 4); Chris@1089: test(&mwm, RectangularWindow, 8, 8, 8, 2, Chris@1089: { { {}, { 0.f, 2.f }, {}, {}, {} } }, 4); Chris@1089: test(&mwm, RectangularWindow, 8, 8, 8, 3, Chris@1089: { { {}, {}, {}, {}, {} } }, 4); Chris@1089: } Chris@1089: Chris@1089: void cosine_simple_rect() { Chris@1089: MockWaveModel mwm({ Cosine }, 16, 4); Chris@1091: // Cosine: output is purely real. Note the sign is flipped Chris@1091: // because the model does an FFT shift to centre the phase Chris@1089: test(&mwm, RectangularWindow, 8, 8, 8, 0, Chris@1089: { { {}, {}, {}, {}, {} } }, 4); Chris@1089: test(&mwm, RectangularWindow, 8, 8, 8, 1, Chris@1091: { { {}, { -2.f, 0.f }, {}, {}, {} } }, 4); Chris@1089: test(&mwm, RectangularWindow, 8, 8, 8, 2, Chris@1091: { { {}, { -2.f, 0.f }, {}, {}, {} } }, 4); Chris@1089: test(&mwm, RectangularWindow, 8, 8, 8, 3, Chris@1089: { { {}, {}, {}, {}, {} } }, 4); Chris@1089: } Chris@1089: Chris@1104: void twochan_simple_rect() { Chris@1104: MockWaveModel mwm({ Sine, Cosine }, 16, 4); Chris@1104: // Test that the two channels are read and converted separately Chris@1104: test(&mwm, RectangularWindow, 8, 8, 8, 0, Chris@1104: { Chris@1104: { {}, {}, {}, {}, {} }, Chris@1104: { {}, {}, {}, {}, {} } Chris@1104: }, 4); Chris@1104: test(&mwm, RectangularWindow, 8, 8, 8, 1, Chris@1104: { Chris@1104: { {}, { 0.f, 2.f }, {}, {}, {} }, Chris@1104: { {}, { -2.f, 0.f }, {}, {}, {} } Chris@1104: }, 4); Chris@1104: test(&mwm, RectangularWindow, 8, 8, 8, 2, Chris@1104: { Chris@1104: { {}, { 0.f, 2.f }, {}, {}, {} }, Chris@1104: { {}, { -2.f, 0.f }, {}, {}, {} } Chris@1104: }, 4); Chris@1104: test(&mwm, RectangularWindow, 8, 8, 8, 3, Chris@1104: { Chris@1104: { {}, {}, {}, {}, {} }, Chris@1104: { {}, {}, {}, {}, {} } Chris@1104: }, 4); Chris@1104: } Chris@1104: Chris@1089: void nyquist_simple_rect() { Chris@1089: MockWaveModel mwm({ Nyquist }, 16, 4); Chris@1091: // Again, the sign is flipped. This has the same amount of Chris@1091: // energy as the DC example Chris@1089: test(&mwm, RectangularWindow, 8, 8, 8, 0, Chris@1089: { { {}, {}, {}, {}, {} } }, 4); Chris@1089: test(&mwm, RectangularWindow, 8, 8, 8, 1, Chris@1091: { { {}, {}, {}, {}, { -4.f, 0.f } } }, 4); Chris@1089: test(&mwm, RectangularWindow, 8, 8, 8, 2, Chris@1091: { { {}, {}, {}, {}, { -4.f, 0.f } } }, 4); Chris@1089: test(&mwm, RectangularWindow, 8, 8, 8, 3, Chris@1089: { { {}, {}, {}, {}, {} } }, 4); Chris@1089: } Chris@1089: Chris@1089: void dirac_simple_rect() { Chris@1089: MockWaveModel mwm({ Dirac }, 16, 4); Chris@1091: // The window scales by 0.5 and some signs are flipped. Only Chris@1091: // column 1 has any data (the single impulse). Chris@1089: test(&mwm, RectangularWindow, 8, 8, 8, 0, Chris@1089: { { {}, {}, {}, {}, {} } }, 4); Chris@1089: test(&mwm, RectangularWindow, 8, 8, 8, 1, Chris@1091: { { { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f } } }, 4); Chris@1089: test(&mwm, RectangularWindow, 8, 8, 8, 2, Chris@1091: { { {}, {}, {}, {}, {} } }, 4); Chris@1089: test(&mwm, RectangularWindow, 8, 8, 8, 3, Chris@1089: { { {}, {}, {}, {}, {} } }, 4); Chris@1089: } Chris@1091: Chris@1091: void dirac_simple_rect_2() { Chris@1091: MockWaveModel mwm({ Dirac }, 16, 8); Chris@1091: // With 8 samples padding, the FFT shift places the first Chris@1091: // Dirac impulse at the start of column 1, thus giving all Chris@1091: // positive values Chris@1091: test(&mwm, RectangularWindow, 8, 8, 8, 0, Chris@1091: { { {}, {}, {}, {}, {} } }, 5); Chris@1091: test(&mwm, RectangularWindow, 8, 8, 8, 1, Chris@1091: { { { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f } } }, 5); Chris@1091: test(&mwm, RectangularWindow, 8, 8, 8, 2, Chris@1091: { { {}, {}, {}, {}, {} } }, 5); Chris@1091: test(&mwm, RectangularWindow, 8, 8, 8, 3, Chris@1091: { { {}, {}, {}, {}, {} } }, 5); Chris@1091: test(&mwm, RectangularWindow, 8, 8, 8, 4, Chris@1091: { { {}, {}, {}, {}, {} } }, 5); Chris@1091: } Chris@1089: Chris@1099: void dirac_simple_rect_halfoverlap() { Chris@1099: MockWaveModel mwm({ Dirac }, 16, 4); Chris@1099: test(&mwm, RectangularWindow, 8, 4, 8, 0, Chris@1099: { { {}, {}, {}, {}, {} } }, 7); Chris@1099: test(&mwm, RectangularWindow, 8, 4, 8, 1, Chris@1099: { { { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f } } }, 7); Chris@1099: test(&mwm, RectangularWindow, 8, 4, 8, 2, Chris@1099: { { { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f } } }, 7); Chris@1099: test(&mwm, RectangularWindow, 8, 4, 8, 3, Chris@1099: { { {}, {}, {}, {}, {} } }, 7); Chris@1086: } Chris@1086: Chris@1086: }; Chris@1086: Chris@1086: #endif