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#

NodeTitleRoleDocumented in
std.namNeural ampBounded WaveNet A1/A2 inference, prepared conversion and size switchingNAM architecture
std.gainGainFoundationthis page
std.lowpassLow-pass filterFoundation, one-polethis page
std.delayDelayFoundation; Clean/Filtered/Tape/Ducked modesbelow
std.crossfadeCrossfadeFoundationthis page
std.oscillatorOscillatorMultiwave sourcebelow
std.svfResonant filterState-variable filterbelow
std.eqParametric EQ1–32 durable RBJ biquad bands with per-band bypassbelow, Parametric EQ
std.noiseNoiseThirteen streaming colors/texturesnoise
std.driveDriveSaturation with oversamplingbelow
std.compressorCompressorDynamicsbelow
std.limiterLimiterDynamicsbelow
std.chorusChorusModulationbelow
std.reverbReverbFeedback-delay networkbelow
std.envelopeEnvelopeGate-controlled gain stagebelow, playable inspection
std.polyPolyphonic instrumentEight-voice note receiver, played from a midi cableperformance and reuse
std.wavetableWavetable1–8 frame table source with MIDI pitchMIDI, control and mixing
std.mathMathUtility; 17 ops on audio or control lanesMath, below
std.compareCompareUtility; gate or trigger with hysteresisCompare, below
ctl.*Control nodesBlock-rate lanes and parameter socketscontrol lanes
midi.*MIDI nodesSample-offset event processors and host portsMIDI cables
std.mixerMixerUp to 32 durable channel strips with gain, pan, mute, solo and masterMIDI, control and mixing
std.combineCombine channelsLane concatenationmultichannel audio
std.selectSelect channelsLane extractionmultichannel audio
std.matrixChannel matrixExplicit routesmultichannel audio

Additional shared processors delivered in v0.0.12 and v0.0.17:

NodesRoleContract and evidence
std.tunerPass-through audio with click-free mute; the shared face detects pitch and reads Toy or node tuningTuner; runtime-web tuner and tuning fixtures
std.sampler, std.playerTuned MIDI sample voices and transport-aware audio-file playbackSampler, Player; sample-asset conformance and runtime-web PCM fixtures
std.gate, std.transient, std.crossover, std.multibandKeyed dynamics, transient shaping, band splitting and multiband compression; Compressor also accepts a sidechain keyGate; runtime-web dynamics parity/latency/partition fixtures
std.pitch, std.harmonizer, std.autotunePitch shifting, three-part harmony and pitch correction using Toy tuning or MIDI targetsPitch correction; runtime-web pitch fixtures
std.vocoder, std.denoise, std.resonance, std.suppressorCarrier/key vocoding, noise reduction, spectral resonance reduction and feedback notchesVocoder; shared FFT and runtime-web spectral fixtures
std.convolverAsset-backed partitioned FFT convolution, trims, predelay, gain and aligned dry/wet mixConvolver; allocation/tail/latency tests and native/WASM parity at every partition size
std.phaser, std.crush, std.granulatorAllpass modulation, bit/sample-rate reduction and live-buffer granulationGranulator; 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.

BoundValue
Operator control vectorExactly the admitted domain length; per-operator prepared size, at most 288 slots
Public controls per definition512
Controls per expanded program4,096
Discrete choices/countsInteger 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#

NodeBehavior
OscillatorMorph, 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 filterA 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 EQ1–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.
DriveCubic 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.
DelayClean, 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.
ChorusChorus, 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.
ReverbAn 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.
CompressorPeak 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.
LimiterImmediate 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.
EnvelopeReal 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.

BoundValue
Delay capacityDerived 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 profile128 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 graphsRejected before allocation/activation; the last valid host generation stays active
Drive latency12 samples at every quality
Tail boundConservative −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.

CommandCovers
npm run checkRequired repository gate, including Rust and native ABI fixtures
npm run build:web, npm run build:playerBrowser and native builds with the shared engine
npm run test:dsp-ui -w @tunetoys/webBrowser playback and shared Frame controls
npm run bench:dspEvery 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#

Edit this page on GitHub

esc

↑ ↓ move↩ open⌘↩ new tabesc close