A dual resonator / pluck voice (engine #1 in docs/engine-ideas.md, "Resonator / Pluck"), built on the Mutable Instruments Rings DSP (src/engine/reso/thirdparty/rings) rather than a hand-rolled Karplus-Strong loop. Each deck wraps one rings::Part (mono, polyphony 1): modal bodies, sympathetic strings, plucked strings, and an FM voice.

Implementation (PIMPL), the build system, the file map, and the bug writeups live in docs/dev/reso-impl.md.


Concept and modes

The reel/slice/drift mode switch (ConfigId::Mode, per deck - mirrors granular's int mapping: 0 = Slice, 1 = Reel, 2 = Drift) selects how the resonator is excited. Alt+PITCH (ParamId::Aux, CapAux) selects the Rings model. Two orthogonal axes.

Slice and Drift use Rings' internal exciter only - they feed silence into the resonator, not the live input. Only Reel is fed from outside. (Feeding the live input into the internal-exciter modes layered a continuous unpitched drone over every pluck; see the resolved-bug section.) MODFREQ/cycle defaults to off (engine-seeded to 0 via the param cache); a non-zero default previously free-ran the Slice arp from boot.

Control map (per deck)

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

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

KnobParamIdRings target
PITCHSpeednote (pitch) -> performance_state.note
SIZESizepatch.damping (decay/sustain)
POSPospatch.position (excitation/pickup position)
ENVEnvpatch.brightness
MOD_AMTModAmppatch.structure
SOSMixdry/wet
MODFREQModSpeedDrift density / Slice arp rate
Alt+PITCHAuxmodel: modal / sympathetic-string / string / FM / string+reverb

capabilities() = CapOwnDisplay | CapDualDeck | CapAux | CapTransport. Deck A -> left out, deck B -> right out. Output is soft-clipped.

Knob defaults (engine-seeded)

The platform seeds the SIZE/ENV/MODFREQ knob start positions from each engine's param cache (the same param()-seed mechanism as POS/MOD_AMT/Alt+PITCH), so reso carries sensible defaults without changing other engines:

Other engines are unchanged: granular and delay carry their own 1.0 SIZE / 0.3 cycle in their caches; ENV stays 0.0 (granular's documented "envelope off" at fully CCW).

The display (render) draws, per deck: a mode-coloured energy meter (Reel yellow, Slice blue, Drift purple), a white pitch dot whose ring position equals pitch_n (a live readout used as a diagnostic), a play-LED flash on each trigger, and the mode L/C/R indicators.

Model selector (Alt+PITCH). The Rings model has no dedicated hardware indicator, so while Alt is held render draws the five model options as evenly-spaced points around the ring - the selected model bright, the rest dim - in place of the pitch dot, the whole time (not just on a change). The platform pushes the held state each loop via the defaulted IEngine::set_aux_active(deck, active) hook (core.ui.cpp, before the render call): for a CapAux engine it sends Alt down && that deck's PITCH not claimed by an fx touch. reso stores it per deck (aux_held) and shows the selector accordingly; other engines ignore the no-op default. The host test asserts the selector lights while held (more points than the lone pitch dot), persists across frames, and clears on release.

Level note

Reel's external-exciter path is ~4x quieter than Rings' internal plucker, so the Reel trigger burst is boosted (gain 2.0f in the Reel branch of process) to match a Slice pluck (~0.4 peak). That 2.0f is the Reel level knob if it needs nudging.


Build / flash

make ENGINE=reso             # build (~96% SRAM_EXEC)
make engine-reso             # clean + build + DFU flash
make -C host test            # host suites incl. test-reso