Skip to content

Commit c4a8b8a

Browse files
test: expand cabinet IR and DSP stability coverage (#207)
* test: add cabinet sim stability and silence tests * test: expand cabinet IR and DSP stability coverage * test: expand IR and convolution engine coverage * test: address CI and regression review feedback * test: improve DSP and IR coverage * fix: correct wav loader assertion * test: improve wav loader branch coverage * test: improve wav loader and DSP coverage * fix: add missing cstdint include for wav loader tests * test: improve wav loader parser coverage * test: expand wav loader edge case coverage * test: improve wav loader edge case coverage * fix: stabilize wav loader coverage tests across CI * fix: remove tmp paths from wav loader tests * test: stabilize wav loader coverage assets * test: replace M_PI with project constant * fix: ensure wav test asset directory exists in CI * fix: remove stray wav test artifacts * test: improve convolution engine coverage * fix: stabilize convolution FFT coverage test
1 parent d04628f commit c4a8b8a

6 files changed

Lines changed: 572 additions & 1 deletion

File tree

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,8 @@ else() # NOT EMSCRIPTEN, ANDROID, or IOS
512512
tests/test_command_history.cpp
513513
tests/test_snapshot_manager.cpp
514514
tests/test_cabinet_sim_ir.cpp
515+
tests/test_wav_loader.cpp
516+
tests/test_convolution_engine.cpp
515517
tests/test_midi_manager.cpp
516518
tests/test_unsaved_preset.cpp
517519
tests/test_audio_graph.cpp

tests/assets/.gitkeep

Whitespace-only changes.

tests/test_cabinet_sim_ir.cpp

Lines changed: 185 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ static bool write_wav_mono_pcm16(const std::string& path,
6363
int16_t v = static_cast<int16_t>(std::lrint(x * 32767.0f));
6464
write_le16(out, static_cast<uint16_t>(v));
6565
}
66-
66+
out.close();
6767
return true;
6868
}
6969

@@ -95,6 +95,92 @@ TEST(CabinetSim_IR_UnitImpulse_Identity) {
9595
std::remove(path.c_str());
9696
}
9797

98+
TEST(CabinetSim_IR_MissingFileReturnsFalse) {
99+
std::string valid = "valid_ir.wav";
100+
101+
ASSERT_TRUE(write_wav_mono_pcm16(valid, {1.0f}, 48000));
102+
103+
CabinetSim cab;
104+
cab.set_sample_rate(48000);
105+
106+
ASSERT_TRUE(cab.load_ir(valid));
107+
ASSERT_TRUE(cab.has_ir());
108+
109+
ASSERT_FALSE(cab.load_ir("definitely_missing_ir.wav"));
110+
111+
// Ensure failed load clears previous state
112+
ASSERT_TRUE(cab.has_ir());
113+
114+
std::remove(valid.c_str());
115+
}
116+
117+
TEST(CabinetSim_IR_MalformedFileReturnsFalse) {
118+
const std::string path = "bad_ir.wav";
119+
120+
{
121+
std::ofstream out(path, std::ios::binary);
122+
out << "this is not a wav";
123+
}
124+
125+
CabinetSim cab;
126+
cab.set_sample_rate(48000);
127+
128+
ASSERT_FALSE(cab.load_ir(path));
129+
ASSERT_FALSE(cab.has_ir());
130+
131+
std::remove(path.c_str());
132+
}
133+
134+
TEST(CabinetSim_IR_LongRunStability) {
135+
const int block_size = 256;
136+
137+
std::string path = "test_longrun_ir.wav";
138+
ASSERT_TRUE(write_wav_mono_pcm16(path, {1.0f, 0.5f, 0.25f}, 48000));
139+
140+
CabinetSim cab;
141+
cab.set_sample_rate(48000);
142+
143+
ASSERT_TRUE(cab.load_ir(path));
144+
145+
std::vector<float> buf(block_size);
146+
147+
for (int iter = 0; iter < 1000; ++iter) {
148+
for (int i = 0; i < block_size; ++i) {
149+
buf[i] = std::sin(0.01f * static_cast<float>(i));
150+
}
151+
152+
cab.process(buf.data(), block_size);
153+
154+
for (float s : buf) {
155+
ASSERT_TRUE(std::isfinite(s));
156+
}
157+
}
158+
159+
std::remove(path.c_str());
160+
}
161+
162+
TEST(CabinetSim_IR_SilenceRemainsSilent) {
163+
const int block_size = 256;
164+
165+
std::string path = "test_silence_ir.wav";
166+
ASSERT_TRUE(write_wav_mono_pcm16(path, {1.0f}, 48000));
167+
168+
CabinetSim cab;
169+
cab.set_sample_rate(48000);
170+
171+
ASSERT_TRUE(cab.load_ir(path));
172+
173+
std::vector<float> buf(block_size, 0.0f);
174+
175+
cab.process(buf.data(), block_size);
176+
177+
for (float s : buf) {
178+
ASSERT_NEAR(s, 0.0f, 1e-6f);
179+
}
180+
181+
std::remove(path.c_str());
182+
}
183+
98184
TEST(CabinetSim_IR_DelayedImpulse) {
99185
const int block_size = 256;
100186

@@ -122,3 +208,101 @@ TEST(CabinetSim_IR_DelayedImpulse) {
122208
std::remove(path.c_str());
123209
}
124210

211+
TEST(CabinetSim_IR_MetadataQueries) {
212+
std::string path = "metadata_ir.wav";
213+
214+
ASSERT_TRUE(write_wav_mono_pcm16(path, {1.0f, 0.5f}, 48000));
215+
216+
CabinetSim cab;
217+
cab.set_sample_rate(48000);
218+
219+
ASSERT_TRUE(cab.load_ir(path));
220+
221+
ASSERT_TRUE(cab.has_ir());
222+
ASSERT_EQ(cab.ir_name(), "metadata_ir.wav");
223+
ASSERT_EQ(cab.ir_path(), path);
224+
ASSERT_GT(cab.ir_duration_ms(), 0.0f);
225+
226+
std::remove(path.c_str());
227+
}
228+
229+
TEST(CabinetSim_ClearIR_ResetsState) {
230+
std::string path = "clear_ir.wav";
231+
232+
ASSERT_TRUE(write_wav_mono_pcm16(path, {1.0f}, 48000));
233+
234+
CabinetSim cab;
235+
cab.set_sample_rate(48000);
236+
237+
ASSERT_TRUE(cab.load_ir(path));
238+
ASSERT_TRUE(cab.has_ir());
239+
240+
cab.clear_ir();
241+
242+
ASSERT_FALSE(cab.has_ir());
243+
ASSERT_TRUE(cab.ir_name().empty());
244+
ASSERT_TRUE(cab.ir_path().empty());
245+
ASSERT_NEAR(cab.ir_duration_ms(), 0.0f, 1e-6f);
246+
247+
std::remove(path.c_str());
248+
}
249+
250+
TEST(CabinetSim_SetSampleRate_ReloadsIR) {
251+
std::string path = "reload_ir.wav";
252+
253+
ASSERT_TRUE(write_wav_mono_pcm16(path, {1.0f, 0.5f}, 44100));
254+
255+
CabinetSim cab;
256+
cab.set_sample_rate(44100);
257+
258+
ASSERT_TRUE(cab.load_ir(path));
259+
260+
float before = cab.ir_duration_ms();
261+
262+
cab.set_sample_rate(48000);
263+
264+
ASSERT_TRUE(cab.has_ir());
265+
ASSERT_GT(cab.ir_duration_ms(), 0.0f);
266+
ASSERT_NEAR(before, cab.ir_duration_ms(), 5.0f);
267+
268+
std::remove(path.c_str());
269+
}
270+
271+
TEST(CabinetSim_ClearIRRemovesState) {
272+
std::string path = "clear_ir.wav";
273+
274+
ASSERT_TRUE(
275+
write_wav_mono_pcm16(path, {1.0f}, 48000));
276+
277+
CabinetSim cab;
278+
279+
cab.set_sample_rate(48000);
280+
281+
ASSERT_TRUE(cab.load_ir(path));
282+
283+
ASSERT_TRUE(cab.has_ir());
284+
285+
cab.clear_ir();
286+
287+
ASSERT_FALSE(cab.has_ir());
288+
289+
std::remove(path.c_str());
290+
}
291+
292+
TEST(CabinetSim_DisabledProcessPassthrough) {
293+
CabinetSim cab;
294+
295+
cab.set_enabled(false);
296+
297+
float buf[128];
298+
299+
for (int i = 0; i < 128; ++i) {
300+
buf[i] = 0.25f;
301+
}
302+
303+
cab.process(buf, 128);
304+
305+
for (float s : buf) {
306+
ASSERT_NEAR(s, 0.25f, 1e-6f);
307+
}
308+
}

tests/test_convolution_engine.cpp

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#include "test_framework.h"
2+
#include "audio/dsp/convolution_engine.h"
3+
4+
#include <vector>
5+
#include <cmath>
6+
#include <memory>
7+
8+
using namespace Amplitron;
9+
using namespace TestFramework;
10+
11+
TEST(ConvolutionEngine_ResetClearsState) {
12+
std::vector<float> ir = {1.0f, 0.5f, 0.25f};
13+
14+
auto kernel =
15+
std::make_shared<ConvolutionKernel>(ir, 256);
16+
17+
ConvolutionEngine conv;
18+
19+
conv.set_kernel(kernel);
20+
21+
std::vector<float> buffer(256, 1.0f);
22+
23+
conv.process(buffer.data(), 256);
24+
25+
conv.reset();
26+
27+
std::vector<float> silent(256, 0.0f);
28+
29+
conv.process(silent.data(), 256);
30+
31+
for (float s : silent) {
32+
ASSERT_TRUE(std::isfinite(s));
33+
ASSERT_NEAR(s, 0.0f, 1e-4f);
34+
}
35+
}
36+
37+
TEST(ConvolutionEngine_PartitionedFFTPath) {
38+
std::vector<float> ir(1024, 0.0f);
39+
40+
// simple impulse-like IR
41+
ir[0] = 1.0f;
42+
ir[128] = 0.5f;
43+
ir[512] = 0.25f;
44+
45+
auto kernel =
46+
std::make_shared<ConvolutionKernel>(ir, 256);
47+
48+
ConvolutionEngine conv;
49+
conv.set_kernel(kernel);
50+
51+
std::vector<float> buffer(256);
52+
53+
for (size_t i = 0; i < buffer.size(); ++i) {
54+
buffer[i] =
55+
std::sin(static_cast<float>(i) * 0.01f);
56+
}
57+
58+
conv.process(buffer.data(), 256);
59+
60+
for (float s : buffer) {
61+
ASSERT_TRUE(std::isfinite(s));
62+
}
63+
}
64+
65+
TEST(ConvolutionEngine_HasKernelAfterSetKernel) {
66+
std::vector<float> ir = {1.0f};
67+
68+
auto kernel =
69+
std::make_shared<ConvolutionKernel>(ir, 256);
70+
71+
ConvolutionEngine conv;
72+
73+
ASSERT_FALSE(conv.has_kernel());
74+
75+
conv.set_kernel(kernel);
76+
77+
ASSERT_TRUE(conv.has_kernel());
78+
}
79+
80+
TEST(ConvolutionEngine_OverlapAddConsistency) {
81+
std::vector<float> ir = {1.0f, 0.5f, 0.25f};
82+
83+
auto kernel =
84+
std::make_shared<ConvolutionKernel>(ir, 256);
85+
86+
ConvolutionEngine conv;
87+
88+
conv.set_kernel(kernel);
89+
90+
std::vector<float> buffer(1024);
91+
92+
for (size_t i = 0; i < buffer.size(); ++i) {
93+
buffer[i] = std::sin(
94+
static_cast<float>(i) * 0.01f);
95+
}
96+
97+
for (int i = 0; i < 1024; i += 256) {
98+
conv.process(buffer.data() + i, 256);
99+
}
100+
101+
for (float s : buffer) {
102+
ASSERT_TRUE(std::isfinite(s));
103+
}
104+
}
105+
106+
TEST(ConvolutionEngine_ClearKernel) {
107+
std::vector<float> ir = {1.0f};
108+
109+
auto kernel =
110+
std::make_shared<ConvolutionKernel>(ir, 256);
111+
112+
ConvolutionEngine conv;
113+
114+
conv.set_kernel(kernel);
115+
116+
ASSERT_TRUE(conv.has_kernel());
117+
118+
conv.set_kernel(nullptr);
119+
120+
ASSERT_FALSE(conv.has_kernel());
121+
}
122+
123+
TEST(ConvolutionEngine_SmallBlockProcessing) {
124+
std::vector<float> ir = {1.0f, 0.5f};
125+
126+
auto kernel =
127+
std::make_shared<ConvolutionKernel>(ir, 64);
128+
129+
ConvolutionEngine conv;
130+
131+
conv.set_kernel(kernel);
132+
133+
std::vector<float> buffer(32, 1.0f);
134+
135+
conv.process(buffer.data(), 32);
136+
137+
for (float s : buffer) {
138+
ASSERT_TRUE(std::isfinite(s));
139+
}
140+
}
141+
142+
TEST(ConvolutionEngine_ZeroInputRemainsSilent) {
143+
std::vector<float> ir = {1.0f, 0.5f};
144+
145+
auto kernel =
146+
std::make_shared<ConvolutionKernel>(ir, 128);
147+
148+
ConvolutionEngine conv;
149+
150+
conv.set_kernel(kernel);
151+
152+
std::vector<float> buffer(128, 0.0f);
153+
154+
conv.process(buffer.data(), 128);
155+
156+
for (float s : buffer) {
157+
ASSERT_NEAR(s, 0.0f, 1e-5f);
158+
}
159+
}
160+
161+
TEST(ConvolutionKernel_PartitionFreqBounds) {
162+
std::vector<float> ir(512, 0.5f);
163+
164+
ConvolutionKernel kernel(ir, 256);
165+
166+
ASSERT_TRUE(kernel.partition_freq(0) != nullptr);
167+
ASSERT_TRUE(kernel.partition_freq(-1) == nullptr);
168+
ASSERT_TRUE(kernel.partition_freq(999) == nullptr);
169+
}
170+
171+
TEST(ConvolutionKernel_EmptyIR) {
172+
std::vector<float> ir;
173+
174+
ConvolutionKernel kernel(ir, 256);
175+
176+
ASSERT_EQ(kernel.num_partitions(), 0);
177+
}
178+

0 commit comments

Comments
 (0)