Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
fdn_reverb.hpp
Go to the documentation of this file.
1#pragma once
2
3// Multirate FDN reverb — a 16-channel feedback delay network whose tank runs at
4// a selectable internal sample rate.
5//
6// WHAT MAKES IT THIS REVERB
7// 1. The tank has its OWN sample rate, chosen from eight pinned steps. Low
8// rates are darker and grittier by design; high rates are airier. The
9// control is a musical lo-fi <-> hi-fi axis, not a quality setting.
10// 2. Stability is proven, not tuned. The loop gain is normalized each control
11// tick against a closed-form worst case over every stage that can add
12// energy, so the realized per-pass gain is provably below unity for ANY
13// parameter combination — and Bloom then lifts it toward (never to) unity
14// for near-infinite tails, with decay keeping final authority.
15// 3. Everything that adds energy in the loop is normalized: the shimmer
16// injection is energy-weighted, the saturator is 1-Lipschitz, the flux
17// peak is absorptive-only, and loudness makeup happens outside the
18// recursion.
19// 4. Motion is layered and decorrelated per channel — per-line sine plus
20// mean-reverting walk on the delays, a per-line wandering absorption peak,
21// optional diffusion flutter.
22//
23// Output is WET ONLY. Dry/wet belongs to whatever graph or host hosts this
24// block, so the block never has to guess what "dry" means for its caller.
25//
26// TIERS. set_tier(fdn::Tier::classic) swaps the network for ClassicReverbT —
27// parallel combs into series allpasses at the host rate — behind the same
28// parameters, modes, and wet-only output. The parameters that describe
29// machinery the classic tier does not have are kept but have no effect there;
30// see classic_reverb.hpp. A tier change is a clean flush, like a tank-rate
31// change: the newly active engine starts from silence at the next block.
32//
33// RT contract: prepare() allocates for the worst case (96 kHz tank at the
34// maximum block size) for both tiers. Every setter, process_block(), reset(),
35// a live tank-rate change, and a tier change allocate nothing.
36
47
48#include <algorithm>
49#include <array>
50#include <cmath>
51#include <type_traits>
52
53namespace pulp::signal {
54
55template <typename SampleType = float>
57 public:
58 // The network and the classic tier run in single precision: their block
59 // kernels are four-lane float. A double instantiation keeps the double
60 // buffer interface and converts at the block boundary.
61 using Engine = float;
63 using Mode = fdn::Mode;
64
65 // ── Lifecycle ────────────────────────────────────────────────────────
66 void prepare(double sample_rate, int max_block_size) {
67 host_rate_ = sample_rate > 0.0 ? sample_rate : 48000.0;
68 max_block_ = std::max(max_block_size, 1);
69 bridge_.prepare(host_rate_, max_block_);
70
71 // Room for a whole block to be pushed ahead of its reads.
72 const int predelay_cap =
73 static_cast<int>(0.001 *
74 fdn::kParamSpecs[static_cast<std::size_t>(Param::predelay)].max *
77 for (auto& line : predelay_) line.prepare(predelay_cap);
78 for (auto& cascade : pre_diffusion_)
81 tank_.prepare(fdn::kMaxTankRate);
82 ensemble_.prepare(fdn::kMaxTankRate);
83
84 for (int p = 0; p < fdn::kNumParams; ++p) {
85 target_[p] = fdn::kParamSpecs[static_cast<std::size_t>(p)].default_value;
86 smoothed_[p] = target_[p];
87 }
88 apply_tank_rate(rate_index());
89 classic_.prepare(host_rate_, max_block_);
90 reset();
91 }
92
93 // Stamp a mode's defaults. A mode is a parameter table, never a code
94 // branch: all five catalog reverbs run byte-identical DSP.
95 void set_mode(Mode mode) {
96 for (int p = 0; p < fdn::kNumParams; ++p) {
97 const auto param = static_cast<Param>(p);
98 if (param == Param::predelay || param == Param::width) continue;
99 set_parameter(param, fdn::mode_default(mode, param));
100 }
101 output_lp_hz_ = fdn::mode_config(mode).output_lp_hz;
102 design_output_lp();
104 }
105
106 void set_parameter(Param p, double value) {
107 const auto index = static_cast<std::size_t>(p);
108 const fdn::ParamSpec& spec = fdn::kParamSpecs[index];
109 double v = std::clamp(value, spec.min, spec.max);
110 if (spec.stepped) v = std::floor(v + 0.5);
111 target_[index] = v;
112 classic_.set_parameter(p, v);
113 }
114
115 double parameter(Param p) const { return target_[static_cast<std::size_t>(p)]; }
116
117 // Skip the smoothing ramps — for a preset load or a fresh render, where a
118 // 5 ms glide from the previous patch is an artifact, not a feature.
120 for (int p = 0; p < fdn::kNumParams; ++p) smoothed_[p] = target_[p];
121 const bool rate_changed = apply_tank_rate(rate_index());
122 push_controls();
123 // apply_tank_rate() must configure before controls can be derived at
124 // the new rate. Clear once more after that derivation so a snapped rate
125 // change starts from the same stochastic/control phase as a cold reset.
126 if (rate_changed) reset_rate_domain_state();
127 classic_.snap_parameters();
128 }
129
130 // Authored EQ. Not a baked param: the in-loop EQ is a voicing decision
131 // stamped into a preset, not a knob a host automates — and an automatable
132 // in-loop boost would force the stability normalization to trade broadband
133 // decay for it on every block.
134 void set_eq_band(int index, const fdn::EqBand& band) {
135 tank_.eq().set_band(index, band);
136 tank_.eq().configure(bridge_.tank_rate());
137 }
138
139 void set_active_channels(int n) { tank_.set_active_channels(n); }
140
141 // Flux depth, in dB of in-loop absorption. Voicing, like the EQ: a
142 // host-automatable version would let a knob turn change the realized decay.
143 void set_flux_depth_db(double db) { tank_.set_flux_depth_db(db); }
144 void set_flutter(bool on) { controls_.flutter = on; }
145
146 // Select the engine. Takes effect at the start of the next process_block()
147 // call, which flushes the engine it switches to.
149 requested_tier_ = tier;
150 }
151 fdn::Tier tier() const {
152 return requested_tier_;
153 }
154
155 void reset() {
157 reset_rate_domain_state();
158 classic_.reset();
159 active_tier_ = requested_tier_;
160 }
161
162 // Zero. The engine introduces no block-scale buffering; what it does have
163 // is the resampler's 4-point interpolation window, which is a fraction of a
164 // millisecond, ratio-dependent, and not bufferable delay. See
165 // interpolation_skew_samples() for the measured bound.
166 int latency_samples() const { return 0; }
167
169 return bridge_.interpolation_skew_samples();
170 }
171
172 double tank_rate() const { return bridge_.tank_rate(); }
173
174 // Ducker telemetry: the input gain the transient ducker applied to the most
175 // recent tank sample, in (0, 1]. Read-only observability for the acceptance
176 // suite and the audio harness — the ducker is otherwise invisible, and
177 // "the input dipped on that onset" is not inferable from the wet output
178 // alone once the tail has smeared it.
179 double duck_gain() const { return duck_gain_; }
180 const fdn::FdnTank<Engine>& tank() const {
181 return tank_;
182 }
183
184 // ── Audio ────────────────────────────────────────────────────────────
185 // Wet only. `n` may be anything up to the prepared maximum block size.
186 void process_block(const SampleType* in_left, const SampleType* in_right,
187 SampleType* out_left, SampleType* out_right, int n) {
188 if constexpr (std::is_same_v<SampleType, Engine>) {
189 process_engine(in_left, in_right, out_left, out_right, n);
190 } else {
193 for (int offset = 0; offset < n; offset += fdn::kMaxBlockSamples) {
194 const int m = std::min(n - offset, fdn::kMaxBlockSamples);
195 for (int i = 0; i < m; ++i) {
196 l[i] = static_cast<Engine>(in_left[offset + i]);
197 r[i] = static_cast<Engine>(in_right[offset + i]);
198 }
199 process_engine(l, r, l, r, m);
200 for (int i = 0; i < m; ++i) {
201 out_left[offset + i] = static_cast<SampleType>(l[i]);
202 out_right[offset + i] = static_cast<SampleType>(r[i]);
203 }
204 }
205 }
206 }
207
208 private:
209 void process_engine(const Engine* in_left, const Engine* in_right, Engine* out_left,
210 Engine* out_right, int n) {
211 if (n <= 0) return;
212 if (requested_tier_ != active_tier_) {
213 active_tier_ = requested_tier_;
214 if (active_tier_ == fdn::Tier::classic)
215 classic_.reset();
216 else
217 reset_rate_domain_state();
218 }
219 if (active_tier_ == fdn::Tier::classic) {
220 classic_.process_block(in_left, in_right, out_left, out_right, n);
221 return;
222 }
223 int offset = 0;
224 while (offset < n) {
225 const int chunk = std::min(n - offset, max_block_);
226 process_chunk(in_left + offset, in_right + offset, out_left + offset,
227 out_right + offset, chunk);
228 offset += chunk;
229 }
230 }
231
232 // tank_rate is stepped and never smoothed, so the requested index is read
233 // straight off the target rather than off the smoothing state.
234 int rate_index() const {
235 const auto raw = static_cast<int>(
236 target_[static_cast<std::size_t>(Param::tank_rate)] + 0.5);
237 return std::clamp(raw, 0, fdn::kNumTankRates - 1);
238 }
239
240 void reset_rate_domain_state() {
241 bridge_.reset();
242 for (auto& line : predelay_) line.reset();
243 for (auto& cascade : pre_diffusion_) cascade.reset();
244 tank_.reset();
245 ensemble_.reset();
246 ducker_.reset();
247 input_lp_.reset();
248 output_lp_.reset();
249 duck_gain_ = 1.0;
250 control_countdown_ = 0;
251 }
252
253 // A live tank-rate change is a hard, clean flush — never a crossfade. The
254 // running tail is dropped rather than reinterpreted at the new rate, so no
255 // pitch-shifted tail artifact is possible, and every resampler phase and
256 // filter state resets with it. tank_rate is a setup/texture choice, not a
257 // performance macro: do not macro-automate it.
258 //
259 // It must only ever be applied at a CHUNK BOUNDARY. The two resampler legs
260 // agree on how many tank samples a host chunk is worth; changing the ratio
261 // between them — after the input leg has produced its samples but before
262 // the output leg consumes them — leaves the output leg permanently reading
263 // past the end of the tank stream, and a switch UP in rate then silences the
264 // wet output for good rather than flushing it. process_chunk() applies any
265 // pending change before either leg runs, which is what keeps the two in
266 // step.
267 bool apply_tank_rate(int index) {
268 if (index == active_rate_index_) return false;
269 active_rate_index_ = index;
270 const double rate = fdn::kTankRates[static_cast<std::size_t>(index)];
271 bridge_.configure(rate);
272 tank_.configure(rate);
273 ducker_.configure(rate);
274 ensemble_.configure(rate);
275 for (int c = 0; c < kNumRails; ++c) {
276 pre_diffusion_[c].configure(fdn::kPreDiffusionBaseMs, rate,
277 c == 0 ? 3 : 7);
278 }
279 design_input_lp(rate);
280 smoothing_coeff_ = 1.0 - std::exp(-static_cast<double>(fdn::kControlRateSamples) /
281 std::max(1.0, fdn::kSmoothingMs * 0.001 * rate));
282 makeup_gain_ = std::pow(
283 10.0, fdn::kRateMakeupDb[static_cast<std::size_t>(index)] / 20.0);
284 reset_rate_domain_state();
285 return true;
286 }
287
288 // Below an 18 kHz tank bandwidth an extra 2-pole lowpass engages INSIDE the
289 // tank domain, ahead of the network. This is embracing the darkening, not
290 // fighting it: it is the vintage-hardware character of the low rates.
291 void design_input_lp(double tank_rate) {
292 input_lp_active_ = (fdn::kTankInputLpFraction * tank_rate) <
294 if (!input_lp_active_) return;
295 BiquadT<double> designer;
296 designer.set_coefficients(BiquadT<double>::Type::lowpass,
298 0.7071067811865476, tank_rate);
299 input_lp_coefficients_ = fdn::StereoSection::from(designer.coefficients());
300 }
301
302 // The per-mode wet lowpass runs at the HOST rate, after reconstruction: it
303 // shapes the wet return, and doing it downstream of the resampler keeps it
304 // independent of the tank-rate texture.
305 void design_output_lp() {
306 BiquadT<double> designer;
307 designer.set_coefficients(BiquadT<double>::Type::lowpass,
308 std::min(output_lp_hz_, 0.45 * host_rate_),
309 0.7071067811865476, host_rate_);
310 output_lp_coefficients_ = fdn::StereoSection::from(designer.coefficients());
311 }
312
313 void push_controls() {
314 controls_.decay = smoothed_[static_cast<std::size_t>(Param::decay)];
315 controls_.size = smoothed_[static_cast<std::size_t>(Param::size)];
316 controls_.damp_hi = smoothed_[static_cast<std::size_t>(Param::damp_hi)];
317 controls_.damp_lo = smoothed_[static_cast<std::size_t>(Param::damp_lo)];
318 controls_.diffusion = smoothed_[static_cast<std::size_t>(Param::diffusion)];
319 controls_.mod = smoothed_[static_cast<std::size_t>(Param::mod)];
320 controls_.shimmer = smoothed_[static_cast<std::size_t>(Param::shimmer)];
321 controls_.drive = smoothed_[static_cast<std::size_t>(Param::drive)];
322 controls_.bloom = smoothed_[static_cast<std::size_t>(Param::bloom)];
323 tank_.update_controls(controls_);
324
325 const double tank_rate = bridge_.tank_rate();
326 predelay_samples_ =
327 std::max(2.0, smoothed_[static_cast<std::size_t>(Param::predelay)] *
328 0.001 * tank_rate);
329 pre_g_ = static_cast<Engine>(smoothed_[static_cast<std::size_t>(Param::diffusion)] *
331 width_ = smoothed_[static_cast<std::size_t>(Param::width)];
332 const double drive = smoothed_[static_cast<std::size_t>(Param::drive)];
333 if (drive != makeup_drive_) {
334 makeup_drive_ = drive;
335 drive_makeup_gain_ = std::pow(10.0, drive * fdn::kDriveMakeupDb / 20.0);
336 }
337 }
338
339 void tick_controls() {
340 const double coeff = smoothing_coeff_;
341 for (int p = 0; p < fdn::kNumParams; ++p) {
342 if (fdn::kParamSpecs[static_cast<std::size_t>(p)].stepped)
343 smoothed_[p] = target_[p];
344 else
345 smoothed_[p] += (target_[p] - smoothed_[p]) * coeff;
346 }
347 push_controls();
348 }
349
350 void process_chunk(const Engine* in_left, const Engine* in_right, Engine* out_left,
351 Engine* out_right, int n) {
352 // Before either resampler leg runs — see the note on apply_tank_rate().
353 apply_tank_rate(rate_index());
354 const int tank_n = bridge_.host_to_tank(in_left, in_right, n);
355 Engine* tl = bridge_.tank_input(0);
356 Engine* tr = bridge_.tank_input(1);
357 Engine* ol = bridge_.tank_output(0);
358 Engine* or_ = bridge_.tank_output(1);
359
360 // Tank-rate work runs in segments that never cross a control tick, so
361 // every stage inside a segment sees constant controls. The ticks fall
362 // on the same tank samples however the host divides its blocks.
363 int done = 0;
364 while (done < tank_n) {
365 if (control_countdown_ <= 0) {
366 tick_controls();
367 control_countdown_ = fdn::kControlRateSamples;
368 }
369 const int segment = std::min(tank_n - done, control_countdown_);
370 control_countdown_ -= segment;
371 process_segment(tl + done, tr + done, ol + done, or_ + done, segment);
372 done += segment;
373 // The ducker's envelopes are cleaned on the control grid, which
374 // falls on the same tank samples however the host splits blocks.
375 if (control_countdown_ == 0)
376 ducker_.heal();
377 }
378
379 bridge_.tank_to_host(tank_n, out_left, out_right, n);
380
381 // Host-rate tail: the per-mode wet lowpass, then the rate-step makeup
382 // gain — the last multiply before the output buffer.
383 const auto makeup = fdn::lanes::splat(makeup_gain_);
384 const auto output_lp = fdn::StereoSectionLanes::from(output_lp_coefficients_);
385 auto output_state = output_lp_;
386 for (int i = 0; i < n; ++i) {
387 const auto y = output_state.process(
388 &output_lp, fdn::lanes::make(static_cast<double>(out_left[i]),
389 static_cast<double>(out_right[i])));
390 const auto scaled = y * makeup;
391 out_left[i] = static_cast<Engine>(fdn::lanes::lane(scaled, 0));
392 out_right[i] = static_cast<Engine>(fdn::lanes::lane(scaled, 1));
393 }
394 output_lp_ = output_state;
395 }
396
397 // One segment of at most a control period, all in the tank domain.
398 void process_segment(const Engine* tl, const Engine* tr, Engine* ol, Engine* or_, int n) {
401
402 // Predelay, then the transient ducker, then the low-rate input
403 // lowpass. The predelay pushes the whole segment first and reads each
404 // sample back relative to its own push.
405 // The whole segment is pushed first; each sample is read back against
406 // its own post-push position, n - 1 behind the segment's end.
407 predelay_[0].push_block(tl, n);
408 predelay_[1].push_block(tr, n);
409 predelay_[0].read_hermite_span(predelay_samples_, l, n, n - 1);
410 predelay_[1].read_hermite_span(predelay_samples_, r, n, n - 1);
411 duck_gain_ = ducker_.process_block(l, r, n);
412 if (input_lp_active_) {
413 const auto input_lp = fdn::StereoSectionLanes::from(input_lp_coefficients_);
414 auto input_state = input_lp_;
415 for (int k = 0; k < n; ++k) {
416 const auto y =
417 input_state.process(&input_lp, fdn::lanes::make(static_cast<double>(l[k]),
418 static_cast<double>(r[k])));
419 l[k] = static_cast<Engine>(fdn::lanes::lane(y, 0));
420 r[k] = static_cast<Engine>(fdn::lanes::lane(y, 1));
421 }
422 input_lp_ = input_state;
423 }
424
425 // Pre-diffusion with the coefficient's sign alternated between the
426 // rails, so left and right decorrelate from the first reflection.
427 pre_diffusion_[0].process_block(l, n, pre_g_);
428 pre_diffusion_[1].process_block(r, n, static_cast<Engine>(-pre_g_));
429
432 tank_.process_block(l, r, wet_l, wet_r, n);
433
434 const auto makeup = static_cast<Engine>(drive_makeup_gain_);
435 const auto width = static_cast<Engine>(width_);
436 for (int k = 0; k < n; ++k) {
437 const Engine dl = wet_l[k] * makeup;
438 const Engine dr = wet_r[k] * makeup;
439 const Engine mid = Engine{0.5} * (dl + dr);
440 const Engine side = Engine{0.5} * (dl - dr) * width;
441 wet_l[k] = mid + side;
442 wet_r[k] = mid - side;
443 }
444 ensemble_.process_block(wet_l, wet_r, n);
445 for (int k = 0; k < n; ++k) {
446 ol[k] = fdn::soft_limit(wet_l[k]);
447 or_[k] = fdn::soft_limit(wet_r[k]);
448 }
449 }
450
451 static constexpr int kNumRails = 2;
452
453 fdn::MultirateBridge<Engine> bridge_{};
454 std::array<fdn::FracDelayT<Engine>, kNumRails> predelay_{};
455 std::array<fdn::AllpassCascade<Engine>, kNumRails> pre_diffusion_{};
456 fdn::FdnTank<Engine> tank_{};
457 fdn::TransientDucker ducker_{};
458 fdn::EnsembleChorus<Engine> ensemble_{};
459 // Both rails share each design; the state runs them as one two-lane
460 // vector, snapped and sanitized once per block.
461 fdn::StereoBiquadState<1> input_lp_{};
462 fdn::StereoBiquadState<1> output_lp_{};
463 fdn::StereoSection input_lp_coefficients_{};
464 fdn::StereoSection output_lp_coefficients_{};
465
466 std::array<double, fdn::kNumParams> target_{};
467 std::array<double, fdn::kNumParams> smoothed_{};
468 fdn::TankControls controls_{};
469
470 double host_rate_ = 48000.0;
471 double predelay_samples_ = 2.0;
472 double width_ = 1.0;
473 double makeup_gain_ = 1.0;
474 double drive_makeup_gain_ = 1.0;
475 double makeup_drive_ = 0.0;
476 double smoothing_coeff_ = 1.0;
477 double duck_gain_ = 1.0;
478 double output_lp_hz_ = 14000.0;
479 Engine pre_g_ = 0;
480 int max_block_ = 512;
481 int control_countdown_ = 0;
482 int active_rate_index_ = -1;
483 ClassicReverbT<Engine> classic_{};
484 fdn::Tier requested_tier_ = fdn::Tier::network;
485 fdn::Tier active_tier_ = fdn::Tier::network;
486 bool input_lp_active_ = false;
487};
488
490
491} // namespace pulp::signal
@ lowpass
Definition biquad.hpp:58
void process_block(const SampleType *in_l, const SampleType *in_r, SampleType *out_l, SampleType *out_r, int n)
Definition classic_reverb.hpp:198
void reset()
Definition classic_reverb.hpp:173
Definition fdn_reverb.hpp:56
fdn::Tier tier() const
Definition fdn_reverb.hpp:151
void process_block(const SampleType *in_left, const SampleType *in_right, SampleType *out_left, SampleType *out_right, int n)
Definition fdn_reverb.hpp:186
void set_active_channels(int n)
Definition fdn_reverb.hpp:139
fdn::Param Param
Definition fdn_reverb.hpp:62
void reset()
Definition fdn_reverb.hpp:155
double tank_rate() const
Definition fdn_reverb.hpp:172
double interpolation_skew_samples() const
Definition fdn_reverb.hpp:168
int latency_samples() const
Definition fdn_reverb.hpp:166
float Engine
Definition fdn_reverb.hpp:61
void prepare(double sample_rate, int max_block_size)
Definition fdn_reverb.hpp:66
void set_eq_band(int index, const fdn::EqBand &band)
Definition fdn_reverb.hpp:134
void set_flutter(bool on)
Definition fdn_reverb.hpp:144
double parameter(Param p) const
Definition fdn_reverb.hpp:115
void set_mode(Mode mode)
Definition fdn_reverb.hpp:95
const fdn::FdnTank< Engine > & tank() const
Definition fdn_reverb.hpp:180
void set_parameter(Param p, double value)
Definition fdn_reverb.hpp:106
void snap_parameters()
Definition fdn_reverb.hpp:119
double duck_gain() const
Definition fdn_reverb.hpp:179
fdn::Mode Mode
Definition fdn_reverb.hpp:63
void set_tier(fdn::Tier tier)
Definition fdn_reverb.hpp:148
void set_flux_depth_db(double db)
Definition fdn_reverb.hpp:143
Definition tank.hpp:119
@ index
Definition gpu_pass.hpp:140
std::string tr(std::string_view key)
Convenience: translate a key using the global instance.
Definition i18n.hpp:68
constexpr double mode_default(Mode mode, Param param)
Definition config.hpp:297
constexpr double kSmoothingMs
Definition config.hpp:186
constexpr int kControlRateSamples
Definition config.hpp:187
constexpr const ModeConfig & mode_config(Mode m)
Definition config.hpp:287
constexpr std::array< ParamSpec, kNumParams > kParamSpecs
Definition config.hpp:241
constexpr double kMaxTankRate
Definition config.hpp:37
constexpr std::array< double, 8 > kRateMakeupDb
Definition config.hpp:45
constexpr int kPreDiffusionStages
Definition config.hpp:63
Param
Definition config.hpp:214
@ drive
Definition config.hpp:223
@ width
Definition config.hpp:225
Tier
Definition config.hpp:209
@ network
Definition config.hpp:209
@ classic
Definition config.hpp:209
constexpr std::array< double, 8 > kTankRates
Definition config.hpp:34
constexpr int kNumParams
Definition config.hpp:229
constexpr int kHermiteGuard
Definition config.hpp:171
Mode
Definition config.hpp:260
constexpr int kMaxBlockSamples
Definition config.hpp:190
constexpr double kTankInputLpThresholdHz
Definition config.hpp:174
constexpr double kPreDiffusionBaseMs
Definition config.hpp:67
constexpr int kNumTankRates
Definition config.hpp:36
constexpr double kTankInputLpFraction
Definition config.hpp:175
constexpr double kPreDiffusionG
Definition config.hpp:65
constexpr double kDriveMakeupDb
Definition config.hpp:163
double soft_limit(double x)
Definition stages.hpp:251
Definition additive_bank.hpp:218
@ c
Definition spectrum_trace.hpp:35
@ i
Definition formant_filter_bank.hpp:20
FdnReverbT< float > FdnReverb
Definition fdn_reverb.hpp:489
@ line
Definition web_event_translate.hpp:116
@ l
Definition input_events.hpp:40
@ y
Definition input_events.hpp:42
@ r
Definition input_events.hpp:41
@ n
Definition input_events.hpp:40
@ p
Definition input_events.hpp:40
@ k
Definition input_events.hpp:40
Definition loop_eq.hpp:51
double output_lp_hz
Definition config.hpp:275
Definition config.hpp:231
double max
Definition config.hpp:234
bool stepped
Definition config.hpp:236
double min
Definition config.hpp:233
static StereoSectionLanes from(const StereoSection &s)
Definition multirate.hpp:246
static StereoSection from(const BiquadCoefficientsT< double > &c)
Definition multirate.hpp:233