Skip to content

Commit bcc44d3

Browse files
committed
More test coverage
1 parent c97b8f7 commit bcc44d3

File tree

8 files changed

+2051
-0
lines changed

8 files changed

+2051
-0
lines changed
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
/*
2+
==============================================================================
3+
4+
This file is part of the YUP library.
5+
Copyright (c) 2025 - [email protected]
6+
7+
YUP is an open source library subject to open-source licensing.
8+
9+
The code included in this file is provided under the terms of the ISC license
10+
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
11+
to use, copy, modify, and/or distribute this software for any purpose with or
12+
without fee is hereby granted provided that the above copyright notice and
13+
this permission notice appear in all copies.
14+
15+
YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
16+
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
17+
DISCLAIMED.
18+
19+
==============================================================================
20+
*/
21+
22+
#include <yup_dsp/yup_dsp.h>
23+
24+
#include <gtest/gtest.h>
25+
26+
using namespace yup;
27+
28+
namespace
29+
{
30+
constexpr double tolerance = 1e-4;
31+
constexpr float toleranceF = 1e-4f;
32+
constexpr double sampleRate = 44100.0;
33+
constexpr int blockSize = 256;
34+
} // namespace
35+
36+
//==============================================================================
37+
class BiquadCascadeFilterTests : public ::testing::Test
38+
{
39+
protected:
40+
void SetUp() override
41+
{
42+
cascadeFloat.prepare (sampleRate, blockSize);
43+
cascadeDouble.prepare (sampleRate, blockSize);
44+
45+
// Initialize test vectors
46+
testData.resize (blockSize);
47+
outputData.resize (blockSize);
48+
doubleTestData.resize (blockSize);
49+
doubleOutputData.resize (blockSize);
50+
51+
for (int i = 0; i < blockSize; ++i)
52+
{
53+
testData[i] = static_cast<float> (i) / blockSize - 0.5f;
54+
doubleTestData[i] = static_cast<double> (i) / blockSize - 0.5;
55+
}
56+
}
57+
58+
BiquadCascade<float> cascadeFloat { 2 };
59+
BiquadCascade<double> cascadeDouble { 2 };
60+
61+
std::vector<float> testData;
62+
std::vector<float> outputData;
63+
std::vector<double> doubleTestData;
64+
std::vector<double> doubleOutputData;
65+
};
66+
67+
//==============================================================================
68+
TEST_F (BiquadCascadeFilterTests, DefaultConstructorInitializes)
69+
{
70+
BiquadCascade<float> defaultCascade;
71+
EXPECT_EQ (1, defaultCascade.getNumSections());
72+
}
73+
74+
TEST_F (BiquadCascadeFilterTests, ConstructorWithSectionsInitializes)
75+
{
76+
BiquadCascade<float> cascade (4);
77+
EXPECT_EQ (4, cascade.getNumSections());
78+
}
79+
80+
TEST_F (BiquadCascadeFilterTests, SetNumSectionsChangesSize)
81+
{
82+
EXPECT_EQ (2, cascadeFloat.getNumSections());
83+
84+
cascadeFloat.setNumSections (5);
85+
EXPECT_EQ (5, cascadeFloat.getNumSections());
86+
87+
cascadeFloat.setNumSections (1);
88+
EXPECT_EQ (1, cascadeFloat.getNumSections());
89+
}
90+
91+
TEST_F (BiquadCascadeFilterTests, SetAndGetSectionCoefficients)
92+
{
93+
// Create lowpass coefficients
94+
auto coeffs = FilterDesigner<double>::designRbjLowpass (1000.0, 0.707, sampleRate);
95+
96+
cascadeDouble.setSectionCoefficients (0, coeffs);
97+
auto retrievedCoeffs = cascadeDouble.getSectionCoefficients (0);
98+
99+
EXPECT_NEAR (coeffs.b0, retrievedCoeffs.b0, tolerance);
100+
EXPECT_NEAR (coeffs.b1, retrievedCoeffs.b1, tolerance);
101+
EXPECT_NEAR (coeffs.b2, retrievedCoeffs.b2, tolerance);
102+
EXPECT_NEAR (coeffs.a1, retrievedCoeffs.a1, tolerance);
103+
EXPECT_NEAR (coeffs.a2, retrievedCoeffs.a2, tolerance);
104+
}
105+
106+
TEST_F (BiquadCascadeFilterTests, InvalidSectionIndexHandling)
107+
{
108+
auto coeffs = FilterDesigner<double>::designRbjLowpass (1000.0, 0.707, sampleRate);
109+
110+
// Should not crash with invalid index
111+
cascadeDouble.setSectionCoefficients (999, coeffs);
112+
113+
// Should return empty coefficients for invalid index
114+
auto emptyCoeffs = cascadeDouble.getSectionCoefficients (999);
115+
EXPECT_EQ (1.0, emptyCoeffs.b0); // Default biquad passes through (b0=1)
116+
EXPECT_EQ (0.0, emptyCoeffs.b1);
117+
EXPECT_EQ (0.0, emptyCoeffs.b2);
118+
EXPECT_EQ (0.0, emptyCoeffs.a1);
119+
EXPECT_EQ (0.0, emptyCoeffs.a2);
120+
}
121+
122+
TEST_F (BiquadCascadeFilterTests, ProcessesFloatSamples)
123+
{
124+
// Set up lowpass filter on first section
125+
auto coeffs = FilterDesigner<double>::designRbjLowpass (1000.0, 0.707, sampleRate);
126+
cascadeFloat.setSectionCoefficients (0, coeffs);
127+
128+
cascadeFloat.processBlock (testData.data(), outputData.data(), blockSize);
129+
130+
// Output should be different from input (filtered)
131+
bool outputDiffers = false;
132+
for (int i = 0; i < blockSize; ++i)
133+
{
134+
if (std::abs (outputData[i] - testData[i]) > toleranceF)
135+
{
136+
outputDiffers = true;
137+
break;
138+
}
139+
}
140+
EXPECT_TRUE (outputDiffers);
141+
142+
// Output should not contain NaN or inf
143+
for (int i = 0; i < blockSize; ++i)
144+
{
145+
EXPECT_TRUE (std::isfinite (outputData[i]));
146+
}
147+
}
148+
149+
TEST_F (BiquadCascadeFilterTests, ProcessesDoubleSamples)
150+
{
151+
// Set up lowpass filter on first section
152+
auto coeffs = FilterDesigner<double>::designRbjLowpass (1000.0, 0.707, sampleRate);
153+
cascadeDouble.setSectionCoefficients (0, coeffs);
154+
155+
cascadeDouble.processBlock (doubleTestData.data(), doubleOutputData.data(), blockSize);
156+
157+
// Output should be different from input (filtered)
158+
bool outputDiffers = false;
159+
for (int i = 0; i < blockSize; ++i)
160+
{
161+
if (std::abs (doubleOutputData[i] - doubleTestData[i]) > tolerance)
162+
{
163+
outputDiffers = true;
164+
break;
165+
}
166+
}
167+
EXPECT_TRUE (outputDiffers);
168+
169+
// Output should not contain NaN or inf
170+
for (int i = 0; i < blockSize; ++i)
171+
{
172+
EXPECT_TRUE (std::isfinite (doubleOutputData[i]));
173+
}
174+
}
175+
176+
TEST_F (BiquadCascadeFilterTests, MultipleSectionsCascadeCorrectly)
177+
{
178+
// Set up two identical lowpass sections
179+
auto coeffs = FilterDesigner<double>::designRbjLowpass (1000.0, 0.707, sampleRate);
180+
181+
cascadeDouble.setSectionCoefficients (0, coeffs);
182+
cascadeDouble.setSectionCoefficients (1, coeffs);
183+
184+
// Process with cascade
185+
cascadeDouble.processBlock (doubleTestData.data(), doubleOutputData.data(), blockSize);
186+
187+
// Create single section for comparison
188+
BiquadCascade<double> singleSection (1);
189+
singleSection.prepare (sampleRate, blockSize);
190+
singleSection.setSectionCoefficients (0, coeffs);
191+
192+
std::vector<double> singleOutput (blockSize);
193+
singleSection.processBlock (doubleTestData.data(), singleOutput.data(), blockSize);
194+
195+
// The two-section cascade should have more attenuation than single section
196+
double cascadeEnergy = 0.0;
197+
double singleEnergy = 0.0;
198+
199+
for (int i = 0; i < blockSize; ++i)
200+
{
201+
cascadeEnergy += doubleOutputData[i] * doubleOutputData[i];
202+
singleEnergy += singleOutput[i] * singleOutput[i];
203+
}
204+
205+
// Cascade should have less energy (more filtering)
206+
EXPECT_LT (cascadeEnergy, singleEnergy);
207+
}
208+
209+
TEST_F (BiquadCascadeFilterTests, InPlaceProcessing)
210+
{
211+
auto coeffs = FilterDesigner<double>::designRbjLowpass (1000.0, 0.707, sampleRate);
212+
cascadeFloat.setSectionCoefficients (0, coeffs);
213+
214+
// Make a copy for comparison
215+
std::vector<float> originalData = testData;
216+
217+
// Process in-place
218+
cascadeFloat.processBlock (testData.data(), testData.data(), blockSize);
219+
220+
// Output should be different from original
221+
bool outputDiffers = false;
222+
for (int i = 0; i < blockSize; ++i)
223+
{
224+
if (std::abs (testData[i] - originalData[i]) > toleranceF)
225+
{
226+
outputDiffers = true;
227+
break;
228+
}
229+
}
230+
EXPECT_TRUE (outputDiffers);
231+
}
232+
233+
TEST_F (BiquadCascadeFilterTests, ResetClearsState)
234+
{
235+
auto coeffs = FilterDesigner<double>::designRbjLowpass (1000.0, 0.707, sampleRate);
236+
cascadeFloat.setSectionCoefficients (0, coeffs);
237+
238+
// Process some data to build up state
239+
cascadeFloat.processBlock (testData.data(), outputData.data(), blockSize);
240+
241+
// Reset and process impulse
242+
cascadeFloat.reset();
243+
244+
std::vector<float> impulse (blockSize, 0.0f);
245+
impulse[0] = 1.0f;
246+
247+
cascadeFloat.processBlock (impulse.data(), outputData.data(), blockSize);
248+
249+
// First output should be b0 coefficient (impulse response)
250+
EXPECT_NEAR (coeffs.b0, outputData[0], toleranceF);
251+
}
252+
253+
TEST_F (BiquadCascadeFilterTests, ImpulseResponseCharacteristics)
254+
{
255+
// Set up lowpass filter
256+
auto coeffs = FilterDesigner<double>::designRbjLowpass (1000.0, 0.707, sampleRate);
257+
cascadeFloat.setSectionCoefficients (0, coeffs);
258+
259+
// Create impulse
260+
std::vector<float> impulse (blockSize, 0.0f);
261+
impulse[0] = 1.0f;
262+
263+
cascadeFloat.reset();
264+
cascadeFloat.processBlock (impulse.data(), outputData.data(), blockSize);
265+
266+
// Impulse response should start with b0 and decay
267+
EXPECT_NEAR (coeffs.b0, outputData[0], toleranceF);
268+
269+
// Response should be finite
270+
for (int i = 0; i < blockSize; ++i)
271+
{
272+
EXPECT_TRUE (std::isfinite (outputData[i]));
273+
}
274+
}
275+
276+
TEST_F (BiquadCascadeFilterTests, StabilityCheck)
277+
{
278+
// Create a high-Q filter that could become unstable
279+
auto coeffs = FilterDesigner<double>::designRbjLowpass (5000.0, 50.0, sampleRate);
280+
cascadeFloat.setSectionCoefficients (0, coeffs);
281+
282+
// Process white noise-like signal
283+
std::vector<float> noiseInput (blockSize);
284+
for (int i = 0; i < blockSize; ++i)
285+
noiseInput[i] = (static_cast<float> (rand()) / RAND_MAX) * 2.0f - 1.0f;
286+
287+
cascadeFloat.processBlock (noiseInput.data(), outputData.data(), blockSize);
288+
289+
// Output should remain finite
290+
for (int i = 0; i < blockSize; ++i)
291+
{
292+
EXPECT_TRUE (std::isfinite (outputData[i]));
293+
EXPECT_LT (std::abs (outputData[i]), 10.0f); // Reasonable bounds
294+
}
295+
}

0 commit comments

Comments
 (0)