A lightweight C++ oscillator and effects library for real-time audio synthesis, with WebAssembly support for browser-based DSP.
git clone https://github.com/khenderson20/sonic-forge-dsp.git
cd sonic-forge-dsp
cmake -B build -DSONICFORGE_BUILD_TESTS=ON
cmake --build build --parallel
ctest --test-dir build # 61 tests, all should passSonicForge DSP provides a collection of zero-allocation, thread-safe audio processing modules suitable for synthesizers, effects plugins, and modular audio systems:
| Module | Purpose |
|---|---|
Oscillator |
Band-limited sine (4096-entry LUT), saw/square (PolyBLEP anti-aliased), triangle |
StateVariableFilter |
Resonant lowpass/highpass/bandpass/notch (Cytomic/Zavalishin ZDF topology) |
DelayLine |
Fractional-sample delay with none, linear, and 3rd-order Lagrange interpolation |
Waveshaper |
tanh, polynomial soft clip, hard clip, Buchla-style wavefolder |
SmoothedValue |
Linear and multiplicative (exponential) sub-sample parameter ramping |
- No heap allocation in the audio path —
process()andprocess_block()perform zero dynamic memory allocation - Lock-free parameter access — Oscillator and SVF use
std::atomicwithmemory_order_relaxedfor thread-safe modulation - C++17, zero runtime dependencies — GoogleTest is only used for tests (fetched via CPM.cmake)
- WebAssembly ready — compiles to a standalone
.wasmbinary via Emscripten for AudioWorklet use
- No envelopes — no ADSR or envelope generator
- No DSP chaining — no
connect()or node graph; each module is standalone - No polyphony management — no voice pool or voice stealing
- No wavetable oscillator — waveforms are generated algorithmically; no wavetable loading
| Requirement | Version |
|---|---|
| Compiler | GCC 9+ or Clang 10+ |
| CMake | 3.15+ |
| Emscripten (optional) | 3.1.0+ — only needed for Wasm builds |
| Option | Default | Description |
|---|---|---|
SONICFORGE_BUILD_TESTS |
ON |
Build unit tests (fetches GoogleTest automatically) |
SONICFORGE_BUILD_EXAMPLES |
ON |
Build example programs |
SONICFORGE_BUILD_WEB |
OFF |
Build WebAssembly AudioWorklet module (requires emcmake cmake) |
SONICFORGE_OPTIMIZE_FOR_HOST |
OFF |
Add -march=native to Release builds |
SONICFORGE_ENABLE_LTO |
ON |
Enable link-time optimisation for Release builds |
# Debug (default)
cmake -B build -DSONICFORGE_BUILD_TESTS=ON
# Release (optimized)
cmake -B build -DCMAKE_BUILD_TYPE=Release -DSONICFORGE_BUILD_TESTS=ON
# Ninja (for incremental builds)
cmake -B build -G Ninja -DSONICFORGE_BUILD_TESTS=ONAvoid re-downloading GoogleTest on clean builds:
export CPM_SOURCE_CACHE=~/.cache/CPMRequires the Emscripten SDK:
cd web
emcmake cmake -B build-web -DCMAKE_BUILD_TYPE=Release
cmake --build build-webThe output sonicforge.wasm is placed in web/public/. Serve that directory with a static file server to run the 3D visualization demo.
#include <sonicforge/oscillator.hpp>
int main() {
sonicforge::Oscillator osc(sonicforge::Waveform::SINE, 440.0F);
osc.set_sample_rate(48000.0F);
float buffer[256];
osc.process_block(buffer, 256);
// Thread-safe parameter changes (safe from any thread)
osc.set_frequency(880.0F);
osc.set_waveform(sonicforge::Waveform::SAW);
return 0;
}#include <sonicforge/oscillator.hpp>
#include <sonicforge/smoothed_value.hpp>
#include <sonicforge/state_variable_filter.hpp>
#include <sonicforge/waveshaper.hpp>
#include <sonicforge/delayline.hpp>
// Signal: saw → lowpass filter → tanh waveshaper → delay
sonicforge::Oscillator osc{sonicforge::Waveform::SAW, 440.0F, 48000.0F};
sonicforge::StateVariableFilter filter{
sonicforge::FilterMode::Lowpass, 2000.0F, 0.3F, 48000.0F};
sonicforge::WaveshaperProcessor ws{sonicforge::WaveshaperShape::Tanh, 2.0F};
sonicforge::DelayLine<sonicforge::DelayInterpolation::Linear> delay{24000};
delay.set_delay(4800.0F);
delay.set_feedback(0.3F);
float buffer[256];
osc.process_block(buffer, 256); // 1. Oscillator
filter.process_block(buffer, 256); // 2. Filter
ws.process_block(buffer, 256); // 3. Waveshaper
delay.process_block(buffer, 256); // 4. DelaySmoothedValue — click-free parameter ramps:
sonicforge::SmoothedValue<sonicforge::SmoothingMode::Multiplicative> freq;
freq.reset(440.0F, 48000.0F);
freq.set_ramp_duration(0.02F); // 20 ms exponential ramp
freq.set_target(880.0F); // begin ramping
for (int i = 0; i < 256; ++i) {
float g = freq.process(); // one sample of the ramp
buffer[i] *= g;
}Waveshaper — distortion and wavefolding:
float sample = sonicforge::soft_clip_tanh(input * drive); // tanh saturation
sample = sonicforge::wavefold_buchla(input * drive); // Buchla 259 foldDelayLine — fractional-sample delay:
sonicforge::DelayLine<sonicforge::DelayInterpolation::Lagrange3rd> dl{48000};
dl.set_delay(9600.0F); // 200 ms at 48 kHz
float tap = dl.read(4800.5F); // fractional read (no feedback)Via CMake FetchContent (recommended — no install step needed):
include(FetchContent)
FetchContent_Declare(
sonicforge
GIT_REPOSITORY https://github.com/khenderson20/sonic-forge-dsp.git
GIT_TAG main
)
FetchContent_MakeAvailable(sonicforge)
target_link_libraries(your_target PRIVATE sonicforge)Via CMake subdirectory (vendored copy):
add_subdirectory(path/to/sonic-forge-dsp)
target_link_libraries(your_target PRIVATE sonicforge)Via pkg-config (requires cmake --install first):
cmake --install build # installs to /usr/local by default
cmake --install build --prefix /your/prefix # or a custom prefixfind_package(PkgConfig REQUIRED)
pkg_check_modules(SONICFORGE REQUIRED IMPORTED_TARGET sonicforge)
target_link_libraries(your_target PRIVATE PkgConfig::SONICFORGE)If installed to a non-default prefix, tell pkg-config where to look:
export PKG_CONFIG_PATH=/your/prefix/lib/pkgconfig:$PKG_CONFIG_PATHManual compilation (requires cmake --install first):
g++ -std=c++17 -I/usr/local/include your_code.cpp -L/usr/local/lib -lsonicforge -o your_appReplace /usr/local with your install prefix if it differs.
61 test cases across 11 suites cover all modules:
| Module | Cases | Coverage |
|---|---|---|
| Oscillator | 22 | Construction, range, PolyBLEP, LUT accuracy, block/sample processing, sample_at static evaluation |
| SmoothedValue | 7 | Linear/multiplicative ramping, snap, process_block |
| StateVariableFilter | 10 | LP/HP attenuation, DC pass-through, clamping, reset |
| Waveshaper | 10 | Transfer function bounds (tanh, poly, hard clip, fold, rectify), processor, null safety |
| DelayLine | 11 | Integer/fractional delay, feedback, interpolation modes, reset |
| Integration | 1 | Full chain: oscillator → SVF → waveshaper → delay |
ctest --test-dir build --output-on-failureThe project enforces formatting and static analysis via pre-commit hooks and CI:
| Tool | Purpose |
|---|---|
clang-format-18 |
Enforces consistent code style (ColumnLimit 120, 4-space indent) |
clang-tidy-18 |
Static analysis with --warnings-as-errors='*' (bugprone, cppcoreguidelines, misc, modernize, performance, readability, clang-analyzer) |
Generate compile_commands.json and run clang-tidy:
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DSONICFORGE_BUILD_TESTS=ON
clang-tidy --warnings-as-errors='*' -p build \
src/oscillator.cpp src/state_variable_filter.cpp src/delayline.cpp \
tests/oscillator_test.cpp tests/new_modules_test.cppsonic-forge-dsp/
├── include/sonicforge/ # Public API headers (5 modules)
├── src/ # Implementation files
├── tests/ # GoogleTest unit tests (61 cases)
├── examples/ # Example programs
│ ├── sine_example.cpp # Pipeable raw float output
│ └── wav_writer_example.cpp # WAV file generator
├── web/ # WebAssembly build + Three.js demo
│ ├── src/sonicforge_worklet.cpp # C bridge for AudioWorklet
│ └── public/ # Three.js 3D visualization
├── .github/workflows/ci.yml # GitHub Actions CI
├── CMakeLists.txt # Root build configuration
└── Doxyfile # API documentation generator
- Fork the repository and create a feature branch
- Install pre-commit hooks:
bash scripts/install-hooks.sh - Write code following modern C++17 practices, zero allocation in audio paths
- Add unit tests for new functionality
- Document public APIs with Doxygen comments (
@brief,@param,@return) - Ensure clang-format, clang-tidy, and all tests pass before submitting a PR
MIT License — see the LICENSE file for details.
GoogleTest is used solely for the test suite and is fetched automatically by CPM.cmake. It is not linked into the installed library and does not affect the licensing of libsonicforge.
The API reference is published automatically to GitHub Pages on every merge to main:
https://khenderson20.github.io/sonic-forge-dsp/
To build the docs locally:
doxygen DoxyfileOutput will be available at docs/html/index.html.
- CHANGELOG.md — per-version history of changes
- ROADMAP.md — planned features and longer-term direction

