Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
config.hpp
Go to the documentation of this file.
1#pragma once
2
3// Multirate FDN reverb — structural constants, tuning constants, and mode table.
4//
5// Everything the engine treats as *design* (topology, matrix size, the decay
6// law) is a hard constant here; everything it treats as *voicing* is a named
7// tuning constant with a documented default, so a change of taste is a one-line
8// data edit rather than a hunt through the DSP. The mode table is the same idea
9// one level up: a mode stamps parameter DEFAULTS, it never selects a code path,
10// so all five catalog reverbs run byte-identical DSP.
11
12#include <array>
13#include <cstddef>
14
16
17// ── Structural constants (topology, not taste) ───────────────────────────
18inline constexpr int kNumChannels = 16; // FDN lines; Hadamard needs 2^k
19inline constexpr int kMaxDiffusionStages = 8; // per cascade, pre and in-loop
20inline constexpr int kNumEqBands = 10; // 1 low shelf + 8 peaks + 1 high shelf
21inline constexpr int kNumShimmerGrains = 4; // grains per channel pitch shifter
22inline constexpr int kNumEnsembleLfos = 6; // output ensemble chorus
23
24// Per-line prime offsets. Added (never multiplied) to the base lengths so the
25// lines stay pairwise distinct even when the requested range is narrow — see
26// the delay-length note in fdn/tank.hpp.
27inline constexpr std::array<int, kNumChannels> kChannelPrimes = {
28 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
29
30// ── The eight tank rates ─────────────────────────────────────────────────
31// Discrete by design: a continuously variable tank rate would pitch-shift the
32// running tail and explode the resampler configuration space. Eight pinned
33// rates keep switches well defined and renders reproducible.
34inline constexpr std::array<double, 8> kTankRates = {
35 16000.0, 20000.0, 24000.0, 32000.0, 44100.0, 48000.0, 64000.0, 96000.0};
36inline constexpr int kNumTankRates = static_cast<int>(kTankRates.size());
37inline constexpr double kMaxTankRate = 96000.0;
38
39// Rate-step makeup gain, in dB, applied to the wet signal after everything.
40// Derived by the procedure in the acceptance suite (broadband noise, hall
41// defaults, 48 kHz host, steady-state wet RMS per rate against the `direct`
42// no-resample reference, inverted); `fdn_reverb_rate_makeup` re-derives and
43// re-verifies these, so an engine change that shifts a rate's level fails a
44// test instead of silently re-voicing the mode.
45inline constexpr std::array<double, 8> kRateMakeupDb = {
46 2.01, 1.42, 0.92, 0.35, 0.06, 0.00, 0.21, 0.26};
47
48// ── Tuning constants ─────────────────────────────────────────────────────
49// Delay lengths, in SECONDS, at size 0.5 — converted at the tank rate, so a
50// tank-rate change alters texture, never the room.
51inline constexpr double kDelayMinSeconds = 0.010;
52inline constexpr double kDelayMaxSeconds = 0.090;
53// `size` scales both ends over this span (size 0 → 0.5x, size 1 → 1.667x),
54// keeping the realized lengths inside the 5–150 ms design envelope.
55inline constexpr double kSizeScaleMin = 0.5;
56inline constexpr double kSizeScaleMax = 5.0 / 3.0;
57// Clusters lengths toward the short end (early density) while keeping a few
58// long lines (low mode spacing). 1 = linear spread, 3 = heavily clustered.
59inline constexpr double kDelayExponent = 2.0;
60
61// Diffusion. The loop wants LESS diffusion than the input: a high coefficient
62// re-applied every pass turns metallic.
63inline constexpr int kPreDiffusionStages = 4;
64inline constexpr int kLoopDiffusionStages = 4;
65inline constexpr double kPreDiffusionG = 0.70;
66inline constexpr double kLoopDiffusionG = 0.45;
67inline constexpr double kPreDiffusionBaseMs = 12.0;
68inline constexpr double kLoopDiffusionBaseMs = 7.0;
69inline constexpr double kDiffusionStageShrink = 0.70710678118654752; // 1/sqrt(2)
70// Flutter modulates the in-loop allpass delays. Off by default: the integer
71// path is bit-exact and cheaper, and the wobble is a deliberate effect.
72inline constexpr double kFlutterDepth = 0.02; // fraction of the stage delay
73inline constexpr double kFlutterRateMinHz = 0.13;
74inline constexpr double kFlutterRateMaxHz = 0.71;
75
76// Delay modulation (dual-source: sine + mean-reverting walk).
77inline constexpr double kModSineMix = 0.6;
78inline constexpr double kModWalkMix = 0.4;
79inline constexpr double kModRateMinHz = 0.10;
80inline constexpr double kModRateMaxHz = 1.50;
81// Peak excursion as a fraction of the line length at mod = 1 and short decay.
82inline constexpr double kModMaxFraction = 0.0035;
83// Modulation depth SHRINKS as decay lengthens: constant-depth modulation on a
84// very long tail accumulates audible pitch drift, while short tails want the
85// lushness. Full depth at or below kModShrinkFullSeconds, kModShrinkFloor of it
86// at or above kModShrinkMinSeconds.
87inline constexpr double kModShrinkFullSeconds = 2.0;
88inline constexpr double kModShrinkMinSeconds = 20.0;
89inline constexpr double kModShrinkFloor = 0.25;
90// Mean-reverting (Ornstein-Uhlenbeck-style) walk: how fast it returns to zero
91// and how far it strays. Stepped at the control rate.
92inline constexpr double kWalkReversion = 0.004;
93inline constexpr double kWalkStep = 0.09;
94
95// Damping. kDampingScale blends textbook-correct Jot-proportional damping
96// (1.0) against uniform damping (0.0) — uniform is "wrong" but sometimes
97// sounds better on small bright rooms, so the choice is exposed as data.
98inline constexpr double kDampingScale = 1.0;
99inline constexpr double kDampHiMinHz = 1200.0; // damp_hi = 1 → in-loop LP here
100// damp_hi = 0 must be TRANSPARENT, not merely gentle: a one-pole whose cutoff
101// still sits below a few times Nyquist leaves a fraction of a dB of loss per
102// pass, and a fraction of a dB per pass is several percent of the realized T60
103// at long decays. 192 kHz is two octaves above the highest tank Nyquist, which
104// drives the pole coefficient to ~1e-5 at 96 kHz and to nothing below it.
105inline constexpr double kDampHiMaxHz = 192000.0;
106inline constexpr double kDampLoMinHz = 10.0; // damp_lo = 0 → DC blocker only
107inline constexpr double kDampLoMaxHz = 500.0; // damp_lo = 1 → in-loop HP here
108// Skews both damping knobs so the audible action lands in the upper half
109// rather than being squashed against the transparent end of the range.
110inline constexpr double kDampCurve = 1.6;
111
112// Flux — the wandering in-loop peak. It is ABSORPTIVE ONLY (gain in
113// [-kFluxMaxDb, 0]; see fdn/loop_eq.hpp), so the motion can never make a pass
114// louder than flat and the Jot decay law stays exact at every decay setting.
115inline constexpr double kFluxMaxDb = 3.0;
116// Default depth. Every dB of in-loop absorption is a real per-pass loss that
117// shortens the broadband decay, so the default is set from the acceptance
118// suite's T60 budget: deep enough to hear the colour move, shallow enough that
119// the realized T60 stays inside the Jot law's stated tolerance.
120inline constexpr double kFluxDefaultDb = 0.5;
121inline constexpr double kFluxBaseMinHz = 300.0;
122inline constexpr double kFluxBaseMaxHz = 6000.0;
123inline constexpr double kFluxSpreadOctaves = 0.7; // re-target range around base
124inline constexpr double kFluxIntervalMinMs = 120.0;
125inline constexpr double kFluxIntervalMaxMs = 250.0;
126inline constexpr double kFluxGlideMs = 90.0;
127// A wide dip takes a broadband bite out of every pass, and a broadband bite is
128// a decay change. This Q keeps the absorption narrow enough that the realized
129// T60 stays within the Jot law's tolerance while the colour still moves.
130inline constexpr double kFluxQ = 8.0;
131
132// Shimmer. Grain size varies per channel across this span so the 16 shifters'
133// grain-boundary artifacts decorrelate instead of stacking.
134inline constexpr double kShimmerGrainMinMs = 40.0;
135inline constexpr double kShimmerGrainMaxMs = 60.0;
136inline constexpr double kShimmerToneHz = 8000.0;
137// Injection weight ceiling before the 1/sqrt(active_n) energy normalization.
138inline constexpr double kShimmerMaxWeight = 0.6;
139
140// Stability. The realized per-pass gain is provably <= kGainCeil for every
141// parameter combination; Bloom lifts toward that ceiling but never past it.
142inline constexpr double kGainCeil = 0.999;
143// Bloom's authority ramps in over this decay span: at short decays Bloom only
144// fattens the tail, at long decays it reaches the ceiling (the freeze regime).
145inline constexpr double kBloomAuthorityMinSeconds = 1.0;
146inline constexpr double kBloomAuthorityMaxSeconds = 20.0;
147
148// Transient ducking of the INPUT (attacks excite the reverb less).
149inline constexpr double kDuckFastMs = 3.0;
150inline constexpr double kDuckSlowMs = 40.0;
151inline constexpr double kDuckAmount = 0.3;
152inline constexpr double kDuckKnee = 1.5; // transient ratio that saturates the duck
153
154// Output stage.
155inline constexpr double kEnsembleBaseMs = 5.0;
156inline constexpr double kEnsembleDepthMs = 1.6;
157inline constexpr double kEnsembleRateMinHz = 0.11;
158inline constexpr double kEnsembleRateMaxHz = 0.83;
159inline constexpr double kEnsembleWetMix = 0.35;
160// The loop saturator is deliberately contractive. Restore a modest amount of
161// its perceived level at the output tap, outside the recursion, so the drive
162// control changes colour rather than reading mainly as a level loss.
163inline constexpr double kDriveMakeupDb = 3.0;
164inline constexpr double kWetLimiterHeadroom = 2.0; // soft-tanh knee (~ +6 dB)
165
166// Resampling / anti-alias.
167// Butterworth 4-pole Q pair, cut at 0.9 x Nyquist of the bottleneck rate.
168inline constexpr double kButterworthQ0 = 0.5412;
169inline constexpr double kButterworthQ1 = 1.3065;
170inline constexpr double kAaCutoffFraction = 0.45; // 0.9 x Nyquist
171inline constexpr int kHermiteGuard = 8;
172// Below this tank Nyquist the in-tank input lowpass engages — the deliberate
173// vintage-hardware darkening at the low rates.
174inline constexpr double kTankInputLpThresholdHz = 18000.0;
175inline constexpr double kTankInputLpFraction = 0.42;
176
177// Sanitization (defense in depth). A 16-line recursive structure needs more
178// than one guard at the output: a single corrupted channel is invisible at the
179// sum until it has already poisoned every line through the matrix. The matrix
180// boundary gets the narrower overflow clamp; the delay-line write keeps the
181// wider emergency ceiling.
182inline constexpr double kPostHadamardCeil = 4.0;
183inline constexpr double kSanityCeil = 10.0;
184
185// Parameter smoothing and control-rate cadence.
186inline constexpr double kSmoothingMs = 5.0;
187inline constexpr int kControlRateSamples = 32;
188// The block kernels never take more than one control period at a time:
189// controls are constant inside a period, so a period is the natural unit.
191// Block kernels run their recursions bare and snap / sanitize the state once
192// every this many samples instead of at every multiply-add. The interval is
193// counted in each stage's own samples from its last reset, never in caller
194// blocks, so the cleanup lands on the same samples however a render is split.
195inline constexpr int kHealIntervalSamples = 32;
196// Offset step between rings that advance in lockstep (see FracDelayT::prepare):
197// an odd number of 64-byte lines, so no two rings share a cache set or low
198// address bits at their write positions.
199inline constexpr std::size_t kRingStaggerSamples = 37 * 16;
200// Quadrature oscillators re-derive their phase exactly after this many
201// rotation steps, bounding accumulated rounding far below anything audible.
202inline constexpr int kOscReanchorSamples = 4096;
203
204// ── Engine tiers ─────────────────────────────────────────────────────────
205// Which engine an FdnReverbT runs. `network` is the multirate sixteen-line
206// FDN this file describes. `classic` is ClassicReverbT: parallel combs into
207// series allpasses at the host rate, driven by the same parameters, for when a
208// conventional reverb at a fraction of the cost is the right trade.
209enum class Tier { network = 0, classic };
210
211// ── Parameters ───────────────────────────────────────────────────────────
212// Index into the engine's parameter array. The catalog node maps its baked
213// param ids onto these, so the DSP never learns about host param ids.
229inline constexpr int kNumParams = static_cast<int>(Param::count);
230
231struct ParamSpec {
232 const char* id;
233 double min;
234 double max;
237};
238
239// The canonical parameter contract. Forge's registry mirrors the ranges; the
240// contract test asserts the two agree, so this stays the single source.
241inline constexpr std::array<ParamSpec, kNumParams> kParamSpecs = {{
242 {"decay", 0.1, 60.0, 2.5, false},
243 {"size", 0.0, 1.0, 0.5, false},
244 {"predelay", 0.0, 250.0, 10.0, false},
245 {"damp_hi", 0.0, 1.0, 0.4, false},
246 {"damp_lo", 0.0, 1.0, 0.1, false},
247 {"diffusion", 0.0, 1.0, 0.7, false},
248 {"mod", 0.0, 1.0, 0.35, false},
249 {"shimmer", 0.0, 1.0, 0.0, false},
250 {"drive", 0.0, 1.0, 0.0, false},
251 {"bloom", 0.0, 1.0, 0.0, false},
252 {"width", 0.0, 1.0, 1.0, false},
253 {"tank_rate", 0.0, 7.0, 1.0, true},
254}};
255
256// ── Modes ────────────────────────────────────────────────────────────────
257// A mode is a parameter-stamping table, never a code branch: every mode runs
258// the same DSP and differs only in the defaults it stamps and the output
259// lowpass it asks for.
260enum class Mode { room = 0, hall, galaxy, shimmer, lofi, count };
261inline constexpr int kNumModes = static_cast<int>(Mode::count);
262
264 const char* name;
265 double decay;
266 double size;
268 double diffusion;
269 double mod;
270 double shimmer;
271 double bloom;
272 double damp_hi;
273 double damp_lo;
274 double drive;
275 double output_lp_hz; // per-mode wet lowpass, at the HOST rate
276};
277
278inline constexpr std::array<ModeConfig, kNumModes> kModeConfigs = {{
279 // name decay size rate diff mod shim bloom dhi dlo drive outLP
280 {"room", 0.8, 0.35, 5, 0.75, 0.30, 0.00, 0.00, 0.50, 0.15, 0.00, 16000.0},
281 {"hall", 2.8, 0.65, 4, 0.70, 0.35, 0.00, 0.20, 0.40, 0.10, 0.00, 14000.0},
282 {"galaxy", 12.0, 0.90, 3, 0.60, 0.50, 0.15, 0.70, 0.25, 0.05, 0.10, 12000.0},
283 {"shimmer", 6.0, 0.70, 4, 0.65, 0.40, 0.60, 0.40, 0.30, 0.10, 0.00, 15000.0},
284 {"lofi", 1.6, 0.45, 0, 0.55, 0.25, 0.00, 0.00, 0.70, 0.20, 0.35, 7000.0},
285}};
286
287inline constexpr const ModeConfig& mode_config(Mode m) {
288 return kModeConfigs[static_cast<std::size_t>(m)];
289}
290
291// The value a mode stamps into one parameter, or that parameter's own default
292// when the mode does not speak to it (predelay and width are performance
293// choices, not part of a mode's voicing). This is the single mapping from the
294// mode table onto the parameter list: the engine's set_mode() and the catalog
295// node's declared defaults both read it, so a re-voiced mode can never mean one
296// thing to the DSP and another to the host's parameter defaults.
297inline constexpr double mode_default(Mode mode, Param param) {
298 const ModeConfig& m = mode_config(mode);
299 switch (param) {
300 case Param::decay: return m.decay;
301 case Param::size: return m.size;
302 case Param::tank_rate: return static_cast<double>(m.tank_rate_index);
303 case Param::diffusion: return m.diffusion;
304 case Param::mod: return m.mod;
305 case Param::shimmer: return m.shimmer;
306 case Param::bloom: return m.bloom;
307 case Param::damp_hi: return m.damp_hi;
308 case Param::damp_lo: return m.damp_lo;
309 case Param::drive: return m.drive;
310 case Param::predelay:
311 case Param::width: break;
312 case Param::count: return 0.0; // not a parameter; never indexes the table
313 }
314 return kParamSpecs[static_cast<std::size_t>(param)].default_value;
315}
316
317} // namespace pulp::signal::fdn
Definition config.hpp:15
constexpr double mode_default(Mode mode, Param param)
Definition config.hpp:297
constexpr double kDuckFastMs
Definition config.hpp:149
constexpr double kSmoothingMs
Definition config.hpp:186
constexpr int kControlRateSamples
Definition config.hpp:187
constexpr double kDampCurve
Definition config.hpp:110
constexpr double kFluxDefaultDb
Definition config.hpp:120
constexpr const ModeConfig & mode_config(Mode m)
Definition config.hpp:287
constexpr double kWetLimiterHeadroom
Definition config.hpp:164
constexpr double kEnsembleRateMaxHz
Definition config.hpp:158
constexpr int kHealIntervalSamples
Definition config.hpp:195
constexpr std::array< ParamSpec, kNumParams > kParamSpecs
Definition config.hpp:241
constexpr double kMaxTankRate
Definition config.hpp:37
constexpr double kDampingScale
Definition config.hpp:98
constexpr double kDuckKnee
Definition config.hpp:152
constexpr double kWalkStep
Definition config.hpp:93
constexpr double kFluxBaseMinHz
Definition config.hpp:121
constexpr double kDelayMinSeconds
Definition config.hpp:51
constexpr std::array< double, 8 > kRateMakeupDb
Definition config.hpp:45
constexpr double kGainCeil
Definition config.hpp:142
constexpr int kNumModes
Definition config.hpp:261
constexpr double kFlutterDepth
Definition config.hpp:72
constexpr double kEnsembleRateMinHz
Definition config.hpp:157
constexpr double kSizeScaleMax
Definition config.hpp:56
constexpr double kFluxQ
Definition config.hpp:130
constexpr int kMaxDiffusionStages
Definition config.hpp:19
constexpr double kWalkReversion
Definition config.hpp:92
constexpr double kDiffusionStageShrink
Definition config.hpp:69
constexpr int kNumEqBands
Definition config.hpp:20
constexpr double kModRateMinHz
Definition config.hpp:79
constexpr double kSanityCeil
Definition config.hpp:183
constexpr double kFluxBaseMaxHz
Definition config.hpp:122
constexpr double kBloomAuthorityMaxSeconds
Definition config.hpp:146
constexpr double kModWalkMix
Definition config.hpp:78
constexpr int kNumChannels
Definition config.hpp:18
constexpr int kNumEnsembleLfos
Definition config.hpp:22
constexpr int kPreDiffusionStages
Definition config.hpp:63
constexpr double kButterworthQ1
Definition config.hpp:169
constexpr double kLoopDiffusionBaseMs
Definition config.hpp:68
constexpr double kDampHiMinHz
Definition config.hpp:99
constexpr int kLoopDiffusionStages
Definition config.hpp:64
constexpr double kEnsembleWetMix
Definition config.hpp:159
Param
Definition config.hpp:214
@ bloom
Definition config.hpp:224
@ drive
Definition config.hpp:223
@ damp_lo
Definition config.hpp:219
@ decay
Definition config.hpp:215
@ shimmer
Definition config.hpp:222
@ predelay
Definition config.hpp:217
@ damp_hi
Definition config.hpp:218
@ diffusion
Definition config.hpp:220
@ mod
Definition config.hpp:221
@ count
Definition config.hpp:227
@ width
Definition config.hpp:225
@ size
Definition config.hpp:216
@ tank_rate
Definition config.hpp:226
constexpr int kNumShimmerGrains
Definition config.hpp:21
constexpr double kEnsembleBaseMs
Definition config.hpp:155
Tier
Definition config.hpp:209
@ network
Definition config.hpp:209
@ classic
Definition config.hpp:209
constexpr double kShimmerGrainMaxMs
Definition config.hpp:135
constexpr double kModRateMaxHz
Definition config.hpp:80
constexpr std::array< int, kNumChannels > kChannelPrimes
Definition config.hpp:27
constexpr double kAaCutoffFraction
Definition config.hpp:170
constexpr double kDuckAmount
Definition config.hpp:151
constexpr double kFlutterRateMinHz
Definition config.hpp:73
constexpr double kShimmerMaxWeight
Definition config.hpp:138
constexpr double kFluxIntervalMinMs
Definition config.hpp:124
constexpr double kFlutterRateMaxHz
Definition config.hpp:74
constexpr int kOscReanchorSamples
Definition config.hpp:202
constexpr double kLoopDiffusionG
Definition config.hpp:66
constexpr double kDampLoMinHz
Definition config.hpp:106
constexpr double kModSineMix
Definition config.hpp:77
constexpr double kSizeScaleMin
Definition config.hpp:55
constexpr std::array< double, 8 > kTankRates
Definition config.hpp:34
constexpr int kNumParams
Definition config.hpp:229
constexpr double kShimmerGrainMinMs
Definition config.hpp:134
constexpr std::size_t kRingStaggerSamples
Definition config.hpp:199
constexpr double kDelayMaxSeconds
Definition config.hpp:52
constexpr int kHermiteGuard
Definition config.hpp:171
constexpr double kModShrinkMinSeconds
Definition config.hpp:88
Mode
Definition config.hpp:260
@ hall
Definition config.hpp:260
@ lofi
Definition config.hpp:260
@ galaxy
Definition config.hpp:260
@ count
Definition config.hpp:260
@ room
Definition config.hpp:260
constexpr double kFluxSpreadOctaves
Definition config.hpp:123
constexpr int kMaxBlockSamples
Definition config.hpp:190
constexpr double kTankInputLpThresholdHz
Definition config.hpp:174
constexpr double kPreDiffusionBaseMs
Definition config.hpp:67
constexpr double kFluxGlideMs
Definition config.hpp:126
constexpr int kNumTankRates
Definition config.hpp:36
constexpr double kTankInputLpFraction
Definition config.hpp:175
constexpr double kModShrinkFloor
Definition config.hpp:89
constexpr double kPreDiffusionG
Definition config.hpp:65
constexpr double kEnsembleDepthMs
Definition config.hpp:156
constexpr double kButterworthQ0
Definition config.hpp:168
constexpr std::array< ModeConfig, kNumModes > kModeConfigs
Definition config.hpp:278
constexpr double kBloomAuthorityMinSeconds
Definition config.hpp:145
constexpr double kFluxMaxDb
Definition config.hpp:115
constexpr double kDuckSlowMs
Definition config.hpp:150
constexpr double kModShrinkFullSeconds
Definition config.hpp:87
constexpr double kDriveMakeupDb
Definition config.hpp:163
constexpr double kFluxIntervalMaxMs
Definition config.hpp:125
constexpr double kDampLoMaxHz
Definition config.hpp:107
constexpr double kPostHadamardCeil
Definition config.hpp:182
constexpr double kDelayExponent
Definition config.hpp:59
constexpr double kShimmerToneHz
Definition config.hpp:136
constexpr double kDampHiMaxHz
Definition config.hpp:105
constexpr double kModMaxFraction
Definition config.hpp:82
Definition config.hpp:263
double bloom
Definition config.hpp:271
double drive
Definition config.hpp:274
int tank_rate_index
Definition config.hpp:267
double diffusion
Definition config.hpp:268
double shimmer
Definition config.hpp:270
double damp_hi
Definition config.hpp:272
double output_lp_hz
Definition config.hpp:275
double decay
Definition config.hpp:265
const char * name
Definition config.hpp:264
double mod
Definition config.hpp:269
double damp_lo
Definition config.hpp:273
double size
Definition config.hpp:266
Definition config.hpp:231
double max
Definition config.hpp:234
const char * id
Definition config.hpp:232
double default_value
Definition config.hpp:235
bool stepped
Definition config.hpp:236
double min
Definition config.hpp:233