Shared DSP primitives
This document answers which processors the shared engine provides, where their numeric contracts come from, and what bounds, latency and evidence apply. The same Rust kernels serve
This document answers which processors the shared engine provides, where their numeric contracts come from, and what bounds, latency and evidence apply. The same Rust kernels serve browser/WASM, the native Player and independent exports.
Status: every processor below has an executable shared Rust kernel with native/WASM parity fixtures. Pre-1.0 contracts change in place under the policy in AGENTS.md; this is not a compatibility promise for earlier development builds. Benchmarks are local software evidence, not device or DAW certification.
Processor catalog#
| Node | Title | Role | Documented in |
|---|---|---|---|
std.nam | Neural amp | Bounded WaveNet A1/A2 inference, prepared conversion and size switching | NAM architecture |
std.gain | Gain | Foundation | this page |
std.lowpass | Low-pass filter | Foundation, one-pole | this page |
std.delay | Delay | Foundation; Clean/Filtered/Tape/Ducked modes | below |
std.crossfade | Crossfade | Foundation | this page |
std.oscillator | Oscillator | Multiwave source | below |
std.svf | Resonant filter | State-variable filter | below |
std.eq | Parametric EQ | 1–32 durable RBJ biquad bands with per-band bypass | below, Parametric EQ |
std.noise | Noise | Thirteen streaming colors/textures | noise |
std.drive | Drive | Saturation with oversampling | below |
std.compressor | Compressor | Dynamics | below |
std.limiter | Limiter | Dynamics | below |
std.chorus | Chorus | Modulation | below |
std.reverb | Reverb | Feedback-delay network | below |
std.envelope | Envelope | Gate-controlled gain stage | below, playable inspection |
std.poly | Polyphonic instrument | Eight-voice note receiver, played from a midi cable | performance and reuse |
std.wavetable | Wavetable | 1–8 frame table source with MIDI pitch | MIDI, control and mixing |
std.math | Math | Utility; 17 ops on audio or control lanes | Math, below |
std.compare | Compare | Utility; gate or trigger with hysteresis | Compare, below |
ctl.* | Control nodes | Block-rate lanes and parameter sockets | control lanes |
midi.* | MIDI nodes | Sample-offset event processors and host ports | MIDI cables |
std.mixer | Mixer | Up to 32 durable channel strips with gain, pan, mute, solo and master | MIDI, control and mixing |
std.combine | Combine channels | Lane concatenation | multichannel audio |
std.select | Select channels | Lane extraction | multichannel audio |
std.matrix | Channel matrix | Explicit routes | multichannel audio |
Additional shared processors delivered in v0.0.12 and v0.0.17:
| Nodes | Role | Contract and evidence |
|---|---|---|
std.tuner | Pass-through audio with click-free mute; the shared face detects pitch and reads Toy or node tuning | Tuner; runtime-web tuner and tuning fixtures |
std.sampler, std.player | Tuned MIDI sample voices and transport-aware audio-file playback | Sampler, Player; sample-asset conformance and runtime-web PCM fixtures |
std.gate, std.transient, std.crossover, std.multiband | Keyed dynamics, transient shaping, band splitting and multiband compression; Compressor also accepts a sidechain key | Gate; runtime-web dynamics parity/latency/partition fixtures |
std.pitch, std.harmonizer, std.autotune | Pitch shifting, three-part harmony and pitch correction using Toy tuning or MIDI targets | Pitch correction; runtime-web pitch fixtures |
std.vocoder, std.denoise, std.resonance, std.suppressor | Carrier/key vocoding, noise reduction, spectral resonance reduction and feedback notches | Vocoder; shared FFT and runtime-web spectral fixtures |
std.convolver | Asset-backed partitioned FFT convolution, trims, predelay, gain and aligned dry/wet mix | Convolver; allocation/tail/latency tests and native/WASM parity at every partition size |
std.phaser, std.crush, std.granulator | Allpass modulation, bit/sample-rate reduction and live-buffer granulation | Granulator; runtime-web character fixtures |
All processors operate on 1–32-channel bundles, subject to node-specific lane rules such as Crossover's band-major output. The foundation nodes keep version 0.1.0 fixed-stereo contracts and gained automatic widths in 0.2.0. std.oscillator is the single Oscillator node: sine tones, other waveforms, morphing and optional MIDI pitch share one prepared kernel and one waveform face.
Asset-backed nodes share ABI 3 tt_attach_asset: WAV parsing, resampling and storage admission happen before activation, away from rendering. Portable Toys carry declarations and hashes; hosts supply verified bytes. Convolution uses the shared FFT and a prepared partition of 64–1024 samples, which also determines its declared latency. Graph compensation aligns parallel paths, and the attached IR determines the convolver's tail. Lossy/FLAC source decoding, time stretching, formant shifting and asset-backed granulation remain outside this slice. The cross-sprint verification record records host evidence and remaining listening/DAW/CPU qualification.
Signal ports#
Math and Compare declare signal ports: audio or control, resolved per instance before lane solving. A connected signal input takes its source's type, an unconnected one is control, and the output is audio when any input is audio, else control. A MIDI, video or data source is a PORT_TYPE rejection, and definition boundaries cannot declare signal. resolveSignalPorts in packages/node-catalog/src/channels.ts and resolve_signals in crates/compiler/src/channels.rs apply the same rule, and the result lands in Operator.output. The engine builds a sample kernel (primitives/math.rs, primitives/compare.rs) for audio output and a block kernel (control/) for control output. Both call the op tables in crates/engine/src/ops.rs. Operands of the other rate are read lane by lane with broadcast. See value nodes for the shared op vocabulary and the data counterparts, and sprint v0.0.7 §01.
Contracts and presentation#
The node manifest is the single numeric/presentation source for every admitted primitive: ports with their signal type and width, execution domain, derived host requirements, parameter domains, smoothing, discrete steps, choices and visibility. tools/generate-node-manifest.mjs generates the TypeScript catalog and the Rust contracts (domains, port lists, smoothing, discrete admission and named slot indices); root npm run generate:check rejects stale output. The version 0.3.0 noise contract remains hand-maintained in noise.ts and the compiler.
| Bound | Value |
|---|---|
| Operator control vector | Exactly the admitted domain length; per-operator prepared size, at most 288 slots |
| Public controls per definition | 512 |
| Controls per expanded program | 4,096 |
| Discrete choices/counts | Integer linear ranges, no LFO, zero DSP smoothing, identical rounding at browser, MIDI and native automation boundaries |
Public and nested aliases inherit these semantics. Patch, Properties and insertion previews use shared choices and conditional controls; authored Frame controls stay explicit. The shared graphical filter/envelope widgets bind every response-affecting control to the same processor, including Type, slope/compensation or curve/loop/retrigger/hold. Their analytic pictures are verified against actual PCM; they are not a second audio engine.
Implemented processing#
| Node | Behavior |
|---|---|
| Oscillator | Morph, Sine, Triangle, Saw, Pulse, Double saw, Organ and Harmonic bell. Morph evaluates only its two required neighbors; a named waveform evaluates its selected kernel. Saw/pulse use polynomial step correction; triangle uses its integrated corner correction, with no leaky integrator history. Harmonic shapes taper upper partials near Nyquist while retaining the fundamental. Phase offset, cents detune and a held/retriggering Gate share one node oscillator across identically controlled output lanes. This is not implicit unison or voice allocation. |
| Resonant filter | A trapezoidal state-variable core supplies continuous LP/BP/HP Morph, named low-pass/band-pass/high-pass/notch/all-pass, and one or two cascaded stages. Q 0.5–12, cutoff capped at 0.45 × rate. Optional slope-aware resonance compensation applies to resonant LP/HP/Morph responses. All-pass changes phase with flat magnitude. Each audio lane owns its integrators. |
| Parametric EQ | 1–32 item bands in canonical ID order, then an Output trim. Each band is an RBJ Audio-EQ-Cookbook biquad in transposed direct form II: 12 dB/oct low/high cut with Q as corner resonance, low/high shelf with Q as slope, bell, or notch, at 20 Hz–20 kHz capped at 0.49 × rate. Coefficients recompute only when a band's values change, on the shared 16-sample cadence. An off band is skipped, so a fully off EQ is bit-exact; a band switched on starts from rest, and band on/type changes pass through the bounded continuity correction. Each audio lane owns its band states. The node face draws the same coefficients (face/eqMath.ts), parity-tested against rendered PCM. |
| Drive | Cubic soft clip, hard clip, asymmetric saturation and wavefolding. Drive, Bias, Tone, Mix and Output; a DC blocker removes persistent offsets and the shaper's zero-input offset is subtracted. The soft curve is monotonic with a continuous first derivative at its saturation endpoints; it avoids audio-rate transcendental calls. Quality selects 1×, 2× or 4× processing through prepared 17-tap half-band FIR stages. Polyphase/symmetric evaluation skips exact zero taps, zero-stuffed multiplies and discarded decimator outputs. Every quality has 12 samples of processing latency, with dry and wet paths aligned. Filter/DC transients can exceed the static shaper peak; there is no alias-producing post-filter hard clamp. Oversampling reduces residual aliasing rather than guaranteeing its absence. |
| Delay | Clean, Filtered, Tape and Ducked echoes; signed feedback, wet ducking, musical divisions or milliseconds, and 10 ms read-head transitions. Tempo comes from the shared transport. Time is bounded by an eight-second prepared ceiling; longer beat divisions cap there. Independent feedback, a ring over all N channels, or explicit sparse routes in the node's Channels inspector section. Empty custom routes produce no feedback. Matrix row absolute gain sums must be at most one, and feedback is bounded to ±0.95. |
| Chorus | Chorus, Ensemble and wet-only Vibrato. One to eight internal taps per audio channel, continuous channel spread, depth/rate/center delay, and feedback bounded to ±0.85. Internal tap count and cable width are separate dimensions. Prepared voice angles and cached spread weights share one modulation phase across the bundle; audio histories stay independent. |
| Reverb | An eight-line feedback-delay network with Room, Hall and Plate-like delay tunings, Size, Diffusion, Modulation, damping, pre-delay and mix. A contractive Householder blend mixes the internal lines; channels retain separate networks, with no assumed stereo pairing or spatial layout. Shared delay-read positions and cached decay/damping coefficients avoid repeating that work per channel. Freeze removes excitation, suspends modulation/size movement and uses unity feedback; it reports an infinite tail while active. This is an algorithmic reverb, with no convolution assets or physical-plate claim. |
| Compressor | Peak or 10 ms mean-square/RMS detector, optional detector-only high-pass, threshold/ratio/quadratic knee, attack/release, hold, makeup and adaptive release. Linking interpolates independent lane detectors toward the strongest detector across the whole bundle. Fully linked operation shares gain-reduction calculations and applies one common gain, preserving channel balance. |
| Limiter | Immediate sample-peak attenuation, ceiling, hold, adaptive release and independent/full-bundle linking. Fully linked operation shares one attenuation state. No lookahead or intersample true-peak claim. |
| Envelope | Real AD, AR and ADSR audio gain stages, concave/linear/convex polynomial segment curves, current-level/from-zero retrigger, held loops and loop hold time. Gate changes are sample-boundary controls. One-shot AD completes after Gate release; AR holds its peak; ADSR holds Sustain. Looping repeats complete cycles while Gate remains high. Voice processing reuses the same envelope core with its own state. |
Exact ranges, defaults and help text live in the manifest. Mode changes use bounded continuity correction without rebuilding hosts or duplicating delay networks. Delay retiming has separate dual read heads. Noise keeps its own prepared equal-power transition.
Preparation, latency and resource bounds#
crates/engine/src/primitives/ separates the kernels from common bounded buffer, cache and transition helpers; the engine facade owns dispatch and accounting. Fixed FIR taps, voice angles, arrays and buffers are prepared before rendering. Coefficients for smoothed controls refresh only when changed, at a fixed 16-sample cadence shared across lanes and independent of callback partitioning. Detector, gate and envelope histories still advance sample by sample. No callback allocates, locks, performs I/O, constructs a graph or calls UI APIs.
| Bound | Value |
|---|---|
| Delay capacity | Derived from literal values, public target ranges and modulation bounds; the eight-second ceiling covers tempo sync. A short literal echo does not reserve an eight-second loop |
| Prepared storage profile | 128 MiB of signal/DSP storage per engine, including signal history, network buffers, control planes and graph alignment (replaces the earlier 16 MiB development profile so a 32-channel, 96 kHz long delay fits) |
| Oversized graphs | Rejected before allocation/activation; the last valid host generation stays active |
| Drive latency | 12 samples at every quality |
| Tail bound | Conservative −120 dB amplitude bounds following output ancestors, including feedback, damping, DC removal and serial processing; frozen reachable reverbs report infinity |
The engine propagates intrinsic processing latency through the graph and prepares per-input compensation where paths rejoin. Musical echo, chorus and reverb delays are effect behavior, not compensation latency. Owner-thread ABI queries expose tt_latency_samples and tt_prepared_storage_bytes; the native Session/Processor reports prepared latency to JUCE on prepare, load and restore. No latency-dependent allocation or host call occurs inside Rust rendering. Native rendering publishes the current tail after controls/process; actual DAW tail and latency handling still needs host qualification.
Verification and profiling#
tests/conformance/dsp-upgrade-cases.mjs is the owned shared mode corpus. Runtime fixtures verify every mode over multichannel bundles, integer source/alias boundaries, waveform phase/spectra, analytic filter response, drive DC and measured alias reduction, fixed dry latency, graph branch alignment, tempo echoes, odd-width feedback rings and custom matrices, envelope timing/curves/loops, RMS/hold dynamics and freeze tail semantics. Default/edge/native/WASM PCM and block-partition fixtures remain active. Allocation counting includes live selector changes. The linked C++ ABI consumes the same mode corpus; native Session tests cover control recall and host-facing latency. Browser regression exercises real playback and shared Frame controls.
| Command | Covers |
|---|---|
npm run check | Required repository gate, including Rust and native ABI fixtures |
npm run build:web, npm run build:player | Browser and native builds with the shared engine |
npm run test:dsp-ui -w @tunetoys/web | Browser playback and shared Frame controls |
npm run bench:dsp | Every mode at 1/2/3/8/16/32 channels and 44.1/48/96 kHz, native release and Node WASM |
The benchmark records a median of three runs after 128 warm-up blocks, preparation time, callback budget percentage, conservative storage and latency, plus hardware identity and the tested engine digest in .cache/benchmarks/dsp.json. Run it after building contracts and WASM.
Local Apple M2 Pro evidence (2,052 native/WASM measurements): the largest median was the eight-voice Ensemble at 32 channels/96 kHz in Node WASM at 193 µs per 128 frames, or 14.5% of one callback budget. Four-times Drive measured about 133 µs (10%) after polyphase FIR and cubic-shaper refinement. The largest accounted single-node profile was about 12.4 MiB for the 32-channel FDN; long public-control delay ranges can reserve more, up to the admitted engine ceiling. These are software measurements on that host.
Provenance#
Kernels, coefficients and fixtures are original TuneToys work. Sibling projects are behavior references, not build dependencies. Mathematical references: Andrew Simper's trapezoidal SVF derivation, Faust oscillator documentation for polynomial step corrections, Esqueda/Välimäki/Bilbao's BLAMP work for corner correction, JUCE's oversampling documentation for filtering/latency tradeoffs, and Julius O. Smith's feedback-delay-network treatment. No implementation or compatibility claim for another product is imported.
Implementation#
crates/engine— kernels insrc/primitives, facade, tail and alignmentcrates/compiler— admission and program buildingpackages/node-catalog— manifest, generated contracts and control metadatapackages/runtime-web/testandtests/conformance— fixtures