2323
2424#include < gtest/gtest.h>
2525
26- #if 0
27-
2826using namespace yup ;
2927
3028namespace
@@ -52,8 +50,8 @@ class BiquadCascadeFilterTests : public ::testing::Test
5250
5351 for (int i = 0 ; i < blockSize; ++i)
5452 {
55- testData[i] = static_cast <float> (i) / blockSize - 0.5f ;
56- doubleTestData[i] = static_cast<double> (i) / blockSize - 0.5 ;
53+ testData[i] = 0 . 1f * std::sin ( 2 . 0f * MathConstants <float >::pi * 1000 . 0f * i / static_cast < float > (sampleRate)) ;
54+ doubleTestData[i] = static_cast <double > (testData[i]) ;
5755 }
5856 }
5957
@@ -79,25 +77,33 @@ TEST_F (BiquadCascadeFilterTests, ConstructorWithSectionsInitializes)
7977 EXPECT_EQ (4 , cascade.getNumSections ());
8078}
8179
82- TEST_F (BiquadCascadeFilterTests, SetNumSectionsChangesSize )
80+ TEST_F (BiquadCascadeFilterTests, SectionManagement )
8381{
84- EXPECT_EQ (2, cascadeFloat.getNumSections());
85-
86- cascadeFloat.setNumSections (5);
87- EXPECT_EQ (5, cascadeFloat.getNumSections());
88-
89- cascadeFloat.setNumSections (1);
90- EXPECT_EQ (1, cascadeFloat.getNumSections());
82+ cascadeFloat.setNumSections (3 );
83+ EXPECT_EQ (cascadeFloat.getNumSections (), 3u );
84+
85+ // Set coefficients for each section
86+ auto coeffs1 = FilterDesigner<double >::designRbjLowpass (500.0 , 0.707 , sampleRate);
87+ auto coeffs2 = FilterDesigner<double >::designRbjBandpass (1000.0 , 2.0 , sampleRate);
88+ auto coeffs3 = FilterDesigner<double >::designRbjHighpass (2000.0 , 0.707 , sampleRate);
89+
90+ cascadeFloat.setSectionCoefficients (0 , coeffs1);
91+ cascadeFloat.setSectionCoefficients (1 , coeffs2);
92+ cascadeFloat.setSectionCoefficients (2 , coeffs3);
93+
94+ // Verify coefficients were set correctly
95+ auto retrievedCoeffs1 = cascadeFloat.getSectionCoefficients (0 );
96+ EXPECT_FLOAT_EQ (retrievedCoeffs1.b0 , coeffs1.b0 );
97+ EXPECT_FLOAT_EQ (retrievedCoeffs1.a1 , coeffs1.a1 );
9198}
9299
93100TEST_F (BiquadCascadeFilterTests, SetAndGetSectionCoefficients)
94101{
95102 // Create lowpass coefficients
96- BiquadCascade<float> defaultCascade;
97103 auto coeffs = FilterDesigner<double >::designRbjLowpass (1000.0 , 0.707 , sampleRate);
98104
99- defaultCascade .setSectionCoefficients (0, coeffs);
100- auto retrievedCoeffs = defaultCascade .getSectionCoefficients (0);
105+ cascadeFloat .setSectionCoefficients (0 , coeffs);
106+ auto retrievedCoeffs = cascadeFloat .getSectionCoefficients (0 );
101107
102108 EXPECT_NEAR (coeffs.b0 , retrievedCoeffs.b0 , tolerance);
103109 EXPECT_NEAR (coeffs.b1 , retrievedCoeffs.b1 , tolerance);
@@ -108,29 +114,60 @@ TEST_F (BiquadCascadeFilterTests, SetAndGetSectionCoefficients)
108114
109115TEST_F (BiquadCascadeFilterTests, InvalidSectionIndexHandling)
110116{
111- BiquadCascade<float> defaultCascade;
112117 auto coeffs = FilterDesigner<double >::designRbjLowpass (1000.0 , 0.707 , sampleRate);
113118
114119 // Should not crash with invalid index
115- defaultCascade .setSectionCoefficients (999, coeffs);
120+ cascadeFloat .setSectionCoefficients (999 , coeffs);
116121
117122 // Should return empty coefficients for invalid index
118- auto emptyCoeffs = defaultCascade .getSectionCoefficients (999);
123+ auto emptyCoeffs = cascadeFloat .getSectionCoefficients (999 );
119124 EXPECT_EQ (1.0 , emptyCoeffs.b0 ); // Default biquad passes through (b0=1)
120125 EXPECT_EQ (0.0 , emptyCoeffs.b1 );
121126 EXPECT_EQ (0.0 , emptyCoeffs.b2 );
122127 EXPECT_EQ (0.0 , emptyCoeffs.a1 );
123128 EXPECT_EQ (0.0 , emptyCoeffs.a2 );
124129}
125130
131+ TEST_F (BiquadCascadeFilterTests, InvalidSectionAccess)
132+ {
133+ cascadeFloat.setNumSections (2 );
134+
135+ // Trying to access section 5 when only 2 sections exist should not crash
136+ auto coeffs = cascadeFloat.getSectionCoefficients (5 );
137+ // Should return default/empty coefficients
138+ EXPECT_TRUE (std::isfinite (coeffs.b0 ));
139+ }
140+
141+ TEST_F (BiquadCascadeFilterTests, DynamicSectionResize)
142+ {
143+ // Start with 1 section
144+ cascadeFloat.setNumSections (1 );
145+ EXPECT_EQ (cascadeFloat.getNumSections (), 1u );
146+
147+ // Expand to 4 sections
148+ cascadeFloat.setNumSections (4 );
149+ EXPECT_EQ (cascadeFloat.getNumSections (), 4u );
150+
151+ // Shrink to 2 sections
152+ cascadeFloat.setNumSections (2 );
153+ EXPECT_EQ (cascadeFloat.getNumSections (), 2u );
154+
155+ // Should still process correctly after resize
156+ cascadeFloat.processBlock (testData.data (), outputData.data (), blockSize);
157+
158+ for (int i = 0 ; i < blockSize; ++i)
159+ {
160+ EXPECT_TRUE (std::isfinite (outputData[i]));
161+ }
162+ }
163+
126164TEST_F (BiquadCascadeFilterTests, ProcessesFloatSamples)
127165{
128166 // Set up lowpass filter on first section
129- BiquadCascade<float> defaultCascade;
130167 auto coeffs = FilterDesigner<double >::designRbjLowpass (1000.0 , 0.707 , sampleRate);
131- defaultCascade .setSectionCoefficients (0, coeffs);
168+ cascadeFloat .setSectionCoefficients (0 , coeffs);
132169
133- defaultCascade .processBlock (testData.data(), outputData.data(), blockSize);
170+ cascadeFloat .processBlock (testData.data (), outputData.data (), blockSize);
134171
135172 // Output should be different from input (filtered)
136173 bool outputDiffers = false ;
@@ -154,11 +191,10 @@ TEST_F (BiquadCascadeFilterTests, ProcessesFloatSamples)
154191TEST_F (BiquadCascadeFilterTests, ProcessesDoubleSamples)
155192{
156193 // Set up lowpass filter on first section
157- BiquadCascade<double> defaultCascade;
158194 auto coeffs = FilterDesigner<double >::designRbjLowpass (1000.0 , 0.707 , sampleRate);
159- defaultCascade .setSectionCoefficients (0, coeffs);
195+ cascadeDouble .setSectionCoefficients (0 , coeffs);
160196
161- defaultCascade .processBlock (doubleTestData.data(), doubleOutputData.data(), blockSize);
197+ cascadeDouble .processBlock (doubleTestData.data (), doubleOutputData.data (), blockSize);
162198
163199 // Output should be different from input (filtered)
164200 bool outputDiffers = false ;
@@ -212,17 +248,51 @@ TEST_F (BiquadCascadeFilterTests, MultipleSectionsCascadeCorrectly)
212248 EXPECT_LT (cascadeEnergy, singleEnergy);
213249}
214250
251+ TEST_F (BiquadCascadeFilterTests, ProcessingThroughCascade)
252+ {
253+ cascadeFloat.setNumSections (3 );
254+
255+ // Set up a multi-stage filter
256+ auto lowpass = FilterDesigner<double >::designRbjLowpass (2000.0 , 0.707 , sampleRate);
257+ auto peak = FilterDesigner<double >::designRbjPeak (1000.0 , 2.0 , 6.0 , sampleRate);
258+ auto highpass = FilterDesigner<double >::designRbjHighpass (500.0 , 0.707 , sampleRate);
259+
260+ cascadeFloat.setSectionCoefficients (0 , lowpass);
261+ cascadeFloat.setSectionCoefficients (1 , peak);
262+ cascadeFloat.setSectionCoefficients (2 , highpass);
263+
264+ cascadeFloat.processBlock (testData.data (), outputData.data (), blockSize);
265+
266+ for (int i = 0 ; i < blockSize; ++i)
267+ {
268+ EXPECT_TRUE (std::isfinite (outputData[i]));
269+ }
270+ }
271+
272+ TEST_F (BiquadCascadeFilterTests, EmptyCascade)
273+ {
274+ cascadeFloat.setNumSections (0 );
275+ EXPECT_EQ (cascadeFloat.getNumSections (), 0u );
276+
277+ // Processing through empty cascade should pass signal through unchanged
278+ cascadeFloat.processBlock (testData.data (), outputData.data (), blockSize);
279+
280+ for (int i = 0 ; i < blockSize; ++i)
281+ {
282+ EXPECT_FLOAT_EQ (outputData[i], testData[i]);
283+ }
284+ }
285+
215286TEST_F (BiquadCascadeFilterTests, InPlaceProcessing)
216287{
217- BiquadCascade<float> defaultCascade;
218288 auto coeffs = FilterDesigner<double >::designRbjLowpass (1000.0 , 0.707 , sampleRate);
219- defaultCascade .setSectionCoefficients (0, coeffs);
289+ cascadeFloat .setSectionCoefficients (0 , coeffs);
220290
221291 // Make a copy for comparison
222292 std::vector<float > originalData = testData;
223293
224294 // Process in-place
225- defaultCascade .processBlock (testData.data(), testData.data(), blockSize);
295+ cascadeFloat .processBlock (testData.data (), testData.data (), blockSize);
226296
227297 // Output should be different from original
228298 bool outputDiffers = false ;
@@ -239,38 +309,78 @@ TEST_F (BiquadCascadeFilterTests, InPlaceProcessing)
239309
240310TEST_F (BiquadCascadeFilterTests, ResetClearsState)
241311{
242- BiquadCascade<float> defaultCascade;
243312 auto coeffs = FilterDesigner<double >::designRbjLowpass (1000.0 , 0.707 , sampleRate);
244- defaultCascade .setSectionCoefficients (0, coeffs);
313+ cascadeFloat .setSectionCoefficients (0 , coeffs);
245314
246315 // Process some data to build up state
247- defaultCascade .processBlock (testData.data(), outputData.data(), blockSize);
316+ cascadeFloat .processBlock (testData.data (), outputData.data (), blockSize);
248317
249318 // Reset and process impulse
250- defaultCascade .reset();
319+ cascadeFloat .reset ();
251320
252321 std::vector<float > impulse (blockSize, 0 .0f );
253322 impulse[0 ] = 1 .0f ;
254323
255- defaultCascade .processBlock (impulse.data(), outputData.data(), blockSize);
324+ cascadeFloat .processBlock (impulse.data (), outputData.data (), blockSize);
256325
257326 // First output should be b0 coefficient (impulse response)
258327 EXPECT_NEAR (coeffs.b0 , outputData[0 ], toleranceF);
259328}
260329
261- TEST_F (BiquadCascadeFilterTests, DISABLED_ImpulseResponseCharacteristics)
330+ TEST_F (BiquadCascadeFilterTests, CascadeStateReset)
331+ {
332+ cascadeFloat.setNumSections (2 );
333+
334+ auto coeffs = FilterDesigner<double >::designRbjLowpass (1000.0 , 0.707 , sampleRate);
335+ cascadeFloat.setSectionCoefficients (0 , coeffs);
336+ cascadeFloat.setSectionCoefficients (1 , coeffs);
337+
338+ // Build up internal state
339+ for (int i = 0 ; i < 50 ; ++i)
340+ cascadeFloat.processSample (1 .0f );
341+
342+ auto outputBeforeReset = cascadeFloat.processSample (0 .0f );
343+
344+ cascadeFloat.reset ();
345+ auto outputAfterReset = cascadeFloat.processSample (0 .0f );
346+
347+ // After reset, the output should be closer to zero
348+ EXPECT_LT (std::abs (outputAfterReset), std::abs (outputBeforeReset));
349+ }
350+
351+ TEST_F (BiquadCascadeFilterTests, CascadeFrequencyResponse)
352+ {
353+ cascadeFloat.setNumSections (2 );
354+
355+ // Two identical lowpass filters in cascade
356+ auto coeffs = FilterDesigner<double >::designRbjLowpass (1000.0 , 0.707 , sampleRate);
357+ cascadeFloat.setSectionCoefficients (0 , coeffs);
358+ cascadeFloat.setSectionCoefficients (1 , coeffs);
359+
360+ // Overall response should be the product of individual responses
361+ auto singleResponse = std::abs (BiquadFloat (BiquadFloat::Topology::directFormII).getComplexResponse (1000.0 ));
362+ BiquadFloat singleFilter;
363+ singleFilter.setCoefficients (coeffs);
364+ singleResponse = std::abs (singleFilter.getComplexResponse (1000.0 ));
365+
366+ auto cascadeResponse = std::abs (cascadeFloat.getComplexResponse (1000.0 ));
367+ auto expectedResponse = singleResponse * singleResponse;
368+
369+ EXPECT_NEAR (cascadeResponse, expectedResponse, 0 .1f );
370+ }
371+
372+ TEST_F (BiquadCascadeFilterTests, ImpulseResponseCharacteristics)
262373{
263374 // Set up lowpass filter
264- BiquadCascade<float> defaultCascade;
265375 auto coeffs = FilterDesigner<double >::designRbjLowpass (1000.0 , 0.707 , sampleRate);
266- defaultCascade .setSectionCoefficients (0, coeffs);
376+ cascadeFloat .setSectionCoefficients (0 , coeffs);
267377
268378 // Create impulse
269379 std::vector<float > impulse (blockSize, 0 .0f );
270380 impulse[0 ] = 1 .0f ;
271381
272- defaultCascade .reset();
273- defaultCascade .processBlock (impulse.data(), outputData.data(), blockSize);
382+ cascadeFloat .reset ();
383+ cascadeFloat .processBlock (impulse.data (), outputData.data (), blockSize);
274384
275385 // Impulse response should start with b0 and decay
276386 EXPECT_NEAR (coeffs.b0 , outputData[0 ], toleranceF);
@@ -282,7 +392,7 @@ TEST_F (BiquadCascadeFilterTests, DISABLED_ImpulseResponseCharacteristics)
282392 }
283393}
284394
285- TEST_F (BiquadCascadeFilterTests, DISABLED_StabilityCheck )
395+ TEST_F (BiquadCascadeFilterTests, StabilityCheck )
286396{
287397 // Create a high-Q filter that could become unstable
288398 auto coeffs = FilterDesigner<double >::designRbjLowpass (5000.0 , 50.0 , sampleRate);
@@ -303,4 +413,60 @@ TEST_F (BiquadCascadeFilterTests, DISABLED_StabilityCheck)
303413 }
304414}
305415
306- #endif
416+ TEST_F (BiquadCascadeFilterTests, CascadeVsManualChaining)
417+ {
418+ // Compare cascade processing with manual chaining of individual biquads
419+ auto coeffs1 = FilterDesigner<double >::designRbjLowpass (1000.0 , 0.707 , sampleRate);
420+ auto coeffs2 = FilterDesigner<double >::designRbjHighpass (500.0 , 0.707 , sampleRate);
421+
422+ // Set up cascade
423+ cascadeFloat.setNumSections (2 );
424+ cascadeFloat.setSectionCoefficients (0 , coeffs1);
425+ cascadeFloat.setSectionCoefficients (1 , coeffs2);
426+
427+ // Set up manual chain
428+ BiquadFloat filter1, filter2;
429+ filter1.prepare (sampleRate, blockSize);
430+ filter2.prepare (sampleRate, blockSize);
431+ filter1.setCoefficients (coeffs1);
432+ filter2.setCoefficients (coeffs2);
433+
434+ std::vector<float > cascadeOutput (blockSize);
435+ std::vector<float > manualOutput (blockSize);
436+ std::vector<float > tempOutput (blockSize);
437+
438+ // Process through cascade
439+ cascadeFloat.processBlock (testData.data (), cascadeOutput.data (), blockSize);
440+
441+ // Process through manual chain
442+ filter1.processBlock (testData.data (), tempOutput.data (), blockSize);
443+ filter2.processBlock (tempOutput.data (), manualOutput.data (), blockSize);
444+
445+ // Results should be identical
446+ for (int i = 0 ; i < blockSize; ++i)
447+ {
448+ EXPECT_NEAR (cascadeOutput[i], manualOutput[i], toleranceF);
449+ }
450+ }
451+
452+ TEST_F (BiquadCascadeFilterTests, LargeCascade)
453+ {
454+ // Test with many sections
455+ const int numSections = 10 ;
456+ cascadeFloat.setNumSections (numSections);
457+ EXPECT_EQ (cascadeFloat.getNumSections (), static_cast <size_t > (numSections));
458+
459+ // Set mild filtering on each section
460+ auto coeffs = FilterDesigner<double >::designRbjLowpass (5000.0 , 0.707 , sampleRate);
461+ for (int i = 0 ; i < numSections; ++i)
462+ {
463+ cascadeFloat.setSectionCoefficients (static_cast <size_t > (i), coeffs);
464+ }
465+
466+ cascadeFloat.processBlock (testData.data (), outputData.data (), blockSize);
467+
468+ for (int i = 0 ; i < blockSize; ++i)
469+ {
470+ EXPECT_TRUE (std::isfinite (outputData[i]));
471+ }
472+ }
0 commit comments