annotate data/model/test/TestFFTModel.h @ 1744:b92bdcd4954b by-id

Update FFT model to ById
author Chris Cannam
date Tue, 02 Jul 2019 11:49:28 +0100
parents 0e9840a381b5
children 6d09d68165a4
rev   line source
Chris@1086 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@1086 2
Chris@1086 3 /*
Chris@1086 4 Sonic Visualiser
Chris@1086 5 An audio file viewer and annotation editor.
Chris@1086 6 Centre for Digital Music, Queen Mary, University of London.
Chris@1086 7
Chris@1086 8 This program is free software; you can redistribute it and/or
Chris@1086 9 modify it under the terms of the GNU General Public License as
Chris@1086 10 published by the Free Software Foundation; either version 2 of the
Chris@1086 11 License, or (at your option) any later version. See the file
Chris@1086 12 COPYING included with this distribution for more information.
Chris@1086 13 */
Chris@1086 14
Chris@1086 15 #ifndef TEST_FFT_MODEL_H
Chris@1086 16 #define TEST_FFT_MODEL_H
Chris@1086 17
Chris@1086 18 #include "../FFTModel.h"
Chris@1086 19
Chris@1086 20 #include "MockWaveModel.h"
Chris@1086 21
Chris@1086 22 #include "Compares.h"
Chris@1086 23
Chris@1086 24 #include <QObject>
Chris@1086 25 #include <QtTest>
Chris@1086 26 #include <QDir>
Chris@1086 27
Chris@1086 28 #include <iostream>
Chris@1088 29 #include <complex>
Chris@1086 30
Chris@1086 31 using namespace std;
Chris@1086 32
Chris@1086 33 class TestFFTModel : public QObject
Chris@1086 34 {
Chris@1086 35 Q_OBJECT
Chris@1086 36
Chris@1088 37 private:
Chris@1744 38 void test(ModelId model, // a DenseTimeValueModel
Chris@1088 39 WindowType window, int windowSize, int windowIncrement, int fftSize,
Chris@1088 40 int columnNo, vector<vector<complex<float>>> expectedValues,
Chris@1088 41 int expectedWidth) {
Chris@1088 42 for (int ch = 0; in_range_for(expectedValues, ch); ++ch) {
Chris@1091 43 FFTModel fftm(model, ch, window, windowSize, windowIncrement, fftSize);
Chris@1091 44 QCOMPARE(fftm.getWidth(), expectedWidth);
Chris@1091 45 int hs1 = fftSize/2 + 1;
Chris@1091 46 QCOMPARE(fftm.getHeight(), hs1);
Chris@1091 47 vector<float> reals(hs1 + 1, 0.f);
Chris@1091 48 vector<float> imags(hs1 + 1, 0.f);
Chris@1091 49 reals[hs1] = 999.f; // overrun guards
Chris@1091 50 imags[hs1] = 999.f;
Chris@1099 51 for (int stepThrough = 0; stepThrough <= 1; ++stepThrough) {
Chris@1099 52 if (stepThrough) {
Chris@1099 53 // Read through the columns in order instead of
Chris@1099 54 // randomly accessing the one we want. This is to
Chris@1099 55 // exercise the case where the FFT model saves
Chris@1099 56 // part of each input frame and moves along by
Chris@1099 57 // only the non-overlapping distance
Chris@1099 58 for (int sc = 0; sc < columnNo; ++sc) {
Chris@1099 59 fftm.getValuesAt(sc, &reals[0], &imags[0]);
Chris@1088 60 }
Chris@1099 61 }
Chris@1088 62 fftm.getValuesAt(columnNo, &reals[0], &imags[0]);
Chris@1088 63 for (int i = 0; i < hs1; ++i) {
Chris@1088 64 float eRe = expectedValues[ch][i].real();
Chris@1088 65 float eIm = expectedValues[ch][i].imag();
Chris@1099 66 float thresh = 1e-5f;
Chris@1099 67 if (abs(reals[i] - eRe) > thresh ||
Chris@1099 68 abs(imags[i] - eIm) > thresh) {
Chris@1428 69 SVCERR << "ERROR: output is not as expected for column "
Chris@1099 70 << i << " in channel " << ch << " (stepThrough = "
Chris@1099 71 << stepThrough << ")" << endl;
Chris@1428 72 SVCERR << "expected : ";
Chris@1088 73 for (int j = 0; j < hs1; ++j) {
Chris@1428 74 SVCERR << expectedValues[ch][j] << " ";
Chris@1088 75 }
Chris@1428 76 SVCERR << "\nactual : ";
Chris@1088 77 for (int j = 0; j < hs1; ++j) {
Chris@1428 78 SVCERR << complex<float>(reals[j], imags[j]) << " ";
Chris@1088 79 }
Chris@1428 80 SVCERR << endl;
Chris@1088 81 }
Chris@1110 82 COMPARE_FUZZIER_F(reals[i], eRe);
Chris@1110 83 COMPARE_FUZZIER_F(imags[i], eIm);
Chris@1088 84 }
Chris@1088 85 QCOMPARE(reals[hs1], 999.f);
Chris@1088 86 QCOMPARE(imags[hs1], 999.f);
Chris@1088 87 }
Chris@1088 88 }
Chris@1088 89 }
Chris@1089 90
Chris@1744 91 ModelId makeMock(std::vector<Sort> sorts, int length, int pad) {
Chris@1744 92 auto mwm = std::make_shared<MockWaveModel>(sorts, length, pad);
Chris@1744 93 ModelById::add(mwm);
Chris@1744 94 return mwm->getId();
Chris@1744 95 }
Chris@1744 96
Chris@1744 97 void releaseMock(ModelId id) {
Chris@1744 98 ModelById::release(id);
Chris@1744 99 }
Chris@1744 100
Chris@1086 101 private slots:
Chris@1086 102
Chris@1088 103 // NB. FFTModel columns are centred on the sample frame, and in
Chris@1088 104 // particular this means column 0 is centred at sample 0 (i.e. it
Chris@1088 105 // contains only half the window-size worth of real samples, the
Chris@1088 106 // others are 0-valued from before the origin). Generally in
Chris@1088 107 // these tests we are padding our signal with half a window of
Chris@1088 108 // zeros, in order that the result for column 0 is all zeros
Chris@1088 109 // (rather than something with a step in it that is harder to
Chris@1088 110 // reason about the FFT of) and the results for subsequent columns
Chris@1088 111 // are those of our expected signal.
Chris@1089 112
Chris@1088 113 void dc_simple_rect() {
Chris@1744 114 auto mwm = makeMock({ DC }, 16, 4);
Chris@1744 115 test(mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1088 116 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 117 test(mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1088 118 { { { 4.f, 0.f }, {}, {}, {}, {} } }, 4);
Chris@1744 119 test(mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1088 120 { { { 4.f, 0.f }, {}, {}, {}, {} } }, 4);
Chris@1744 121 test(mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1089 122 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 123 releaseMock(mwm);
Chris@1088 124 }
Chris@1088 125
Chris@1088 126 void dc_simple_hann() {
Chris@1088 127 // The Hann window function is a simple sinusoid with period
Chris@1088 128 // equal to twice the window size, and it halves the DC energy
Chris@1744 129 auto mwm = makeMock({ DC }, 16, 4);
Chris@1744 130 test(mwm, HanningWindow, 8, 8, 8, 0,
Chris@1088 131 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 132 test(mwm, HanningWindow, 8, 8, 8, 1,
Chris@1088 133 { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 4);
Chris@1744 134 test(mwm, HanningWindow, 8, 8, 8, 2,
Chris@1088 135 { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 4);
Chris@1744 136 test(mwm, HanningWindow, 8, 8, 8, 3,
Chris@1089 137 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 138 releaseMock(mwm);
Chris@1088 139 }
Chris@1088 140
Chris@1099 141 void dc_simple_hann_halfoverlap() {
Chris@1744 142 auto mwm = makeMock({ DC }, 16, 4);
Chris@1744 143 test(mwm, HanningWindow, 8, 4, 8, 0,
Chris@1099 144 { { {}, {}, {}, {}, {} } }, 7);
Chris@1744 145 test(mwm, HanningWindow, 8, 4, 8, 2,
Chris@1099 146 { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 7);
Chris@1744 147 test(mwm, HanningWindow, 8, 4, 8, 3,
Chris@1099 148 { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 7);
Chris@1744 149 test(mwm, HanningWindow, 8, 4, 8, 6,
Chris@1099 150 { { {}, {}, {}, {}, {} } }, 7);
Chris@1744 151 releaseMock(mwm);
Chris@1099 152 }
Chris@1099 153
Chris@1089 154 void sine_simple_rect() {
Chris@1744 155 auto mwm = makeMock({ Sine }, 16, 4);
Chris@1091 156 // Sine: output is purely imaginary. Note the sign is flipped
Chris@1091 157 // (normally the first half of the output would have negative
Chris@1091 158 // sign for a sine starting at 0) because the model does an
Chris@1091 159 // FFT shift to centre the phase
Chris@1744 160 test(mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1089 161 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 162 test(mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1089 163 { { {}, { 0.f, 2.f }, {}, {}, {} } }, 4);
Chris@1744 164 test(mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1089 165 { { {}, { 0.f, 2.f }, {}, {}, {} } }, 4);
Chris@1744 166 test(mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1089 167 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 168 releaseMock(mwm);
Chris@1089 169 }
Chris@1089 170
Chris@1089 171 void cosine_simple_rect() {
Chris@1744 172 auto mwm = makeMock({ Cosine }, 16, 4);
Chris@1091 173 // Cosine: output is purely real. Note the sign is flipped
Chris@1091 174 // because the model does an FFT shift to centre the phase
Chris@1744 175 test(mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1089 176 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 177 test(mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1091 178 { { {}, { -2.f, 0.f }, {}, {}, {} } }, 4);
Chris@1744 179 test(mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1091 180 { { {}, { -2.f, 0.f }, {}, {}, {} } }, 4);
Chris@1744 181 test(mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1089 182 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 183 releaseMock(mwm);
Chris@1089 184 }
Chris@1089 185
Chris@1104 186 void twochan_simple_rect() {
Chris@1744 187 auto mwm = makeMock({ Sine, Cosine }, 16, 4);
Chris@1104 188 // Test that the two channels are read and converted separately
Chris@1744 189 test(mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1104 190 {
Chris@1104 191 { {}, {}, {}, {}, {} },
Chris@1104 192 { {}, {}, {}, {}, {} }
Chris@1104 193 }, 4);
Chris@1744 194 test(mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1104 195 {
Chris@1104 196 { {}, { 0.f, 2.f }, {}, {}, {} },
Chris@1104 197 { {}, { -2.f, 0.f }, {}, {}, {} }
Chris@1104 198 }, 4);
Chris@1744 199 test(mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1104 200 {
Chris@1104 201 { {}, { 0.f, 2.f }, {}, {}, {} },
Chris@1104 202 { {}, { -2.f, 0.f }, {}, {}, {} }
Chris@1104 203 }, 4);
Chris@1744 204 test(mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1104 205 {
Chris@1104 206 { {}, {}, {}, {}, {} },
Chris@1104 207 { {}, {}, {}, {}, {} }
Chris@1104 208 }, 4);
Chris@1744 209 releaseMock(mwm);
Chris@1104 210 }
Chris@1104 211
Chris@1089 212 void nyquist_simple_rect() {
Chris@1744 213 auto mwm = makeMock({ Nyquist }, 16, 4);
Chris@1091 214 // Again, the sign is flipped. This has the same amount of
Chris@1091 215 // energy as the DC example
Chris@1744 216 test(mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1089 217 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 218 test(mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1091 219 { { {}, {}, {}, {}, { -4.f, 0.f } } }, 4);
Chris@1744 220 test(mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1091 221 { { {}, {}, {}, {}, { -4.f, 0.f } } }, 4);
Chris@1744 222 test(mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1089 223 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 224 releaseMock(mwm);
Chris@1089 225 }
Chris@1089 226
Chris@1089 227 void dirac_simple_rect() {
Chris@1744 228 auto mwm = makeMock({ Dirac }, 16, 4);
Chris@1091 229 // The window scales by 0.5 and some signs are flipped. Only
Chris@1091 230 // column 1 has any data (the single impulse).
Chris@1744 231 test(mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1089 232 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 233 test(mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1091 234 { { { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f } } }, 4);
Chris@1744 235 test(mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1091 236 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 237 test(mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1089 238 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 239 releaseMock(mwm);
Chris@1089 240 }
Chris@1091 241
Chris@1091 242 void dirac_simple_rect_2() {
Chris@1744 243 auto mwm = makeMock({ Dirac }, 16, 8);
Chris@1091 244 // With 8 samples padding, the FFT shift places the first
Chris@1091 245 // Dirac impulse at the start of column 1, thus giving all
Chris@1091 246 // positive values
Chris@1744 247 test(mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1091 248 { { {}, {}, {}, {}, {} } }, 5);
Chris@1744 249 test(mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1091 250 { { { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f } } }, 5);
Chris@1744 251 test(mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1091 252 { { {}, {}, {}, {}, {} } }, 5);
Chris@1744 253 test(mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1091 254 { { {}, {}, {}, {}, {} } }, 5);
Chris@1744 255 test(mwm, RectangularWindow, 8, 8, 8, 4,
Chris@1091 256 { { {}, {}, {}, {}, {} } }, 5);
Chris@1744 257 releaseMock(mwm);
Chris@1091 258 }
Chris@1089 259
Chris@1099 260 void dirac_simple_rect_halfoverlap() {
Chris@1744 261 auto mwm = makeMock({ Dirac }, 16, 4);
Chris@1744 262 test(mwm, RectangularWindow, 8, 4, 8, 0,
Chris@1099 263 { { {}, {}, {}, {}, {} } }, 7);
Chris@1744 264 test(mwm, RectangularWindow, 8, 4, 8, 1,
Chris@1099 265 { { { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f } } }, 7);
Chris@1744 266 test(mwm, RectangularWindow, 8, 4, 8, 2,
Chris@1099 267 { { { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f } } }, 7);
Chris@1744 268 test(mwm, RectangularWindow, 8, 4, 8, 3,
Chris@1099 269 { { {}, {}, {}, {}, {} } }, 7);
Chris@1744 270 releaseMock(mwm);
Chris@1086 271 }
Chris@1086 272
Chris@1086 273 };
Chris@1086 274
Chris@1086 275 #endif