changeset 506:285f18c0992a

Tests and fixes for FiltFilt: Fix overrun; reset filter between forwards and backwards processes
author Chris Cannam <cannam@all-day-breakfast.com>
date Wed, 05 Jun 2019 15:50:38 +0100
parents 930b5b0f707d
children d7b9691817a3
files dsp/signalconditioning/FiltFilt.cpp dsp/signalconditioning/FiltFilt.h tests/Makefile tests/TestFiltFilt.cpp
diffstat 4 files changed, 127 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/dsp/signalconditioning/FiltFilt.cpp	Wed Jun 05 12:55:15 2019 +0100
+++ b/dsp/signalconditioning/FiltFilt.cpp	Wed Jun 05 15:50:38 2019 +0100
@@ -15,10 +15,6 @@
 
 #include "FiltFilt.h"
 
-//////////////////////////////////////////////////////////////////////
-// Construction/Destruction
-//////////////////////////////////////////////////////////////////////
-
 FiltFilt::FiltFilt(Filter::Parameters parameters) :
     m_filter(parameters)
 {
@@ -29,7 +25,9 @@
 {
 }
 
-void FiltFilt::process(double *src, double *dst, int length)
+void FiltFilt::process(const double *const QM_R__ src,
+                       double *const QM_R__ dst,
+                       const int length)
 {       
     int i;
 
@@ -53,19 +51,25 @@
 
     int index = 0;
     for (i = nFact; i > 0; i--) {
-        filtScratchIn[ index++ ] = sample0 - src[ i ];
+        if (i < length) {
+            filtScratchIn[index] = sample0 - src[ i ];
+        }
+        ++index;
     }
     index = 0;
     for (i = 0; i < nFact; i++) {
-        filtScratchIn[ (nExt - nFact) + index++ ] =
-            sampleN - src[ (length - 2) - i ];
+        if (i < length) {
+            filtScratchIn[(nExt - nFact) + index] =
+                sampleN - src[ (length - 2) - i ];
+        }
+        ++index;
     }
 
     index = 0;
     for (i = 0; i < length; i++) {
         filtScratchIn[ i + nFact ] = src[ i ];
     }
-        
+    
     ////////////////////////////////
     // Do 0Ph filtering
     m_filter.process(filtScratchIn, filtScratchOut, nExt);
@@ -75,9 +79,12 @@
         filtScratchIn[ i ] = filtScratchOut[ nExt - i - 1];
     }
 
+    // clear filter state
+    m_filter.reset();
+    
     // do FILTER again 
     m_filter.process(filtScratchIn, filtScratchOut, nExt);
-        
+
     // reverse the series back 
     for (i = 0; i < nExt; i++) {
         filtScratchIn[ i ] = filtScratchOut[ nExt - i - 1 ];
@@ -95,7 +102,3 @@
     delete [] filtScratchOut;
 }
 
-void FiltFilt::reset()
-{
-
-}
--- a/dsp/signalconditioning/FiltFilt.h	Wed Jun 05 12:55:15 2019 +0100
+++ b/dsp/signalconditioning/FiltFilt.h	Wed Jun 05 15:50:38 2019 +0100
@@ -28,8 +28,9 @@
     FiltFilt(Filter::Parameters);
     virtual ~FiltFilt();
 
-    void reset();
-    void process(double* src, double* dst, int length);
+    void process(const double *const QM_R__ src,
+                 double *const QM_R__ dst,
+                 const int length);
 
 private:
     Filter m_filter;
--- a/tests/Makefile	Wed Jun 05 12:55:15 2019 +0100
+++ b/tests/Makefile	Wed Jun 05 15:50:38 2019 +0100
@@ -5,7 +5,7 @@
 LDFLAGS := $(LDFLAGS) -lboost_unit_test_framework -lpthread
 LIBS := ../libqm-dsp.a
 
-TESTS	:= test-mathutilities test-window test-filter test-fft test-dct test-pvoc test-resampler test-medianfilter test-getkeymode test-chromagram
+TESTS	:= test-mathutilities test-window test-filter test-filtfilt test-fft test-dct test-pvoc test-resampler test-medianfilter test-getkeymode test-chromagram
 
 VG	:= valgrind -q
 
@@ -25,6 +25,9 @@
 test-filter: TestFilter.o $(LIBS)
 	$(CXX) -o $@ $^ $(LDFLAGS)
 
+test-filtfilt: TestFiltFilt.o $(LIBS)
+	$(CXX) -o $@ $^ $(LDFLAGS)
+
 test-fft: TestFFT.o $(LIBS)
 	$(CXX) -o $@ $^ $(LDFLAGS)
 
@@ -47,6 +50,7 @@
 TestMedianFilter.o: $(LIBS)
 TestWindow.o: $(LIBS)
 TestFilter.o: $(LIBS)
+TestFiltFilt.o: $(LIBS)
 TestFFT.o: $(LIBS)
 TestDCT.o: $(LIBS)
 TestPhaseVocoder.o: $(LIBS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/TestFiltFilt.cpp	Wed Jun 05 15:50:38 2019 +0100
@@ -0,0 +1,102 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+#include "dsp/signalconditioning/FiltFilt.h"
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include <stdexcept>
+
+using namespace std;
+
+BOOST_AUTO_TEST_SUITE(TestFiltFilt)
+
+// Our FiltFilt is, apparently intentionally (?) a bit different from
+// the equivalents in MATLAB and Octave (which themselves also differ)
+// in how it handles transients at either end. While the three should
+// all be "roughly" the same, all produce slightly different values.
+// So the "expected" values here are, unfortunately, simply produced
+// by the function itself, in a process involving tracing the values
+// passed in to each of the filter stages within it and compared their
+// outputs against MATLAB.
+
+static vector<double> iir_a { 1,5.75501989315662,16.326056867468,28.779190797823,34.2874379215653,28.137815126537,15.6064643257793,5.37874515231553,0.913800050254382,0.0,0.0 };
+static vector<double> iir_b { 0.0031954608137085,0.0180937089815597,0.0508407778575426,0.0895040074158415,0.107385387168148,0.0895040074158415,0.0508407778575426,0.0180937089815597,0.0031954608137085,0.0,0.0 };
+
+BOOST_AUTO_TEST_CASE(long_zeroes)
+{
+    vector<double> a(iir_a);
+    vector<double> b(iir_b);
+
+    vector<double> zeroes(1000, 0.0);
+    vector<double> expected(zeroes);
+
+    FiltFilt f({ a, b });
+    
+    int n = expected.size();
+    vector<double> out(n, 0.0);
+
+    f.process(zeroes.data(), out.data(), n);
+    
+    double thresh = 1e-12;
+    
+    for (int i = 0; i < n; ++i) {
+	BOOST_CHECK_SMALL(out[i] - expected[i], thresh);
+    }
+}
+
+BOOST_AUTO_TEST_CASE(empty)
+{
+    vector<double> a(iir_a);
+    vector<double> b(iir_b);
+    vector<double> in;
+    vector<double> out;
+    vector<double> expected;
+
+    FiltFilt f({ a, b });
+
+    f.process(in.data(), out.data(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(single_value)
+{
+    vector<double> a(iir_a);
+    vector<double> b(iir_b);
+    vector<double> in({ 1.0 });
+    vector<double> out({ 0.0 });
+    vector<double> expected({ -0.000236642942007 });
+
+    FiltFilt f({ a, b });
+
+    f.process(in.data(), out.data(), 1);
+
+    double thresh = 1e-12;
+
+    BOOST_CHECK_SMALL(out[0] - expected[0], thresh);
+}
+
+BOOST_AUTO_TEST_CASE(shortish)
+{
+    vector<double> a(iir_a);
+    vector<double> b(iir_b);
+
+    vector<double> in { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
+    vector<double> expected { 0.0180395628158, -0.0317213769922, 0.0276928584707, -0.00944844359572, -0.0119394068579, 0.0252763627734, -0.0239770024451, 0.010560830803, 0.00726290602379, -0.0191915890394, 0.0202451156989, -0.0103434265023, -0.00341952789985, 0.0142823904341, -0.0160627659442, 0.00984876782803 };
+
+    FiltFilt f({ a, b });
+    
+    int n = expected.size();
+    vector<double> out(n, 0.0);
+
+    f.process(in.data(), out.data(), n);
+    
+    double thresh = 1e-12;
+    
+    for (int i = 0; i < n; ++i) {
+	BOOST_CHECK_SMALL(out[i] - expected[i], thresh);
+    }
+}
+
+BOOST_AUTO_TEST_SUITE_END()