ENGINE=delay · src/engine/delay/delay_engine.{h,cpp} · class DelayEngine

Two delay lines (deck A → left, deck B → right) whose delay time locks to the transport tempo, stepping through musical divisions. The second engine to consume the platform transport (it reads tempo; it does not subscribe to ticks). The Reel/Slice/Drift switch picks the delay character and the routing switch picks the stereo topology.

Implementation, the file map, and dev notes live in docs/dev/delay-impl.md.

Signal path (per deck)

A single delay line over borrowed SDRAM (sub-allocated from the engine arena, sized to hold the longest division at the slowest tempo, ~6 s). Per sample, split into read_color() then write_out() (so ping-pong can cross the two taps' feedback):

  1. Fractional read s_delay samples behind the write head (Tape adds a wow/flutter LFO to the read time). When the deck is reversed (Rev pad), the read instead walks backwards over a delay-length window, with two heads half a window apart raised-cosine crossfaded so the wrap is click-free (the same seamless-wrap trick the pitch shifter uses).
  1. Colorize the feedback signal: a one-pole low-pass (the ENV tone control), then — by character — Tape soft-saturates it, or Shimmer pitch-shifts it +12.
  1. Write input + feedback·colorized (in ping-pong, the colorized feedback comes from the other deck). Tape/Shimmer soft-clip the loop so the colored feedback can't run away; Clean is bit-for-bit.
  1. Output dry·(1−mix) + pitch(wet)·mix — a crossfading two-head pitch shifter transposes the heard wet (the PITCH knob), bypassed at unity. The same shifter type does the Shimmer feedback shift. All controls are one-pole smoothed per sample (no zipper).

Tempo sync

process() reads transport->tempo() once per block and sets each tap's target delay from the selected musical division: delay_samples = sr · (60/bpm) · beats. The per-sample smoother glides the change, so turning tempo (or patching a different clock) ramps the delay time rather than clicking.

The division table (kDivBeats, ascending so SIZE up = longer): 1/16T, 1/16, 1/8T, 1/16., 1/8, 1/4T, 1/8., 1/4, 1/4., 1/2 (straight + dotted + triplet).

Control map

Delay control surface
Delay control surface - open full size download PDF

_Generated from docs/diagrams/controls/delay.json via make diagrams._

ControlParamId / configFunction
SIZESizemusical division (stepped; the ring shows the selected division)
POSPosfeedback (capped ·0.95)
SOSMixwet/dry mix
PITCHSpeedtranspose the heard wet (±1 octave, centre = unity)
ENVEnvfeedback tone — a one-pole low-pass in the loop: up = open/clean, down = darker repeats (dub)
MODFREQset_mod_speedmod-LFO rate (~0.05 .. 12 Hz) — chorus/flange/vibrato when paired with MOD_AMT
MOD_AMTModAmpmod-LFO depth — modulates the delay time 0 .. ~12 ms (all characters; Tape keeps a small floor)
Play padon_play_padFreeze (per deck): loop the buffer at unity feedback; the input still passes dry so you can play over it
Rev padon_play_pad (reverse)Reverse (per deck): read the buffer backwards over a delay-length window — repeats play in reverse
Reel/Slice/Drift switchConfigId::Mode (per deck)character: Clean / Tape / Shimmer
Route switchConfigId::Routetopology: Stereo / DoubleMono / Ping-pong

Knob meanings are fixed across characters — the mode only changes the feedback-path treatment.

Characters (ConfigId::Mode, per deck; ring tint blue / amber / violet)

Topologies (ConfigId::Route; shown on the mode L/C/R LEDs)

capabilities() = CapOwnDisplay | CapDualDeck; route() reports the topology for the route LED. The engine renders its own display (a division arc tinted by character + a play indicator: white when frozen, cyan when reversed, else green lit by the input); the platform composites the clock indicators over it.