# `delay` _(no submodules)_ ## Purpose `DelayPlugin` is the universal stereo delay effect. Per-channel circular delay lines render decaying echoes; bounded feedback (clamped at `0.95`) prevents the recursion from diverging regardless of automation; an optional ping-pong mode cross-feeds the two channels for that classic alternating-side bounce; and a transport-BPM sync mode derives the delay length from the live `tempo` on the chain's ambient state so a 1/4-note delay always stays tempo-locked. The plugin extends `AbstractAudioFxPlugin`, so it runs identically inside the Web Audio engine, the server renderer, and the worklet wrapper. Delay state is per-channel and cleared in place by `reset()`. With `wet === 0` the plugin short-circuits to `dry * input`, satisfying the spec's "fully-dry mix is transparent" guarantee. ## Features - Up-to-2 s per-channel delay line, sized at `setup` and never re-allocated. - Seven parameters: `delayMs`, `feedback` (0–0.95), `wet`, `dry`, `pingPong`, `bpmSync`, `syncBeats`. - Ping-pong mode (stereo only): the L delay reads R's feedback and vice versa for an alternating-side bounce; mono input silently falls back to regular delay. - BPM sync: when `bpmSync >= 0.5`, delay length = `60_000 · syncBeats / tempo` ms, with `tempo` read from `ChainContext.ambient.transport`. - `wet === 0` early-return path: output equals `dry * input` exactly, bit-transparent when `dry === 1`. - Bounded feedback so the IIR recursion cannot blow up. ## Usage: Basic Example ```ts import { DelayPlugin } from "@suaveplan/audio-fx-plugin-delay"; const delay = new DelayPlugin({ initialDelayMs: 250, initialFeedback: 0.4, initialWet: 0.3, initialDry: 0.7, }); chain.add(delay); ``` ## Usage: Advanced Example ```ts import { DelayPlugin } from "@suaveplan/audio-fx-plugin-delay"; // Tempo-locked 1/4-note ping-pong delay for stereo guitar. const delay = new DelayPlugin({ initialBpmSync: true, initialSyncBeats: 1, initialFeedback: 0.6, initialPingPong: true, initialWet: 0.5, initialDry: 0.7, }); await delay.setup({ sampleRate: 48_000, channels: 2, blockSize: 512 }, ctx); // Sweep the delay length from 1/4 → 1/8 over four bars via automation. delay.setParameterCurve("syncBeats", { type: "linear", value: 0.5, duration: 8 }); ``` ## API Reference - `DelayPlugin` (class extends `AbstractAudioFxPlugin`) - `new DelayPlugin(options?: DelayPluginOptions)` — throws `AUDIO_FX.INVALID_OPTIONS` on malformed options. - Inherited parameter accessors for `delayMs`, `feedback`, `wet`, `dry`, `pingPong`, `bpmSync`, `syncBeats`. - `DelayPluginOptions` — initial parameter values and mode flags. - `DelayPresetData` — `{ pingPong, bpmSync }`; the continuous parameters round-trip via the universal `parameters` map. ## Implementation Notes The non-ping-pong path is the standard "comb" structure: `line[w] = input + feedback · line[r]; out = dry · input + wet · line[r]`. The read index `r` is `(w + lineSize − delaySamples) % lineSize`, computed once per sample because `delaySamples` may move with automation. The line size is fixed at the maximum allowed (`2 s · sampleRate`) so changing `delayMs` between blocks costs nothing. Ping-pong cross-feeds the feedbacks: `lineL[w] = inL + feedback · lineR[r]` and symmetrically for R. The dry path stays in-channel. This is the classic Eventide-style ping-pong topology; an impulse on L produces a bounce L → R → L → R with each bounce attenuated by `feedback`. BPM sync uses `60_000 / tempo · syncBeats` to convert beats to milliseconds. The plugin reads `tempo` from `ChainContext.ambient.transport.tempo` each block, so a tempo automation lane updates the delay length continuously without per-tempo recompute on the plugin side. Feedback is clamped to `0.95` by the parameter declaration. Higher values are mathematically unstable for an IIR delay; the clamp keeps the plugin safe under any automation lane the user can write.