Testing your plugin
From a unit test of one filter to a DAW driving the built binary.
On this page
What a generated project already runs
ctest in a project from sonore_new_plugin.cmake runs:
MyFilterApp --verify: the standalone build renders the plugin offline, with no audio device and no window, and fails on a NaN or an infinity, on silence where there should be sound, on a render that blows up, and on a malformed descriptor. An effect is also fed a file it writes itself; an instrument must sound on a held note and fall silent after its release.- The real-time audit (
sonore_add_rt_test, with a source checkout of the SDK): your plugin’s source compiled into a program that counts every allocation made on the audio thread, across sample rates and block sizes. Allocations only; locks are not measured.
MyFilterApp --render out.wav 2 writes two seconds of the plugin to a WAV, for
listening or for a comparison of your own.
Unit tests
sonore/unit_test.h is a small framework in the shape code from the reference
expects: a sonore::UnitTest per area, beginTest and the expect family
inside runTest(), a static instance that registers itself, and a
sonore::UnitTestRunner. Each test gets a reproducible random generator
(getRandom()), and the runner logs the seed so a failure can be replayed.
#include <sonore/dsp.h>
#include <sonore/unit_test.h>
#include <algorithm>
#include <cmath>
struct LowpassTests : sonore::UnitTest {
LowpassTests() : UnitTest("Lowpass", "dsp") {}
void runTest() override {
beginTest("DC passes");
sonore::SVF f;
f.setSampleRate(48000.0f);
f.set(1000.0f, 0.707f);
for (int i = 0; i < 48000; ++i) f.process(1.0f);
expectWithinAbsoluteError(f.lp, 1.0f, 1e-3f);
beginTest("Nyquist does not");
f.reset();
float peak = 0.0f;
for (int i = 0; i < 48000; ++i) {
f.process(i % 2 ? -1.0f : 1.0f);
if (i >= 4800) peak = (std::max)(peak, std::fabs(f.lp)); // after the transient
}
expectLessThan(peak, 0.01f, "Nyquist leaks through the lowpass");
}
};
static LowpassTests lowpassTests;
int main() {
sonore::UnitTestRunner runner;
runner.runAllTests();
return runner.getTotalFailures() == 0 ? 0 : 1;
}Register it like any program: add_executable, target_link_libraries(... sonore_sdk),
add_test. Grade against a number you can defend – the standard, the paper,
the analytic result – not against what the code printed last time.
The format owners’ validators
What a host vendor or a store runs first:
- clap-validator (github.com/free-audio/clap-validator):
clap-validator validate MyFilter.clap. - pluginval (github.com/Tracktion/pluginval):
pluginval --strictness-level 10 --validate vst3/MyFilterVST3.vst3. The SDK’s examples pass at strictness 10, editor tests included on Windows. - auval on macOS, for the Audio Unit:
auval -v aufx <subtype> <manufacturer>after copying the.componentinto~/Library/Audio/Plug-Ins/Components.
On Windows, clap-validator refuses a preset location that does not begin with
/, which every drive-letter path fails, so its preset-discovery checks fail
on the default preset folder. Run it with SONORE_PRESET_ROOT set to a folder
spelled //?/C:/...: Windows opens that like any other path, and the
validator accepts it.
A real DAW
npm run verify:reaper (from the repository root, with REAPER installed and a
Release build of the SDK) hands the CLAP, VST3 and LV2 build of every SDK
example to REAPER and checks what REAPER made of them: the scan, offline renders
against the SDK’s own host, automation, a session saved and reopened, latency
compensation, editors opening and closing. It runs REAPER with a profile of its
own and never touches yours; --only, --skip-editor and --rates narrow it.
It is the check to run after changing anything in a process path, state,
parameters, presets or latency in the SDK itself.
The SDK’s own tests
For a change to the SDK, each area has a standalone test program,
sdk/tests/<area>_test.cpp: a main function, a check(bool, const char*) helper
printing ok message or FAIL message, real numbers rather than “it
compiles” (round trips, published test vectors, reference outputs), a last line
<AREA> TESTS PASSED or FAILED, and a non-zero exit on any failure. Parsers
of untrusted input also get a fuzzer, sdk/tests/fuzz_<area>.cpp, run under
AddressSanitizer. npm run verify:sdk builds and runs all of it with the
validators and the browser build; npm run verify:sanitize runs the slow
sanitizer legs. CONTRIBUTING.md at the repository root has the rules.