annotate data/model/test/TestFFTModel.h @ 1752:6d09d68165a4 by-id

Further review of ById: make IDs only available when adding a model to the ById store, not by querying the item directly. This means any id encountered in the wild must have been added to the store at some point (even if later released), which simplifies reasoning about lifecycles
author Chris Cannam
date Fri, 05 Jul 2019 15:28:07 +0100
parents b92bdcd4954b
children
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@1752 93 return ModelById::add(mwm);
Chris@1744 94 }
Chris@1744 95
Chris@1744 96 void releaseMock(ModelId id) {
Chris@1744 97 ModelById::release(id);
Chris@1744 98 }
Chris@1744 99
Chris@1086 100 private slots:
Chris@1086 101
Chris@1088 102 // NB. FFTModel columns are centred on the sample frame, and in
Chris@1088 103 // particular this means column 0 is centred at sample 0 (i.e. it
Chris@1088 104 // contains only half the window-size worth of real samples, the
Chris@1088 105 // others are 0-valued from before the origin). Generally in
Chris@1088 106 // these tests we are padding our signal with half a window of
Chris@1088 107 // zeros, in order that the result for column 0 is all zeros
Chris@1088 108 // (rather than something with a step in it that is harder to
Chris@1088 109 // reason about the FFT of) and the results for subsequent columns
Chris@1088 110 // are those of our expected signal.
Chris@1089 111
Chris@1088 112 void dc_simple_rect() {
Chris@1744 113 auto mwm = makeMock({ DC }, 16, 4);
Chris@1744 114 test(mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1088 115 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 116 test(mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1088 117 { { { 4.f, 0.f }, {}, {}, {}, {} } }, 4);
Chris@1744 118 test(mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1088 119 { { { 4.f, 0.f }, {}, {}, {}, {} } }, 4);
Chris@1744 120 test(mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1089 121 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 122 releaseMock(mwm);
Chris@1088 123 }
Chris@1088 124
Chris@1088 125 void dc_simple_hann() {
Chris@1088 126 // The Hann window function is a simple sinusoid with period
Chris@1088 127 // equal to twice the window size, and it halves the DC energy
Chris@1744 128 auto mwm = makeMock({ DC }, 16, 4);
Chris@1744 129 test(mwm, HanningWindow, 8, 8, 8, 0,
Chris@1088 130 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 131 test(mwm, HanningWindow, 8, 8, 8, 1,
Chris@1088 132 { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 4);
Chris@1744 133 test(mwm, HanningWindow, 8, 8, 8, 2,
Chris@1088 134 { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 4);
Chris@1744 135 test(mwm, HanningWindow, 8, 8, 8, 3,
Chris@1089 136 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 137 releaseMock(mwm);
Chris@1088 138 }
Chris@1088 139
Chris@1099 140 void dc_simple_hann_halfoverlap() {
Chris@1744 141 auto mwm = makeMock({ DC }, 16, 4);
Chris@1744 142 test(mwm, HanningWindow, 8, 4, 8, 0,
Chris@1099 143 { { {}, {}, {}, {}, {} } }, 7);
Chris@1744 144 test(mwm, HanningWindow, 8, 4, 8, 2,
Chris@1099 145 { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 7);
Chris@1744 146 test(mwm, HanningWindow, 8, 4, 8, 3,
Chris@1099 147 { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 7);
Chris@1744 148 test(mwm, HanningWindow, 8, 4, 8, 6,
Chris@1099 149 { { {}, {}, {}, {}, {} } }, 7);
Chris@1744 150 releaseMock(mwm);
Chris@1099 151 }
Chris@1099 152
Chris@1089 153 void sine_simple_rect() {
Chris@1744 154 auto mwm = makeMock({ Sine }, 16, 4);
Chris@1091 155 // Sine: output is purely imaginary. Note the sign is flipped
Chris@1091 156 // (normally the first half of the output would have negative
Chris@1091 157 // sign for a sine starting at 0) because the model does an
Chris@1091 158 // FFT shift to centre the phase
Chris@1744 159 test(mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1089 160 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 161 test(mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1089 162 { { {}, { 0.f, 2.f }, {}, {}, {} } }, 4);
Chris@1744 163 test(mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1089 164 { { {}, { 0.f, 2.f }, {}, {}, {} } }, 4);
Chris@1744 165 test(mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1089 166 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 167 releaseMock(mwm);
Chris@1089 168 }
Chris@1089 169
Chris@1089 170 void cosine_simple_rect() {
Chris@1744 171 auto mwm = makeMock({ Cosine }, 16, 4);
Chris@1091 172 // Cosine: output is purely real. Note the sign is flipped
Chris@1091 173 // because the model does an FFT shift to centre the phase
Chris@1744 174 test(mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1089 175 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 176 test(mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1091 177 { { {}, { -2.f, 0.f }, {}, {}, {} } }, 4);
Chris@1744 178 test(mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1091 179 { { {}, { -2.f, 0.f }, {}, {}, {} } }, 4);
Chris@1744 180 test(mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1089 181 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 182 releaseMock(mwm);
Chris@1089 183 }
Chris@1089 184
Chris@1104 185 void twochan_simple_rect() {
Chris@1744 186 auto mwm = makeMock({ Sine, Cosine }, 16, 4);
Chris@1104 187 // Test that the two channels are read and converted separately
Chris@1744 188 test(mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1104 189 {
Chris@1104 190 { {}, {}, {}, {}, {} },
Chris@1104 191 { {}, {}, {}, {}, {} }
Chris@1104 192 }, 4);
Chris@1744 193 test(mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1104 194 {
Chris@1104 195 { {}, { 0.f, 2.f }, {}, {}, {} },
Chris@1104 196 { {}, { -2.f, 0.f }, {}, {}, {} }
Chris@1104 197 }, 4);
Chris@1744 198 test(mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1104 199 {
Chris@1104 200 { {}, { 0.f, 2.f }, {}, {}, {} },
Chris@1104 201 { {}, { -2.f, 0.f }, {}, {}, {} }
Chris@1104 202 }, 4);
Chris@1744 203 test(mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1104 204 {
Chris@1104 205 { {}, {}, {}, {}, {} },
Chris@1104 206 { {}, {}, {}, {}, {} }
Chris@1104 207 }, 4);
Chris@1744 208 releaseMock(mwm);
Chris@1104 209 }
Chris@1104 210
Chris@1089 211 void nyquist_simple_rect() {
Chris@1744 212 auto mwm = makeMock({ Nyquist }, 16, 4);
Chris@1091 213 // Again, the sign is flipped. This has the same amount of
Chris@1091 214 // energy as the DC example
Chris@1744 215 test(mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1089 216 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 217 test(mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1091 218 { { {}, {}, {}, {}, { -4.f, 0.f } } }, 4);
Chris@1744 219 test(mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1091 220 { { {}, {}, {}, {}, { -4.f, 0.f } } }, 4);
Chris@1744 221 test(mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1089 222 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 223 releaseMock(mwm);
Chris@1089 224 }
Chris@1089 225
Chris@1089 226 void dirac_simple_rect() {
Chris@1744 227 auto mwm = makeMock({ Dirac }, 16, 4);
Chris@1091 228 // The window scales by 0.5 and some signs are flipped. Only
Chris@1091 229 // column 1 has any data (the single impulse).
Chris@1744 230 test(mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1089 231 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 232 test(mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1091 233 { { { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f } } }, 4);
Chris@1744 234 test(mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1091 235 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 236 test(mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1089 237 { { {}, {}, {}, {}, {} } }, 4);
Chris@1744 238 releaseMock(mwm);
Chris@1089 239 }
Chris@1091 240
Chris@1091 241 void dirac_simple_rect_2() {
Chris@1744 242 auto mwm = makeMock({ Dirac }, 16, 8);
Chris@1091 243 // With 8 samples padding, the FFT shift places the first
Chris@1091 244 // Dirac impulse at the start of column 1, thus giving all
Chris@1091 245 // positive values
Chris@1744 246 test(mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1091 247 { { {}, {}, {}, {}, {} } }, 5);
Chris@1744 248 test(mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1091 249 { { { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f } } }, 5);
Chris@1744 250 test(mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1091 251 { { {}, {}, {}, {}, {} } }, 5);
Chris@1744 252 test(mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1091 253 { { {}, {}, {}, {}, {} } }, 5);
Chris@1744 254 test(mwm, RectangularWindow, 8, 8, 8, 4,
Chris@1091 255 { { {}, {}, {}, {}, {} } }, 5);
Chris@1744 256 releaseMock(mwm);
Chris@1091 257 }
Chris@1089 258
Chris@1099 259 void dirac_simple_rect_halfoverlap() {
Chris@1744 260 auto mwm = makeMock({ Dirac }, 16, 4);
Chris@1744 261 test(mwm, RectangularWindow, 8, 4, 8, 0,
Chris@1099 262 { { {}, {}, {}, {}, {} } }, 7);
Chris@1744 263 test(mwm, RectangularWindow, 8, 4, 8, 1,
Chris@1099 264 { { { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f } } }, 7);
Chris@1744 265 test(mwm, RectangularWindow, 8, 4, 8, 2,
Chris@1099 266 { { { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f } } }, 7);
Chris@1744 267 test(mwm, RectangularWindow, 8, 4, 8, 3,
Chris@1099 268 { { {}, {}, {}, {}, {} } }, 7);
Chris@1744 269 releaseMock(mwm);
Chris@1086 270 }
Chris@1086 271
Chris@1086 272 };
Chris@1086 273
Chris@1086 274 #endif