Modal Resonator
Tier: Color | ComponentType: 41 | Params: 7
Bank of 32 decaying sinusoidal modes with 6 preset modal signatures, modeling resonant physical bodies.
Overview
ModalResonator synthesizes the resonant behavior of physical objects by summing up to 32 independently decaying sinusoidal modes. Each mode represents a natural vibration frequency of the modeled body, with its own frequency ratio, amplitude, and decay rate defined by one of six material presets. This is the additive synthesis approach to physical modeling -- rather than simulating wave propagation (as in waveguides), it directly constructs the frequency-domain response of a vibrating object.
The six presets model distinct material categories: Marimba (harmonic ratios, fast upper-mode decay), Bell (inharmonic ratios from thick-walled cylinder modes), Gong (densely packed inharmonic modes with long sustain), Drum (membrane modes with quick decay), Glass (sparse modes with very long decay), and Plate (nearly harmonic with progressive detuning). The Inharmonicity parameter stretches or compresses the frequency ratios around the fundamental, morphing between harmonic and inharmonic timbres.
The component supports both continuous excitation from input signal and triggered excitation via the excite() method. In continuous mode, the input signal's amplitude feeds energy into each mode proportional to its preset amplitude and brightness weighting. In triggered mode (used by the bridge's exciteWithAmplitude:brightness: method), all active modes receive an immediate energy impulse. The damp() method reduces all mode energies, simulating palm muting or material damping.
File Locations
| Path | |
|---|---|
| Header | Sources/FolioDSP/include/FolioDSP/Color/ModalResonator.h |
| Implementation | Sources/FolioDSP/src/Color/ModalResonator.cpp |
| Tests | Tests/FolioDSPTests/ModalResonatorTests.swift |
| Bridge | Sources/FolioDSPBridge/src/FolioDSPBridge.mm (ModalResonatorBridge) |
Parameters
| Index | Name | Description | Min | Max | Default Min | Default Max | Default | Unit |
|---|---|---|---|---|---|---|---|---|
| 0 | Fundamental | Base frequency for mode 0 | 20 | 8000 | 60 | 2000 | 440 | Hz |
| 1 | Brightness | High-mode amplitude rolloff (1.0 = no rolloff) | 0.0 | 1.0 | 0.0 | 1.0 | 1.0 | |
| 2 | Inharmonicity | Stretch/compress of frequency ratios relative to fundamental | 0.0 | 2.0 | 0.0 | 1.0 | 1.0 | |
| 3 | Decay Scale | Multiplier applied to all mode decay times | 0.1 | 10.0 | 0.5 | 2.0 | 1.0 | |
| 4 | Preset | Modal signature (0=Marimba, 1=Bell, 2=Gong, 3=Drum, 4=Glass, 5=Plate) | 0 | 5 | 0 | 5 | 0 | |
| 5 | Active Modes | Number of modes to synthesize (1--32) | 1 | 32 | 1 | 32 | 12 | |
| 6 | Mix | Dry/wet blend | 0 | 100 | 0 | 100 | 50 | % |
Processing Algorithm
The process() function executes these steps for each input sample:
1. Mode Synthesis
For each active mode \(i\) (where \(0 \leq i < N_{\text{active}}\)), the output is the sum of decaying sinusoids:
where \(f_0\) is the fundamental, \(r_i\) is the preset's frequency ratio for mode \(i\), and \(I\) is the inharmonicity parameter. When \(I = 1\), the preset's ratios are used directly. When \(I = 0\), all modes collapse to the fundamental. When \(I = 2\), the ratios are stretched to twice their deviation from unity.
2. Brightness Rolloff
Each mode's amplitude is scaled by a linear rolloff factor:
where \(B\) is the brightness parameter and \(a_i\) is the preset amplitude. At \(B = 1.0\), all modes retain their preset amplitudes. At \(B = 0\), upper modes are progressively attenuated to zero.
3. Output Summation
The division by \(N_{\text{active}}\) prevents amplitude buildup when many modes are active.
4. Phase Advance and Energy Decay
Per mode per sample:
where \(\tau_i\) is the preset's base decay time for mode \(i\) and \(D\) is the decay scale parameter.
5. Continuous Excitation
When the input signal has non-trivial amplitude (\(|x| > 0.001\)), energy is continuously fed into each active mode:
where \(b_i\) is the brightness factor for mode \(i\). Energy is clamped to a maximum of 1.0 per mode.
6. DC Blocker
7. Wet/Dry Mix
where \(m = \text{mix} / 100\).
Core Equations
Snapshot Fields
| Field | Type | Range | Unit | Description |
|---|---|---|---|---|
| Fundamental | Float | 20--8000 | Hz | Current fundamental frequency (smoothed) |
| Brightness | Float | 0--1 | Current brightness value (smoothed) | |
| Inharmonicity | Float | 0--2 | Current inharmonicity value (smoothed) | |
| Active Modes | Uint8 | 1--32 | Number of active modes | |
| Preset | Uint8 | 0--5 | Current preset index | |
| Output Level | Float | 0--1 | Smoothed output amplitude | |
| Total Energy | Float | 0--1 | Sum of all mode energies | |
| Mode Energies | Float[32] | 0--1 | Per-mode energy levels | |
| Mode Frequencies | Float[32] | 20--20000 | Hz | Per-mode computed frequencies |
| Ringing | Bool | 0--1 | Whether total energy exceeds 0.001 |
Implementation Notes
- Preset data is stored as static
PresetDataarrays containing 32 frequency ratios, amplitudes, and decay rates per preset. - ParamSmoother is used for fundamental, brightness, inharmonicity, and mix to prevent zipper noise on rapid parameter changes.
- DC blocker uses the standard first-order highpass:
y = x - xprev + R * ywhereR = 1 - (2pi*10/sr). This is essential because modal synthesis can produce DC offset depending on phase relationships. - Energy clamping at 1.0 per mode prevents unbounded energy accumulation from continuous excitation.
- Low-energy skip: modes with energy below 0.0001 are skipped entirely, saving computation when modes have decayed to silence.
- The
excite()method resets all mode phases to 0 for a clean attack transient. - The
damp()method multiplies all mode energies by(1 - amount), allowing graduated damping from gentle to complete. - All atomic parameters use
std::memory_order_relaxedfor lock-free thread safety. - Snapshot emission is decimated to ~60 fps (every 735 samples at 44.1 kHz).
Equation Summary
y = sum(sin(phi) * energy * decay), 32 modes