>_EXECUTIVE SUMMARY
zpwr-synth is a fully modular polyphonic synthesizer plugin built on JUCE 8.0.13 (C++20, CMake 3.22+), shipping as AU, VST3, CLAP and Standalone. There is no fixed signal path: the plugin runs the shared modular patch graph from the zpwr-patch-core submodule, and a patch is the voice, instantiated across the voice pool. dsp/SynthModules.cpp registers 309 synth-specific DSP node types onto the core's zpc::ModuleRegistry — the same registry the live reference is generated from — spanning virtual-analog and wavetable oscillators, FM/additive/granular generators, hardstyle and physical-modelling voices, filters, shapers and CV modulators. The DSP core (dsp/ + src/) is roughly 12.3k lines of own source; the pure-DSP layer (zpwr_dsp) carries no JUCE dependency and is headless-testable. Version 0.2.23.
~MODULE MAP
The processor hosts a polyphonic pool of voices, each running the shared signal-agnostic node graph from zpwr-patch-core. The synth-specific generators, voices, filters and modulators are a DSP pack registered onto that core registry.
| Module | Source | Role |
|---|---|---|
| ZpwrSynthProcessor | src/PluginProcessor.cpp/.h | JUCE AudioProcessor; hosts the WebView editor, host params (soft keys + auto pool), MIDI/MPE intake, preset state. |
| PolyEngine | dsp/PolyEngine.cpp/.h | Voice pool (ceiling kMaxVoices = 32); poly / mono / legato / duophonic modes; unison stacking (kMaxUnison = 11); parallel per-voice rendering via worker threads. |
| RuntimeGraph (per voice) | libs/zpwr-patch-core | Shared signal-agnostic node graph evaluated per sample; nodes carry type + 4 inputs + up to 12 params + 1 output, fed per-voice external sources. |
| Voice / Layer / Instrument | dsp/Voice.cpp, Layer.cpp, Instrument.cpp | Voice lifecycle and layering (kMaxLayers = 16) over the graph engine. |
| Oscillators & generators | dsp/SynthModules.cpp | Osc, Wt (wavetable), Supersaw, FM, Sub, Sync, Additive, Vector, ChordOsc, DCO/VCO, Vosim, PAF, Shepard, Buzz, Bytebeat, Granular, Sample — most with unison Voices + Detune. |
| Physical / instrument voices | dsp/SynthModules.cpp | Karplus and a large family of waveguide / modal voices (strings, winds, brass, mallets, organs, world instruments) registered as self-contained note-driven nodes. |
| Hardstyle / genre voices | dsp/SynthModules.cpp | HardKick, Screech, Hoover, Reese and drum-machine voices (TR-808/909/606) for techno / hardstyle / DnB material. |
| Filters & shapers | dsp/SynthModules.cpp | TPT state-variable Filter, DiodeLadder (303-style), Folder, Waveshaper, Crusher (bit/SR reduction), Drive, RingMod. |
| Envelopes & modulators | dsp/Envelope.h, LFO.h, ModMatrix.h, SynthModules.cpp | ADSR Env, LFO, StepLFO, NoiseLFO, SampleHold, Glide, Scaler; per-param mod matrix routing source → (node, param) × depth. |
| Utility / routing | dsp/SynthModules.cpp | VCA (in1 × in2), Gain, Mixer, Delay, Noise — signal plumbing inside the graph. |
| Editor & preset UI | src/PluginEditor.cpp, PresetManager.cpp, PresetBrowser.cpp | WebView patcher (served from BinaryData), factory + user preset management, NKS export. |
| Sample / wavetable stores | src/SampleBank.cpp, SampleStore.cpp, UserWavetableStore.h | Backing stores for Sample/Granular slots and user wavetables (resources/samples.bin, wavetables.bin). |
~ARCHITECTURE
Headline structural facts about the engine — how a single patch definition becomes a polyphonic voice and what feeds it each sample.
Patch is the voice
No fixed signal path. The default voice is Osc(Note) → VCA ← Env(Gate); each node carries a type, 4 inputs, up to 12 params and one output. The patch is saved as JSON in plugin state.
Shared graph core
The graph engine is zpwr-patch-core (libs/zpwr-patch-core, a git submodule) — the same signal-agnostic cable-routing core that powers zpwr-fx. PolyEngine holds one zpc::RuntimeGraph per voice.
Per-voice external sources
Fed each sample: In L/R, Noise, Soft Keys (expandable pool), Note, Gate, Velocity, Mod Wheel, Pitch Bend, Aftertouch, MPE Pressure / Slide / Bend, plus a first-class Trigger impulse on each note-on edge.
Unison & layering
Every oscillator carries a Voices (1–11) unison param plus Detune (cents). Pool ceiling kMaxVoices = 32, kMaxUnison = 11, kMaxLayers = 16; Voices = 1 keeps classic mono behaviour.
Soft keys & automation
32 soft keys (16 active by default) + a reserved pool of 96 Auto N host params = 128 CC-mappable slots, one per MIDI CC. Right-click any block param to bind it (the Kontakt/Reaktor model).
Headless DSP layer
The pure-DSP layer (zpwr_dsp) carries no JUCE dependency and is headless-testable. Generators read each voice's Note/Gate from the core's ComputeContext::external array.
~DEPENDENCY FOOTPRINT
JUCE and the CLAP wrapper are vendored via FetchContent; the patch graph and clip engine are in-tree git submodules. No system installs are required.
| Dependency | Pin | Notes |
|---|---|---|
| JUCE | 8.0.13 | Vendored via FetchContent (no system install). Plugin formats AU / VST3 / Standalone; WebView editor (NEEDS_WEB_BROWSER). |
| clap-juce-extensions | pinned commit | Adds the CLAP format on top of the JUCE wrapper (same path zpwr-fx uses). |
| zpwr-patch-core | submodule (libs/) | Shared modular patch graph + DSP primitives + script engine; the synth registers its nodes onto this registry. |
| zpwr-clip-engine | submodule (libs/) | CLIP / note-stream engine, reuses zpwr_patch_core. |
| C++ / build | C++20, CMake ≥ 3.22 | macOS default universal binary (x86_64;arm64); Linux x86_64 / aarch64; Windows VST3 + CLAP via WebView2. |
~VERIFICATION
Validation is a CTest suite of 5 headless test executables (≈1,365 lines) registered in CMakeLists.txt, using a lightweight in-file assertion harness — no external test framework, failures print to std::cerr and the binary exits non-zero.
| CTest target | Source | Coverage |
|---|---|---|
| dsp_smoke | tests/dsp_smoke.cpp | Core DSP primitives produce finite, in-range output. |
| synth_modules | tests/synth_modules_test.cpp | The registered synth node types (largest suite, ~629 lines): compute behaviour, params, voice state. |
| poly_engine | tests/poly_engine_test.cpp | PolyEngine voice allocation, unison, poly/mono/legato/duo modes. |
| factory_presets | tests/factory_presets_test.cpp | Factory preset JSON loads and instantiates cleanly. |
| midifx_stage | tests/midifx_stage_test.cpp | MIDI / MPE input staging into the engine. |
There is also an offline benchmark (tools/synth_bench.cpp) and generator tools (tools/gen_*_bank.cpp, gen_reference.cpp, gen_wavetables.cpp, gen_samples.cpp) that build factory banks, wavetables, sample data and the module reference from the same source the plugin runs. No dedicated CI workflow file ships in this repo; tests are run locally via ctest against the headless DSP libraries.