Mercurial > hg > svcore
view data/model/test/TestFFTModel.h @ 1196:c7b9c902642f spectrogram-minor-refactor
Fix threshold in spectrogram -- it wasn't working in the last release.
There is a new protocol for this. Formerly the threshold parameter had a
range from -50dB to 0 with the default at -50, and -50 treated internally
as "no threshold". However, there was a hardcoded, hidden internal threshold
for spectrogram colour mapping at -80dB with anything below this being rounded
to zero. Now the threshold parameter has range -81 to -1 with the default
at -80, -81 is treated internally as "no threshold", and there is no hidden
internal threshold. So the default behaviour is the same as before, an
effective -80dB threshold, but it is now possible to change this in both
directions. Sessions reloaded from prior versions may look slightly different
because, if the session says there should be no threshold, there will now
actually be no threshold instead of having the hidden internal one.
Still need to do something in the UI to make it apparent that the -81dB
setting removes the threshold entirely. This is at least no worse than the
previous, also obscured, magic -50dB setting.
author | Chris Cannam |
---|---|
date | Mon, 01 Aug 2016 16:21:01 +0100 |
parents | 457a1a619c5f |
children | 87ae75da6527 |
line wrap: on
line source
/* -*- 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; class TestFFTModel : public QObject { 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) { 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) { cerr << "ERROR: output is not as expected for column " << i << " in channel " << ch << " (stepThrough = " << stepThrough << ")" << 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; } COMPARE_FUZZIER_F(reals[i], eRe); COMPARE_FUZZIER_F(imags[i], eIm); } QCOMPARE(reals[hs1], 999.f); QCOMPARE(imags[hs1], 999.f); } } } 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() { 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); } void dc_simple_hann_halfoverlap() { MockWaveModel mwm({ 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); } void sine_simple_rect() { MockWaveModel mwm({ 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); } void cosine_simple_rect() { MockWaveModel mwm({ 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); } void twochan_simple_rect() { MockWaveModel mwm({ 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); } void nyquist_simple_rect() { MockWaveModel mwm({ 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); } void dirac_simple_rect() { MockWaveModel mwm({ 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); } void dirac_simple_rect_2() { MockWaveModel mwm({ 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); } void dirac_simple_rect_halfoverlap() { MockWaveModel mwm({ 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); } }; #endif