File: TestFFTModel.h

package info (click to toggle)
sonic-visualiser 5.2.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 24,744 kB
  • sloc: cpp: 158,888; ansic: 11,920; sh: 1,785; makefile: 517; xml: 64; perl: 31
file content (275 lines) | stat: -rw-r--r-- 11,092 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

/*
    Sonic Visualiser
    An audio file viewer and annotation editor.
    Centre for Digital Music, Queen Mary, University of London.
    
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/

#ifndef TEST_FFT_MODEL_H
#define TEST_FFT_MODEL_H

#include "../FFTModel.h"

#include "MockWaveModel.h"

#include "Compares.h"

#include <QObject>
#include <QtTest>
#include <QDir>

#include <iostream>
#include <complex>

using namespace std;
using namespace sv;

class TestFFTModel : public QObject
{
    Q_OBJECT

private:
    void test(ModelId model, // a DenseTimeValueModel
              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) {
            FFTModel fftm(model, ch, window, windowSize, windowIncrement, fftSize);
            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;
            for (int stepThrough = 0; stepThrough <= 1; ++stepThrough) {
                if (stepThrough) {
                    // Read through the columns in order instead of
                    // randomly accessing the one we want. This is to
                    // exercise the case where the FFT model saves
                    // part of each input frame and moves along by
                    // only the non-overlapping distance
                    for (int sc = 0; sc < columnNo; ++sc) {
                        fftm.getValuesAt(sc, &reals[0], &imags[0]);
                    }
                }
                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();
                    float thresh = 1e-5f;
                    if (abs(reals[i] - eRe) > thresh ||
                        abs(imags[i] - eIm) > thresh) {
                        SVCERR << "ERROR: output is not as expected for column "
                             << i << " in channel " << ch << " (stepThrough = "
                             << stepThrough << ")" << endl;
                        SVCERR << "expected : ";
                        for (int j = 0; j < hs1; ++j) {
                            SVCERR << expectedValues[ch][j] << " ";
                        }
                        SVCERR << "\nactual   : ";
                        for (int j = 0; j < hs1; ++j) {
                            SVCERR << complex<float>(reals[j], imags[j]) << " ";
                        }
                        SVCERR << endl;
                    }
                    COMPARE_FUZZIER_F(reals[i], eRe);
                    COMPARE_FUZZIER_F(imags[i], eIm);
                }
                QCOMPARE(reals[hs1], 999.f);
                QCOMPARE(imags[hs1], 999.f);
            }
        }
    }

    ModelId makeMock(std::vector<Sort> sorts, int length, int pad) {
        auto mwm = std::make_shared<MockWaveModel>(sorts, length, pad);
        return ModelById::add(mwm);
    }

    void releaseMock(ModelId id) {
        ModelById::release(id);
    }

private slots:

    // 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() {
        auto mwm = makeMock({ 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);
        releaseMock(mwm);
    }

    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
        auto mwm = makeMock({ 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);
        releaseMock(mwm);
    }
    
    void dc_simple_hann_halfoverlap() {
        auto mwm = makeMock({ DC }, 16, 4);
        test(mwm, HanningWindow, 8, 4, 8, 0,
             { { {}, {}, {}, {}, {} } }, 7);
        test(mwm, HanningWindow, 8, 4, 8, 2,
             { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 7);
        test(mwm, HanningWindow, 8, 4, 8, 3,
             { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 7);
        test(mwm, HanningWindow, 8, 4, 8, 6,
             { { {}, {}, {}, {}, {} } }, 7);
        releaseMock(mwm);
    }
    
    void sine_simple_rect() {
        auto mwm = makeMock({ Sine }, 16, 4);
        // Sine: output is purely imaginary. Note the sign is flipped
        // (normally the first half of the output would have negative
        // sign for a sine starting at 0) because the model does an
        // FFT shift to centre the phase
        test(mwm, RectangularWindow, 8, 8, 8, 0,
             { { {}, {}, {}, {}, {} } }, 4);
        test(mwm, RectangularWindow, 8, 8, 8, 1,
             { { {}, { 0.f, 2.f }, {}, {}, {} } }, 4);
        test(mwm, RectangularWindow, 8, 8, 8, 2,
             { { {}, { 0.f, 2.f }, {}, {}, {} } }, 4);
        test(mwm, RectangularWindow, 8, 8, 8, 3,
             { { {}, {}, {}, {}, {} } }, 4);
        releaseMock(mwm);
    }
    
    void cosine_simple_rect() {
        auto mwm = makeMock({ Cosine }, 16, 4);
        // Cosine: output is purely real. Note the sign is flipped
        // because the model does an FFT shift to centre the phase
        test(mwm, RectangularWindow, 8, 8, 8, 0,
             { { {}, {}, {}, {}, {} } }, 4);
        test(mwm, RectangularWindow, 8, 8, 8, 1,
             { { {}, { -2.f, 0.f }, {}, {}, {} } }, 4);
        test(mwm, RectangularWindow, 8, 8, 8, 2,
             { { {}, { -2.f, 0.f }, {}, {}, {} } }, 4);
        test(mwm, RectangularWindow, 8, 8, 8, 3,
             { { {}, {}, {}, {}, {} } }, 4);
        releaseMock(mwm);
    }
    
    void twochan_simple_rect() {
        auto mwm = makeMock({ Sine, Cosine }, 16, 4);
        // Test that the two channels are read and converted separately
        test(mwm, RectangularWindow, 8, 8, 8, 0,
             {
                 { {}, {}, {}, {}, {} },
                 { {}, {}, {}, {}, {} }
             }, 4);
        test(mwm, RectangularWindow, 8, 8, 8, 1,
             {
                 { {}, {  0.f, 2.f }, {}, {}, {} },
                 { {}, { -2.f, 0.f }, {}, {}, {} }
             }, 4);
        test(mwm, RectangularWindow, 8, 8, 8, 2,
             {
                 { {}, {  0.f, 2.f }, {}, {}, {} },
                 { {}, { -2.f, 0.f }, {}, {}, {} }
             }, 4);
        test(mwm, RectangularWindow, 8, 8, 8, 3,
             {
                 { {}, {}, {}, {}, {} },
                 { {}, {}, {}, {}, {} }
             }, 4);
        releaseMock(mwm);
    }
    
    void nyquist_simple_rect() {
        auto mwm = makeMock({ Nyquist }, 16, 4);
        // Again, the sign is flipped. This has the same amount of
        // energy as the DC example
        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);
        releaseMock(mwm);
    }
    
    void dirac_simple_rect() {
        auto mwm = makeMock({ Dirac }, 16, 4);
        // The window scales by 0.5 and some signs are flipped. Only
        // column 1 has any data (the single impulse).
        test(mwm, RectangularWindow, 8, 8, 8, 0,
             { { {}, {}, {}, {}, {} } }, 4);
        test(mwm, RectangularWindow, 8, 8, 8, 1,
             { { { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f } } }, 4);
        test(mwm, RectangularWindow, 8, 8, 8, 2,
             { { {}, {}, {}, {}, {} } }, 4);
        test(mwm, RectangularWindow, 8, 8, 8, 3,
             { { {}, {}, {}, {}, {} } }, 4);
        releaseMock(mwm);
    }
    
    void dirac_simple_rect_2() {
        auto mwm = makeMock({ Dirac }, 16, 8);
        // With 8 samples padding, the FFT shift places the first
        // Dirac impulse at the start of column 1, thus giving all
        // positive values
        test(mwm, RectangularWindow, 8, 8, 8, 0,
             { { {}, {}, {}, {}, {} } }, 5);
        test(mwm, RectangularWindow, 8, 8, 8, 1,
             { { { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f } } }, 5);
        test(mwm, RectangularWindow, 8, 8, 8, 2,
             { { {}, {}, {}, {}, {} } }, 5);
        test(mwm, RectangularWindow, 8, 8, 8, 3,
             { { {}, {}, {}, {}, {} } }, 5);
        test(mwm, RectangularWindow, 8, 8, 8, 4,
             { { {}, {}, {}, {}, {} } }, 5);
        releaseMock(mwm);
    }

    void dirac_simple_rect_halfoverlap() {
        auto mwm = makeMock({ Dirac }, 16, 4);
        test(mwm, RectangularWindow, 8, 4, 8, 0,
             { { {}, {}, {}, {}, {} } }, 7);
        test(mwm, RectangularWindow, 8, 4, 8, 1,
             { { { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f } } }, 7);
        test(mwm, RectangularWindow, 8, 4, 8, 2,
             { { { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f } } }, 7);
        test(mwm, RectangularWindow, 8, 4, 8, 3,
             { { {}, {}, {}, {}, {} } }, 7);
        releaseMock(mwm);
    }
    
};

#endif