Chris@29: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@29: Chris@29: /* Chris@29: bqfft Chris@29: Chris@29: A small library wrapping various FFT implementations for some Chris@29: common audio processing use cases. Chris@29: Chris@29: Copyright 2007-2015 Particular Programs Ltd. Chris@29: Chris@29: Permission is hereby granted, free of charge, to any person Chris@29: obtaining a copy of this software and associated documentation Chris@29: files (the "Software"), to deal in the Software without Chris@29: restriction, including without limitation the rights to use, copy, Chris@29: modify, merge, publish, distribute, sublicense, and/or sell copies Chris@29: of the Software, and to permit persons to whom the Software is Chris@29: furnished to do so, subject to the following conditions: Chris@29: Chris@29: The above copyright notice and this permission notice shall be Chris@29: included in all copies or substantial portions of the Software. Chris@29: Chris@29: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, Chris@29: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF Chris@29: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND Chris@29: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR Chris@29: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF Chris@29: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION Chris@29: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Chris@29: Chris@29: Except as contained in this notice, the names of Chris Cannam and Chris@29: Particular Programs Ltd shall not be used in advertising or Chris@29: otherwise to promote the sale, use or other dealings in this Chris@29: Software without prior written authorization. Chris@29: */ Chris@29: Chris@29: #ifndef TEST_FFT_H Chris@29: #define TEST_FFT_H Chris@29: Chris@29: #include "bqfft/FFT.h" Chris@29: Chris@29: #include Chris@29: #include Chris@29: Chris@29: #include Chris@29: Chris@29: #include "Compares.h" Chris@29: Chris@29: namespace breakfastquay { Chris@29: Chris@29: class TestFFT : public QObject Chris@29: { Chris@29: Q_OBJECT Chris@29: Chris@29: private: Chris@29: void idat() { Chris@29: QTest::addColumn("implementation"); Chris@29: std::set impls = FFT::getImplementations(); Chris@29: foreach (std::string i, impls) { Chris@29: QTest::newRow(i.c_str()) << i.c_str(); Chris@29: } Chris@29: } Chris@29: QString ifetch() { Chris@29: QFETCH(QString, implementation); Chris@29: FFT::setDefaultImplementation(implementation.toLocal8Bit().data()); Chris@29: return implementation; Chris@29: } Chris@29: Chris@29: bool lackSingle() { Chris@29: return !(FFT(4).getSupportedPrecisions() & FFT::SinglePrecision); Chris@29: } Chris@29: bool lackDouble() { Chris@29: return !(FFT(4).getSupportedPrecisions() & FFT::DoublePrecision); Chris@29: } Chris@29: Chris@29: private slots: Chris@29: Chris@29: void checkD() { Chris@29: QString impl = ifetch(); Chris@29: } Chris@29: Chris@29: void dc() { Chris@29: ifetch(); Chris@29: // DC-only signal. The DC bin is purely real Chris@29: double in[] = { 1, 1, 1, 1 }; Chris@29: double re[3], im[3]; Chris@29: FFT(4).forward(in, re, im); Chris@29: QCOMPARE(re[0], 4.0); Chris@29: COMPARE_ZERO(re[1]); Chris@29: COMPARE_ZERO(re[2]); Chris@29: COMPARE_ALL(im, 0.0); Chris@29: double back[4]; Chris@29: FFT(4).inverse(re, im, back); Chris@29: COMPARE_SCALED(back, in, 4); Chris@29: } Chris@29: Chris@29: void sine() { Chris@29: ifetch(); Chris@29: // Sine. Output is purely imaginary Chris@29: double in[] = { 0, 1, 0, -1 }; Chris@29: double re[3], im[3]; Chris@29: FFT(4).forward(in, re, im); Chris@29: COMPARE_ALL(re, 0.0); Chris@29: COMPARE_ZERO(im[0]); Chris@29: QCOMPARE(im[1], -2.0); Chris@29: COMPARE_ZERO(im[2]); Chris@29: double back[4]; Chris@29: FFT(4).inverse(re, im, back); Chris@29: COMPARE_SCALED(back, in, 4); Chris@29: } Chris@29: Chris@29: void cosine() { Chris@29: ifetch(); Chris@29: // Cosine. Output is purely real Chris@29: double in[] = { 1, 0, -1, 0 }; Chris@29: double re[3], im[3]; Chris@29: FFT(4).forward(in, re, im); Chris@29: COMPARE_ZERO(re[0]); Chris@29: QCOMPARE(re[1], 2.0); Chris@29: COMPARE_ZERO(re[2]); Chris@29: COMPARE_ALL(im, 0.0); Chris@29: double back[4]; Chris@29: FFT(4).inverse(re, im, back); Chris@29: COMPARE_SCALED(back, in, 4); Chris@29: } Chris@29: Chris@29: void sineCosine() { Chris@29: ifetch(); Chris@29: // Sine and cosine mixed Chris@29: double in[] = { 0.5, 1, -0.5, -1 }; Chris@29: double re[3], im[3]; Chris@29: FFT(4).forward(in, re, im); Chris@29: COMPARE_ZERO(re[0]); Chris@29: QCOMPARE(re[1], 1.0); Chris@29: COMPARE_ZERO(re[2]); Chris@29: COMPARE_ZERO(im[0]); Chris@29: QCOMPARE(im[1], -2.0); Chris@29: COMPARE_ZERO(im[2]); Chris@29: double back[4]; Chris@29: FFT(4).inverse(re, im, back); Chris@29: COMPARE_SCALED(back, in, 4); Chris@29: } Chris@29: Chris@29: void nyquist() { Chris@29: ifetch(); Chris@29: double in[] = { 1, -1, 1, -1 }; Chris@29: double re[3], im[3]; Chris@29: FFT(4).forward(in, re, im); Chris@29: COMPARE_ZERO(re[0]); Chris@29: COMPARE_ZERO(re[1]); Chris@29: QCOMPARE(re[2], 4.0); Chris@29: COMPARE_ALL(im, 0.0); Chris@29: double back[4]; Chris@29: FFT(4).inverse(re, im, back); Chris@29: COMPARE_SCALED(back, in, 4); Chris@29: } Chris@29: Chris@29: void interleaved() { Chris@29: ifetch(); Chris@29: // Sine and cosine mixed, test output format Chris@29: double in[] = { 0.5, 1, -0.5, -1 }; Chris@29: double out[6]; Chris@29: FFT(4).forwardInterleaved(in, out); Chris@29: COMPARE_ZERO(out[0]); Chris@29: COMPARE_ZERO(out[1]); Chris@29: QCOMPARE(out[2], 1.0); Chris@29: QCOMPARE(out[3], -2.0); Chris@29: COMPARE_ZERO(out[4]); Chris@29: COMPARE_ZERO(out[5]); Chris@29: double back[4]; Chris@29: FFT(4).inverseInterleaved(out, back); Chris@29: COMPARE_SCALED(back, in, 4); Chris@29: } Chris@29: Chris@29: void cosinePolar() { Chris@29: ifetch(); Chris@29: double in[] = { 1, 0, -1, 0 }; Chris@29: double mag[3], phase[3]; Chris@29: FFT(4).forwardPolar(in, mag, phase); Chris@29: COMPARE_ZERO(mag[0]); Chris@29: QCOMPARE(mag[1], 2.0); Chris@29: COMPARE_ZERO(mag[2]); Chris@29: // No meaningful tests for phase[i] where mag[i]==0 (phase Chris@29: // could legitimately be anything) Chris@29: COMPARE_ZERO(phase[1]); Chris@29: double back[4]; Chris@29: FFT(4).inversePolar(mag, phase, back); Chris@29: COMPARE_SCALED(back, in, 4); Chris@29: } Chris@29: Chris@29: void sinePolar() { Chris@29: ifetch(); Chris@29: double in[] = { 0, 1, 0, -1 }; Chris@29: double mag[3], phase[3]; Chris@29: FFT(4).forwardPolar(in, mag, phase); Chris@29: COMPARE_ZERO(mag[0]); Chris@29: QCOMPARE(mag[1], 2.0); Chris@29: COMPARE_ZERO(mag[2]); Chris@29: // No meaningful tests for phase[i] where mag[i]==0 (phase Chris@29: // could legitimately be anything) Chris@29: QCOMPARE(phase[1], -M_PI/2.0); Chris@29: double back[4]; Chris@29: FFT(4).inversePolar(mag, phase, back); Chris@29: COMPARE_SCALED(back, in, 4); Chris@29: } Chris@29: Chris@29: void magnitude() { Chris@29: ifetch(); Chris@29: // Sine and cosine mixed Chris@29: double in[] = { 0.5, 1, -0.5, -1 }; Chris@29: double out[3]; Chris@29: FFT(4).forwardMagnitude(in, out); Chris@29: COMPARE_ZERO(out[0]); Chris@29: QCOMPARE(float(out[1]), sqrtf(5.0)); Chris@29: COMPARE_ZERO(out[2]); Chris@29: } Chris@29: Chris@29: void dirac() { Chris@29: ifetch(); Chris@29: double in[] = { 1, 0, 0, 0 }; Chris@29: double re[3], im[3]; Chris@29: FFT(4).forward(in, re, im); Chris@29: QCOMPARE(re[0], 1.0); Chris@29: QCOMPARE(re[1], 1.0); Chris@29: QCOMPARE(re[2], 1.0); Chris@29: COMPARE_ALL(im, 0.0); Chris@29: double back[4]; Chris@29: FFT(4).inverse(re, im, back); Chris@29: COMPARE_SCALED(back, in, 4); Chris@29: } Chris@29: Chris@29: void cepstrum() { Chris@29: ifetch(); Chris@29: double in[] = { 1, 0, 0, 0, 1, 0, 0, 0 }; Chris@29: double mag[5]; Chris@29: FFT(8).forwardMagnitude(in, mag); Chris@29: double cep[8]; Chris@29: FFT(8).inverseCepstral(mag, cep); Chris@29: COMPARE_ZERO(cep[1]); Chris@29: COMPARE_ZERO(cep[2]); Chris@29: COMPARE_ZERO(cep[3]); Chris@29: COMPARE_ZERO(cep[5]); Chris@29: COMPARE_ZERO(cep[6]); Chris@29: COMPARE_ZERO(cep[7]); Chris@29: QVERIFY(fabs(-6.561181 - cep[0]/8) < 0.000001); Chris@29: QVERIFY(fabs( 7.254329 - cep[4]/8) < 0.000001); Chris@29: } Chris@29: Chris@29: void forwardArrayBounds() { Chris@29: ifetch(); Chris@29: // initialise bins to something recognisable, so we can tell Chris@29: // if they haven't been written Chris@29: double in[] = { 1, 1, -1, -1 }; Chris@29: double re[] = { 999, 999, 999, 999, 999 }; Chris@29: double im[] = { 999, 999, 999, 999, 999 }; Chris@29: FFT(4).forward(in, re+1, im+1); Chris@29: // And check we haven't overrun the arrays Chris@29: QCOMPARE(re[0], 999.0); Chris@29: QCOMPARE(im[0], 999.0); Chris@29: QCOMPARE(re[4], 999.0); Chris@29: QCOMPARE(im[4], 999.0); Chris@29: } Chris@29: Chris@29: void inverseArrayBounds() { Chris@29: ifetch(); Chris@29: // initialise bins to something recognisable, so we can tell Chris@29: // if they haven't been written Chris@29: double re[] = { 0, 1, 0 }; Chris@29: double im[] = { 0, -2, 0 }; Chris@29: double out[] = { 999, 999, 999, 999, 999, 999 }; Chris@29: FFT(4).inverse(re, im, out+1); Chris@29: // And check we haven't overrun the arrays Chris@29: QCOMPARE(out[0], 999.0); Chris@29: QCOMPARE(out[5], 999.0); Chris@29: } Chris@29: Chris@29: void checkF() { Chris@29: QString impl = ifetch(); Chris@29: } Chris@29: Chris@29: void dcF() { Chris@29: ifetch(); Chris@29: // DC-only signal. The DC bin is purely real Chris@29: float in[] = { 1, 1, 1, 1 }; Chris@29: float re[3], im[3]; Chris@29: FFT(4).forward(in, re, im); Chris@29: QCOMPARE(re[0], 4.0f); Chris@29: COMPARE_ZERO_F(re[1]); Chris@29: COMPARE_ZERO_F(re[2]); Chris@29: COMPARE_ALL_F(im, 0.0f); Chris@29: float back[4]; Chris@29: FFT(4).inverse(re, im, back); Chris@29: COMPARE_SCALED_F(back, in, 4); Chris@29: } Chris@29: Chris@29: void sineF() { Chris@29: ifetch(); Chris@29: // Sine. Output is purely imaginary Chris@29: float in[] = { 0, 1, 0, -1 }; Chris@29: float re[3], im[3]; Chris@29: FFT(4).forward(in, re, im); Chris@29: COMPARE_ALL_F(re, 0.0f); Chris@29: COMPARE_ZERO_F(im[0]); Chris@29: QCOMPARE(im[1], -2.0f); Chris@29: COMPARE_ZERO_F(im[2]); Chris@29: float back[4]; Chris@29: FFT(4).inverse(re, im, back); Chris@29: COMPARE_SCALED_F(back, in, 4); Chris@29: } Chris@29: Chris@29: void cosineF() { Chris@29: ifetch(); Chris@29: // Cosine. Output is purely real Chris@29: float in[] = { 1, 0, -1, 0 }; Chris@29: float re[3], im[3]; Chris@29: FFT(4).forward(in, re, im); Chris@29: COMPARE_ZERO_F(re[0]); Chris@29: QCOMPARE(re[1], 2.0f); Chris@29: COMPARE_ZERO_F(re[2]); Chris@29: COMPARE_ALL_F(im, 0.0f); Chris@29: float back[4]; Chris@29: FFT(4).inverse(re, im, back); Chris@29: COMPARE_SCALED_F(back, in, 4); Chris@29: } Chris@29: Chris@29: void sineCosineF() { Chris@29: ifetch(); Chris@29: // Sine and cosine mixed Chris@29: float in[] = { 0.5, 1, -0.5, -1 }; Chris@29: float re[3], im[3]; Chris@29: FFT(4).forward(in, re, im); Chris@29: COMPARE_ZERO_F(re[0]); Chris@29: QCOMPARE(re[1], 1.0f); Chris@29: COMPARE_ZERO_F(re[2]); Chris@29: COMPARE_ZERO_F(im[0]); Chris@29: QCOMPARE(im[1], -2.0f); Chris@29: COMPARE_ZERO_F(im[2]); Chris@29: float back[4]; Chris@29: FFT(4).inverse(re, im, back); Chris@29: COMPARE_SCALED_F(back, in, 4); Chris@29: } Chris@29: Chris@29: void nyquistF() { Chris@29: ifetch(); Chris@29: float in[] = { 1, -1, 1, -1 }; Chris@29: float re[3], im[3]; Chris@29: FFT(4).forward(in, re, im); Chris@29: COMPARE_ZERO_F(re[0]); Chris@29: COMPARE_ZERO_F(re[1]); Chris@29: QCOMPARE(re[2], 4.0f); Chris@29: COMPARE_ALL_F(im, 0.0f); Chris@29: float back[4]; Chris@29: FFT(4).inverse(re, im, back); Chris@29: COMPARE_SCALED_F(back, in, 4); Chris@29: } Chris@29: Chris@29: void interleavedF() { Chris@29: ifetch(); Chris@29: // Sine and cosine mixed, test output format Chris@29: float in[] = { 0.5, 1, -0.5, -1 }; Chris@29: float out[6]; Chris@29: FFT(4).forwardInterleaved(in, out); Chris@29: COMPARE_ZERO_F(out[0]); Chris@29: COMPARE_ZERO_F(out[1]); Chris@29: QCOMPARE(out[2], 1.0f); Chris@29: QCOMPARE(out[3], -2.0f); Chris@29: COMPARE_ZERO_F(out[4]); Chris@29: COMPARE_ZERO_F(out[5]); Chris@29: float back[4]; Chris@29: FFT(4).inverseInterleaved(out, back); Chris@29: COMPARE_SCALED_F(back, in, 4); Chris@29: } Chris@29: Chris@29: void cosinePolarF() { Chris@29: ifetch(); Chris@29: float in[] = { 1, 0, -1, 0 }; Chris@29: float mag[3], phase[3]; Chris@29: FFT(4).forwardPolar(in, mag, phase); Chris@29: COMPARE_ZERO_F(mag[0]); Chris@29: QCOMPARE(mag[1], 2.0f); Chris@29: COMPARE_ZERO_F(mag[2]); Chris@29: // No meaningful tests for phase[i] where mag[i]==0 (phase Chris@29: // could legitimately be anything) Chris@29: COMPARE_ZERO_F(phase[1]); Chris@29: float back[4]; Chris@29: FFT(4).inversePolar(mag, phase, back); Chris@29: COMPARE_SCALED_F(back, in, 4); Chris@29: } Chris@29: Chris@29: void sinePolarF() { Chris@29: ifetch(); Chris@29: float in[] = { 0, 1, 0, -1 }; Chris@29: float mag[3], phase[3]; Chris@29: FFT(4).forwardPolar(in, mag, phase); Chris@29: COMPARE_ZERO_F(mag[0]); Chris@29: QCOMPARE(mag[1], 2.0f); Chris@29: COMPARE_ZERO_F(mag[2]); Chris@29: // No meaningful tests for phase[i] where mag[i]==0 (phase Chris@29: // could legitimately be anything) Chris@29: QCOMPARE(phase[1], -float(M_PI)/2.0f); Chris@29: float back[4]; Chris@29: FFT(4).inversePolar(mag, phase, back); Chris@29: COMPARE_SCALED_F(back, in, 4); Chris@29: } Chris@29: Chris@29: void magnitudeF() { Chris@29: ifetch(); Chris@29: // Sine and cosine mixed Chris@29: float in[] = { 0.5, 1, -0.5, -1 }; Chris@29: float out[3]; Chris@29: FFT(4).forwardMagnitude(in, out); Chris@29: COMPARE_ZERO_F(out[0]); Chris@29: QCOMPARE(float(out[1]), sqrtf(5.0f)); Chris@29: COMPARE_ZERO_F(out[2]); Chris@29: } Chris@29: Chris@29: void diracF() { Chris@29: ifetch(); Chris@29: float in[] = { 1, 0, 0, 0 }; Chris@29: float re[3], im[3]; Chris@29: FFT(4).forward(in, re, im); Chris@29: QCOMPARE(re[0], 1.0f); Chris@29: QCOMPARE(re[1], 1.0f); Chris@29: QCOMPARE(re[2], 1.0f); Chris@29: COMPARE_ALL_F(im, 0.0f); Chris@29: float back[4]; Chris@29: FFT(4).inverse(re, im, back); Chris@29: COMPARE_SCALED_F(back, in, 4); Chris@29: } Chris@29: Chris@29: void cepstrumF() { Chris@29: ifetch(); Chris@29: float in[] = { 1, 0, 0, 0, 1, 0, 0, 0 }; Chris@29: float mag[5]; Chris@29: FFT(8).forwardMagnitude(in, mag); Chris@29: float cep[8]; Chris@29: FFT(8).inverseCepstral(mag, cep); Chris@29: COMPARE_ZERO_F(cep[1]); Chris@29: COMPARE_ZERO_F(cep[2]); Chris@29: COMPARE_ZERO_F(cep[3]); Chris@29: COMPARE_ZERO_F(cep[5]); Chris@29: COMPARE_ZERO_F(cep[6]); Chris@29: COMPARE_ZERO_F(cep[7]); Chris@29: QVERIFY(fabsf(-6.561181 - cep[0]/8) < 0.000001); Chris@29: QVERIFY(fabsf( 7.254329 - cep[4]/8) < 0.000001); Chris@29: } Chris@29: Chris@29: void forwardArrayBoundsF() { Chris@29: ifetch(); Chris@29: // initialise bins to something recognisable, so we can tell Chris@29: // if they haven't been written Chris@29: float in[] = { 1, 1, -1, -1 }; Chris@29: float re[] = { 999, 999, 999, 999, 999 }; Chris@29: float im[] = { 999, 999, 999, 999, 999 }; Chris@29: FFT(4).forward(in, re+1, im+1); Chris@29: // And check we haven't overrun the arrays Chris@29: QCOMPARE(re[0], 999.0f); Chris@29: QCOMPARE(im[0], 999.0f); Chris@29: QCOMPARE(re[4], 999.0f); Chris@29: QCOMPARE(im[4], 999.0f); Chris@29: } Chris@29: Chris@29: void inverseArrayBoundsF() { Chris@29: ifetch(); Chris@29: // initialise bins to something recognisable, so we can tell Chris@29: // if they haven't been written Chris@29: float re[] = { 0, 1, 0 }; Chris@29: float im[] = { 0, -2, 0 }; Chris@29: float out[] = { 999, 999, 999, 999, 999, 999 }; Chris@29: FFT(4).inverse(re, im, out+1); Chris@29: // And check we haven't overrun the arrays Chris@29: QCOMPARE(out[0], 999.0f); Chris@29: QCOMPARE(out[5], 999.0f); Chris@29: } Chris@29: Chris@29: void checkD_data() { idat(); } Chris@29: void dc_data() { idat(); } Chris@29: void sine_data() { idat(); } Chris@29: void cosine_data() { idat(); } Chris@29: void sineCosine_data() { idat(); } Chris@29: void sineCosineDC_data() { idat(); } Chris@29: void nyquist_data() { idat(); } Chris@29: void interleaved_data() { idat(); } Chris@29: void cosinePolar_data() { idat(); } Chris@29: void sinePolar_data() { idat(); } Chris@29: void magnitude_data() { idat(); } Chris@29: void dirac_data() { idat(); } Chris@29: void cepstrum_data() { idat(); } Chris@29: void forwardArrayBounds_data() { idat(); } Chris@29: void inverseArrayBounds_data() { idat(); } Chris@29: Chris@29: void checkF_data() { idat(); } Chris@29: void dcF_data() { idat(); } Chris@29: void sineF_data() { idat(); } Chris@29: void cosineF_data() { idat(); } Chris@29: void sineCosineF_data() { idat(); } Chris@29: void sineCosineDCF_data() { idat(); } Chris@29: void nyquistF_data() { idat(); } Chris@29: void interleavedF_data() { idat(); } Chris@29: void cosinePolarF_data() { idat(); } Chris@29: void sinePolarF_data() { idat(); } Chris@29: void magnitudeF_data() { idat(); } Chris@29: void diracF_data() { idat(); } Chris@29: void cepstrumF_data() { idat(); } Chris@29: void forwardArrayBoundsF_data() { idat(); } Chris@29: void inverseArrayBoundsF_data() { idat(); } Chris@29: }; Chris@29: Chris@29: } Chris@29: Chris@29: #endif