annotate data/model/test/TestFFTModel.h @ 1520:954d0cf29ca7 import-audio-data

Switch the normalisation option in WritableWaveFileModel from normalising on read to normalising on write, so that the saved file is already normalised and therefore can be read again without having to remember to normalise it
author Chris Cannam
date Wed, 12 Sep 2018 13:56:56 +0100
parents 48e9f538e6e9
children c170b8d0433c
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@1088 38 void test(DenseTimeValueModel *model,
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@1086 91 private slots:
Chris@1086 92
Chris@1088 93 // NB. FFTModel columns are centred on the sample frame, and in
Chris@1088 94 // particular this means column 0 is centred at sample 0 (i.e. it
Chris@1088 95 // contains only half the window-size worth of real samples, the
Chris@1088 96 // others are 0-valued from before the origin). Generally in
Chris@1088 97 // these tests we are padding our signal with half a window of
Chris@1088 98 // zeros, in order that the result for column 0 is all zeros
Chris@1088 99 // (rather than something with a step in it that is harder to
Chris@1088 100 // reason about the FFT of) and the results for subsequent columns
Chris@1088 101 // are those of our expected signal.
Chris@1089 102
Chris@1088 103 void dc_simple_rect() {
Chris@1429 104 MockWaveModel mwm({ DC }, 16, 4);
Chris@1088 105 test(&mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1088 106 { { {}, {}, {}, {}, {} } }, 4);
Chris@1088 107 test(&mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1088 108 { { { 4.f, 0.f }, {}, {}, {}, {} } }, 4);
Chris@1088 109 test(&mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1088 110 { { { 4.f, 0.f }, {}, {}, {}, {} } }, 4);
Chris@1088 111 test(&mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1089 112 { { {}, {}, {}, {}, {} } }, 4);
Chris@1088 113 }
Chris@1088 114
Chris@1088 115 void dc_simple_hann() {
Chris@1088 116 // The Hann window function is a simple sinusoid with period
Chris@1088 117 // equal to twice the window size, and it halves the DC energy
Chris@1429 118 MockWaveModel mwm({ DC }, 16, 4);
Chris@1088 119 test(&mwm, HanningWindow, 8, 8, 8, 0,
Chris@1088 120 { { {}, {}, {}, {}, {} } }, 4);
Chris@1088 121 test(&mwm, HanningWindow, 8, 8, 8, 1,
Chris@1088 122 { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 4);
Chris@1088 123 test(&mwm, HanningWindow, 8, 8, 8, 2,
Chris@1088 124 { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 4);
Chris@1088 125 test(&mwm, HanningWindow, 8, 8, 8, 3,
Chris@1089 126 { { {}, {}, {}, {}, {} } }, 4);
Chris@1088 127 }
Chris@1088 128
Chris@1099 129 void dc_simple_hann_halfoverlap() {
Chris@1429 130 MockWaveModel mwm({ DC }, 16, 4);
Chris@1099 131 test(&mwm, HanningWindow, 8, 4, 8, 0,
Chris@1099 132 { { {}, {}, {}, {}, {} } }, 7);
Chris@1099 133 test(&mwm, HanningWindow, 8, 4, 8, 2,
Chris@1099 134 { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 7);
Chris@1099 135 test(&mwm, HanningWindow, 8, 4, 8, 3,
Chris@1099 136 { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 7);
Chris@1099 137 test(&mwm, HanningWindow, 8, 4, 8, 6,
Chris@1099 138 { { {}, {}, {}, {}, {} } }, 7);
Chris@1099 139 }
Chris@1099 140
Chris@1089 141 void sine_simple_rect() {
Chris@1429 142 MockWaveModel mwm({ Sine }, 16, 4);
Chris@1091 143 // Sine: output is purely imaginary. Note the sign is flipped
Chris@1091 144 // (normally the first half of the output would have negative
Chris@1091 145 // sign for a sine starting at 0) because the model does an
Chris@1091 146 // FFT shift to centre the phase
Chris@1089 147 test(&mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1089 148 { { {}, {}, {}, {}, {} } }, 4);
Chris@1089 149 test(&mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1089 150 { { {}, { 0.f, 2.f }, {}, {}, {} } }, 4);
Chris@1089 151 test(&mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1089 152 { { {}, { 0.f, 2.f }, {}, {}, {} } }, 4);
Chris@1089 153 test(&mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1089 154 { { {}, {}, {}, {}, {} } }, 4);
Chris@1089 155 }
Chris@1089 156
Chris@1089 157 void cosine_simple_rect() {
Chris@1429 158 MockWaveModel mwm({ Cosine }, 16, 4);
Chris@1091 159 // Cosine: output is purely real. Note the sign is flipped
Chris@1091 160 // because the model does an FFT shift to centre the phase
Chris@1089 161 test(&mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1089 162 { { {}, {}, {}, {}, {} } }, 4);
Chris@1089 163 test(&mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1091 164 { { {}, { -2.f, 0.f }, {}, {}, {} } }, 4);
Chris@1089 165 test(&mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1091 166 { { {}, { -2.f, 0.f }, {}, {}, {} } }, 4);
Chris@1089 167 test(&mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1089 168 { { {}, {}, {}, {}, {} } }, 4);
Chris@1089 169 }
Chris@1089 170
Chris@1104 171 void twochan_simple_rect() {
Chris@1429 172 MockWaveModel mwm({ Sine, Cosine }, 16, 4);
Chris@1104 173 // Test that the two channels are read and converted separately
Chris@1104 174 test(&mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1104 175 {
Chris@1104 176 { {}, {}, {}, {}, {} },
Chris@1104 177 { {}, {}, {}, {}, {} }
Chris@1104 178 }, 4);
Chris@1104 179 test(&mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1104 180 {
Chris@1104 181 { {}, { 0.f, 2.f }, {}, {}, {} },
Chris@1104 182 { {}, { -2.f, 0.f }, {}, {}, {} }
Chris@1104 183 }, 4);
Chris@1104 184 test(&mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1104 185 {
Chris@1104 186 { {}, { 0.f, 2.f }, {}, {}, {} },
Chris@1104 187 { {}, { -2.f, 0.f }, {}, {}, {} }
Chris@1104 188 }, 4);
Chris@1104 189 test(&mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1104 190 {
Chris@1104 191 { {}, {}, {}, {}, {} },
Chris@1104 192 { {}, {}, {}, {}, {} }
Chris@1104 193 }, 4);
Chris@1104 194 }
Chris@1104 195
Chris@1089 196 void nyquist_simple_rect() {
Chris@1429 197 MockWaveModel mwm({ Nyquist }, 16, 4);
Chris@1091 198 // Again, the sign is flipped. This has the same amount of
Chris@1091 199 // energy as the DC example
Chris@1089 200 test(&mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1089 201 { { {}, {}, {}, {}, {} } }, 4);
Chris@1089 202 test(&mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1091 203 { { {}, {}, {}, {}, { -4.f, 0.f } } }, 4);
Chris@1089 204 test(&mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1091 205 { { {}, {}, {}, {}, { -4.f, 0.f } } }, 4);
Chris@1089 206 test(&mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1089 207 { { {}, {}, {}, {}, {} } }, 4);
Chris@1089 208 }
Chris@1089 209
Chris@1089 210 void dirac_simple_rect() {
Chris@1429 211 MockWaveModel mwm({ Dirac }, 16, 4);
Chris@1091 212 // The window scales by 0.5 and some signs are flipped. Only
Chris@1091 213 // column 1 has any data (the single impulse).
Chris@1089 214 test(&mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1089 215 { { {}, {}, {}, {}, {} } }, 4);
Chris@1089 216 test(&mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1091 217 { { { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f } } }, 4);
Chris@1089 218 test(&mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1091 219 { { {}, {}, {}, {}, {} } }, 4);
Chris@1089 220 test(&mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1089 221 { { {}, {}, {}, {}, {} } }, 4);
Chris@1089 222 }
Chris@1091 223
Chris@1091 224 void dirac_simple_rect_2() {
Chris@1429 225 MockWaveModel mwm({ Dirac }, 16, 8);
Chris@1091 226 // With 8 samples padding, the FFT shift places the first
Chris@1091 227 // Dirac impulse at the start of column 1, thus giving all
Chris@1091 228 // positive values
Chris@1091 229 test(&mwm, RectangularWindow, 8, 8, 8, 0,
Chris@1091 230 { { {}, {}, {}, {}, {} } }, 5);
Chris@1091 231 test(&mwm, RectangularWindow, 8, 8, 8, 1,
Chris@1091 232 { { { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f } } }, 5);
Chris@1091 233 test(&mwm, RectangularWindow, 8, 8, 8, 2,
Chris@1091 234 { { {}, {}, {}, {}, {} } }, 5);
Chris@1091 235 test(&mwm, RectangularWindow, 8, 8, 8, 3,
Chris@1091 236 { { {}, {}, {}, {}, {} } }, 5);
Chris@1091 237 test(&mwm, RectangularWindow, 8, 8, 8, 4,
Chris@1091 238 { { {}, {}, {}, {}, {} } }, 5);
Chris@1091 239 }
Chris@1089 240
Chris@1099 241 void dirac_simple_rect_halfoverlap() {
Chris@1429 242 MockWaveModel mwm({ Dirac }, 16, 4);
Chris@1099 243 test(&mwm, RectangularWindow, 8, 4, 8, 0,
Chris@1099 244 { { {}, {}, {}, {}, {} } }, 7);
Chris@1099 245 test(&mwm, RectangularWindow, 8, 4, 8, 1,
Chris@1099 246 { { { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f } } }, 7);
Chris@1099 247 test(&mwm, RectangularWindow, 8, 4, 8, 2,
Chris@1099 248 { { { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f } } }, 7);
Chris@1099 249 test(&mwm, RectangularWindow, 8, 4, 8, 3,
Chris@1099 250 { { {}, {}, {}, {}, {} } }, 7);
Chris@1086 251 }
Chris@1086 252
Chris@1086 253 };
Chris@1086 254
Chris@1086 255 #endif