changeset 1088:5fab8e4f5f19

Start making the FFT model tests into proper tests
author Chris Cannam
date Fri, 12 Jun 2015 12:41:19 +0100
parents dcf54a6964d0
children 655cd4e68e9a a27b1ce86e4f 1517d4c60e88
files data/fft/FFTDataServer.cpp data/model/test/MockWaveModel.cpp data/model/test/MockWaveModel.h data/model/test/TestFFTModel.h
diffstat 4 files changed, 97 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- 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 "
--- 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<Sort> sorts, int length)
+MockWaveModel::MockWaveModel(vector<Sort> 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<float>
-MockWaveModel::generate(Sort sort, int length) const
+MockWaveModel::generate(Sort sort, int length, int pad) const
 {
     vector<float> 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;
 }
 
--- 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<Sort> 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<Sort> 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<std::vector<float> > m_data;
-    std::vector<float> generate(Sort sort, int length) const;
+    std::vector<float> generate(Sort sort, int length, int pad) const;
 };
 
 #endif
--- 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 <QDir>
 
 #include <iostream>
+#include <complex>
 
 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<vector<complex<float>>> 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<float> reals(hs1 + 1, 0.f);
+                vector<float> 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<float>(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);
     }
     
 };