@maruaudio/dsp/physical/modal is the shared arithmetic of a modal resonator: the coupled-form (rotation) two-pole recurrence that rings one mode as an exponentially decaying sinusoid, with its pole and coefficient mapping, displacement and velocity taps, the per-mode amplitude roll-off, and the exciter low-pass and Hann window a struck model uses. It is the single copy of this math; @maru/physical-modal-resonator, @maru/physical-modal-resonator-bank and the gamelan, gong and modal-struck voices all call it.
const r = modalPoleRadius(decay, fs) // exp(-decay / fs)
const w = modalPoleAngle(frequency, fs) // 2π f / fs
const c = modalCoefficientC(r, w), s = modalCoefficientS(r, w)
const re2 = modalNextRe(re, im, c, s), im2 = modalNextIm(re, im, c, s)Lifecycle
None: pure stage functions. The caller owns the re and im state, adds excitation into re (the cosine axis) and reads im, the sine-phase displacement, so an impulse starts the mode from zero amplitude. Coefficient functions call exp, cos and sin; run them when a mode changes.
When to use it
Bells, bars, gongs, kalimba tines and other struck bodies whose sound is a sum of decaying modes, where low-frequency pitch and the beat rate between detuned mode pairs must be held precisely in f32.
Limits
- Every state update and coefficient is rounded to f32 (
Math.fround). - The pole radius is clamped below 1 (to 0.99999994, the largest f32 below 1), so a mode cannot grow; a negative decay counts as 0. A non-positive sample rate falls back to 48 kHz.
modalModeAboveNyquistGuardreports modes at or above 0.45 × fs, which callers silence rather than let fold; the pole mapping itself is unchanged.- Roll-off is
amp · min(1, 1000 / f)above 1 kHz. The velocity tap is a one-sample difference times the rate.