Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
classic_reverb.hpp
Go to the documentation of this file.
1#pragma once
2
3// Classic reverb tier — parallel lowpass-feedback combs into series allpasses,
4// the Schroeder/Moorer topology, driven by the same parameter set as the
5// multirate FDN reverb (fdn::Param).
6//
7// WHAT IT IS FOR
8// The FDN engine is a sixteen-line network with a resampling tank, layered
9// modulation, flux, shimmer and an output ensemble. This tier is the cheap,
10// conventional reverb: eight combs per rail, four allpasses per rail, all at
11// the host rate. It takes the controls that mean the same thing in both
12// engines and ignores the ones that describe machinery it does not have:
13//
14// decay T60 in seconds. Every comb's feedback gain follows the
15// absorptive law g = 10^(-3 D / (T60 fs)), so each comb reaches
16// -60 dB at the same time whatever its length.
17// size scales every comb and allpass length, as in the FDN. Lengths
18// are integers, so a live size change follows the smoothed
19// control: each control tick that moves it re-derives the
20// lengths, which step by whole samples as it passes them.
21// predelay milliseconds before the combs.
22// damp_hi the in-loop one-pole lowpass, on the FDN's cutoff curve, with
23// its loss scaled per comb by length so the frequency-dependent
24// decay agrees across combs.
25// diffusion the allpass coefficient (diffusion x 0.7).
26// width mid/side width of the wet output.
27// damp_lo, mod, shimmer, drive, bloom, tank_rate
28// accepted and stored, no effect: this tier has no in-loop
29// highpass, modulation, pitch shifting, saturation, gain
30// lifting, or separate tank rate.
31//
32// Continuous controls are smoothed at the 32-sample control rate, and the
33// comb gains, damping and width then move to each tick's values in
34// per-sample steps, so automation does not zipper. The allpass coefficient
35// and predelay change at the tick.
36//
37// Output is WET ONLY, like the FDN engine. Latency is zero.
38//
39// BLOCK PROCESSING
40// Every comb is longer than a control period, so a period runs at once: the
41// combs read spans written before the period began and write theirs after, and
42// the allpasses run along time on the same principle. Recursive state is
43// snapped on a fixed sample grid, so the result does not depend on how the
44// host divides its blocks.
45//
46// RT contract: prepare() allocates. Setters, process_block(), and reset()
47// allocate nothing.
48
50#include <pulp/signal/detail/simd_lanes.hpp>
55
56#include <algorithm>
57#include <array>
58#include <cmath>
59#include <cstddef>
60#include <type_traits>
61#include <vector>
62
63namespace pulp::signal {
64
65namespace classic_reverb {
66
67inline constexpr int kCombsPerRail = 8;
68inline constexpr int kCombs = 2 * kCombsPerRail;
69inline constexpr int kAllpassStages = 4;
70
71// Comb lengths at size 0.5, in milliseconds: spread over about an octave's
72// worth of modal spacing between 25 and 37 ms, so the comb peaks interleave.
73inline constexpr std::array<double, kCombsPerRail> kCombMs = {25.6, 27.1, 28.9, 30.4,
74 32.0, 33.3, 34.9, 36.6};
75// Added (never multiplied) to the rounded lengths so no two combs coincide.
76inline constexpr std::array<int, kCombsPerRail> kCombPrimes = {2, 3, 5, 7, 11, 13, 17, 19};
77// The right rail runs every comb and allpass this much longer, which is what
78// decorrelates the two outputs.
79inline constexpr double kRailSpreadMs = 0.48;
80inline constexpr double kAllpassBaseMs = 12.0;
81// Per-rail input gain into the comb banks, and the wet output gain: chosen so the
82// tier's wet level at its defaults sits within a decibel of the FDN engine's.
83inline constexpr double kInputGain = 0.05;
84inline constexpr double kOutputGain = 0.7;
85
86} // namespace classic_reverb
87
88template <typename SampleType = float> class ClassicReverbT {
89 static_assert(std::is_same_v<SampleType, float>,
90 "the classic reverb's block kernels are single precision");
91
92 public:
94 using Mode = fdn::Mode;
95
96 void prepare(double sample_rate, int max_block_size) {
97 rate_ = sample_rate > 0.0 ? sample_rate : 48000.0;
98 (void)max_block_size;
99 const double longest_ms =
101 const int comb_cap = static_cast<int>(longest_ms * 0.001 * rate_) +
103 std::size_t size = 8;
104 while (size < static_cast<std::size_t>(comb_cap))
105 size <<= 1;
106 // All sixteen combs share one write position, so they share one
107 // interleaved ring: row p holds every comb's sample p, a sample's
108 // writes are four vector stores, and kMaxBlockSamples rows past the
109 // end mirror the first rows so no span ever wraps.
110 storage_.assign((size + static_cast<std::size_t>(fdn::kMaxBlockSamples)) * kCombs,
111 SampleType{0});
112 comb_mask_ = size - 1;
113 for (auto& cascade : allpass_)
117 const int predelay_cap =
118 static_cast<int>(
119 0.001 * fdn::kParamSpecs[static_cast<std::size_t>(Param::predelay)].max * rate_) +
121 for (auto& line : predelay_)
122 line.prepare(predelay_cap);
123 smoothing_coeff_ = 1.0 - std::exp(-static_cast<double>(fdn::kControlRateSamples) /
124 std::max(1.0, fdn::kSmoothingMs * 0.001 * rate_));
125 for (int p = 0; p < fdn::kNumParams; ++p) {
126 target_[static_cast<std::size_t>(p)] =
127 fdn::kParamSpecs[static_cast<std::size_t>(p)].default_value;
128 }
129 reset();
130 }
131
132 // Stamp a mode's defaults, as FdnReverbT::set_mode does.
133 void set_mode(Mode mode) {
134 for (int p = 0; p < fdn::kNumParams; ++p) {
135 const auto param = static_cast<Param>(p);
136 if (param == Param::predelay || param == Param::width)
137 continue;
138 set_parameter(param, fdn::mode_default(mode, param));
139 }
141 }
142
143 void set_parameter(Param p, double value) {
144 const fdn::ParamSpec& spec = fdn::kParamSpecs[static_cast<std::size_t>(p)];
145 double v = std::clamp(value, spec.min, spec.max);
146 if (spec.stepped)
147 v = std::floor(v + 0.5);
148 target_[static_cast<std::size_t>(p)] = v;
149 settling_ = true;
150 }
151
152 double parameter(Param p) const {
153 return target_[static_cast<std::size_t>(p)];
154 }
155
156 // True when every sample held in the comb bank is exactly zero: the
157 // recirculating part of the tail has fully decayed.
158 bool combs_silent() const {
159 return std::all_of(storage_.begin(), storage_.end(),
160 [](SampleType v) { return v == SampleType{0}; });
161 }
162
163 // Skip the smoothing ramps, and re-derive the lengths from size.
165 smoothed_ = target_;
166 smoothing_stages_.fill(target_);
167 settling_ = false;
168 derive_lengths();
169 derive();
170 ramping_ = false;
171 }
172
173 void reset() {
175 std::fill(storage_.begin(), storage_.end(), SampleType{0});
176 write_ = 0;
177 lp_.fill(SampleType{0});
178 for (auto& cascade : allpass_)
179 cascade.reset();
180 for (auto& line : predelay_)
181 line.reset();
182 control_countdown_ = 0;
183 }
184
185 int latency_samples() const {
186 return 0;
187 }
188
189 // The realized comb lengths and feedback gains, for structural tests.
190 int comb_length(int comb) const {
191 return length_[static_cast<std::size_t>(comb)];
192 }
193 double comb_gain(int comb) const {
194 return gain_[static_cast<std::size_t>(comb)];
195 }
196
197 // Wet only. Any n; the input may alias neither output.
198 void process_block(const SampleType* in_l, const SampleType* in_r, SampleType* out_l,
199 SampleType* out_r, int n) {
200 int done = 0;
201 while (done < n) {
202 if (control_countdown_ <= 0) {
203 tick_controls();
204 control_countdown_ = fdn::kControlRateSamples;
205 }
206 const int piece = std::min(n - done, control_countdown_);
207 process_piece(in_l + done, in_r + done, out_l + done, out_r + done, piece,
208 fdn::kControlRateSamples - control_countdown_);
209 control_countdown_ -= piece;
210 if (control_countdown_ == 0)
211 heal();
212 done += piece;
213 }
214 }
215
216 private:
217 static constexpr int kCombs = classic_reverb::kCombs;
218
219 // Continuous controls pass through three one-pole stages. A host that
220 // moves a target once per block steps it at the block rate, and each
221 // stage takes those steps down by about 20 dB more before they reach the
222 // comb gains; three bring a block-rate sweep's sidebands to the floor of
223 // a measurement that sees -116 dB, for about 10 ms more settling.
224 void tick_controls() {
225 // Once a tick moves nothing, every later tick is the same no-op until
226 // a target changes.
227 if (!settling_) {
228 ramping_ = false;
229 return;
230 }
231 bool changed = false;
232 bool moving = false;
233 for (std::size_t p = 0; p < smoothed_.size(); ++p) {
234 const double before = smoothed_[p];
235 if (fdn::kParamSpecs[p].stepped) {
236 for (auto& stage : smoothing_stages_)
237 stage[p] = target_[p];
238 smoothed_[p] = target_[p];
239 } else {
240 double in = target_[p];
241 for (auto& stage : smoothing_stages_) {
242 const double was = stage[p];
243 stage[p] += (in - stage[p]) * smoothing_coeff_;
244 moving = moving || stage[p] != was;
245 in = stage[p];
246 }
247 smoothed_[p] += (in - smoothed_[p]) * smoothing_coeff_;
248 }
249 changed = changed || smoothed_[p] != before;
250 }
251 settling_ = changed || moving;
252 ramping_ = changed;
253 if (!changed)
254 return;
255 // The comb gains, the damping and the width move to their new values
256 // over the control period in equal per-sample steps, starting from
257 // where the last period ended, so a swept control changes the sound
258 // smoothly rather than in 32-sample stairs.
259 const auto from_gain = gain_f_;
260 const auto from_damp = damp_f_;
261 const SampleType from_width = width_;
262 if (smoothed(Param::size) != lengths_size_)
263 derive_lengths();
264 derive();
265 constexpr auto inv = static_cast<SampleType>(1.0 / fdn::kControlRateSamples);
266 for (std::size_t i = 0; i < static_cast<std::size_t>(kCombs); ++i) {
267 gain_from_[i] = from_gain[i];
268 gain_step_[i] = (gain_f_[i] - from_gain[i]) * inv;
269 damp_from_[i] = from_damp[i];
270 damp_step_[i] = (damp_f_[i] - from_damp[i]) * inv;
271 }
272 width_from_ = from_width;
273 width_step_ = (width_ - from_width) * inv;
274 }
275
276 double smoothed(Param p) const {
277 return smoothed_[static_cast<std::size_t>(p)];
278 }
279
280 // Comb and allpass lengths from size.
281 void derive_lengths() {
282 lengths_size_ = smoothed(Param::size);
283 const double scale = fdn::kSizeScaleMin + std::clamp(smoothed(Param::size), 0.0, 1.0) *
285 const double spread = classic_reverb::kRailSpreadMs * 0.001 * rate_;
286 const int longest = static_cast<int>(comb_mask_) + 1 - fdn::kMaxBlockSamples - 4;
287 for (int rail = 0; rail < 2; ++rail) {
288 for (int c = 0; c < classic_reverb::kCombsPerRail; ++c) {
289 const auto i = static_cast<std::size_t>(rail * classic_reverb::kCombsPerRail + c);
290 const double base =
291 classic_reverb::kCombMs[static_cast<std::size_t>(c)] * 0.001 * rate_ * scale;
292 const int length = static_cast<int>(std::lround(base + rail * spread)) +
293 classic_reverb::kCombPrimes[static_cast<std::size_t>(c)];
294 length_[i] = std::clamp(length, fdn::kMaxBlockSamples, longest);
295 }
296 }
297 for (int rail = 0; rail < 2; ++rail) {
298 allpass_[static_cast<std::size_t>(rail)].configure(
299 classic_reverb::kAllpassBaseMs * scale, rate_,
300 rail == 0 ? 0 : static_cast<int>(std::lround(spread)));
301 }
302 }
303
304 // Gains, damping, allpass coefficient, predelay and width from the
305 // smoothed controls and the current lengths.
306 void derive() {
307 // The absorptive law on every comb, and damping whose loss scales with
308 // length (the FDN's rule): a longer comb filters proportionally harder.
309 const double t60 = std::max(smoothed(Param::decay), 0.01);
310 const double lp_hz = fdn::kDampHiMinHz *
312 std::pow(1.0 - std::clamp(smoothed(Param::damp_hi), 0.0, 1.0),
314 const double pole = std::exp(-6.283185307179586 * lp_hz / rate_);
315 double median = 0.0;
316 for (int i = 0; i < kCombs; ++i)
317 median += static_cast<double>(length_[static_cast<std::size_t>(i)]);
318 median /= kCombs;
319 for (std::size_t i = 0; i < static_cast<std::size_t>(kCombs); ++i) {
320 const double length = static_cast<double>(length_[i]);
321 gain_[i] = std::pow(10.0, -3.0 * length / (t60 * rate_));
322 const double ratio = std::clamp(length / median, 0.05, 20.0);
323 const double damp = 1.0 - std::pow(1.0 - pole, ratio);
324 gain_f_[i] = static_cast<SampleType>(gain_[i]);
325 damp_f_[i] = static_cast<SampleType>(damp);
326 }
327 allpass_g_ = static_cast<SampleType>(smoothed(Param::diffusion) * fdn::kPreDiffusionG);
328 predelay_samples_ = std::max(2.0, smoothed(Param::predelay) * 0.001 * rate_);
329 width_ = static_cast<SampleType>(smoothed(Param::width));
330 }
331
332 // `phase` is the piece's first sample's position within its control period.
333 void process_piece(const SampleType* in_l, const SampleType* in_r, SampleType* out_l,
334 SampleType* out_r, int n, int phase) {
335 // Each rail through its own predelay, pushed as a span and read back
336 // against each sample's own post-push position. The left input feeds
337 // the left comb bank and the right the right one, so the tier is true
338 // stereo like the network: an anti-phase input is not cancelled.
339 SampleType feed[2][fdn::kMaxBlockSamples];
340 constexpr auto input_gain = static_cast<SampleType>(classic_reverb::kInputGain);
341 for (int k = 0; k < n; ++k) {
342 feed[0][k] = input_gain * fdn::finite_or_zero(in_l[k]);
343 feed[1][k] = input_gain * fdn::finite_or_zero(in_r[k]);
344 }
345 for (int rail = 0; rail < 2; ++rail) {
346 predelay_[static_cast<std::size_t>(rail)].push_block(feed[rail], n);
347 predelay_[static_cast<std::size_t>(rail)].read_hermite_span(predelay_samples_,
348 feed[rail], n, n - 1);
349 }
350
351 // The comb bank.
352 SampleType left[fdn::kMaxBlockSamples];
353 SampleType right[fdn::kMaxBlockSamples];
354 if (ramping_)
355 run_combs<true>(feed[0], feed[1], left, right, n, phase);
356 else
357 run_combs<false>(feed[0], feed[1], left, right, n, phase);
358 commit_combs(n);
359
360 allpass_[0].process_block(left, n, allpass_g_);
361 allpass_[1].process_block(right, n, allpass_g_);
362
363 constexpr auto output_gain = static_cast<SampleType>(classic_reverb::kOutputGain);
364 const auto mix = [&](int k, SampleType width) {
365 const SampleType mid = SampleType{0.5} * (left[k] + right[k]);
366 const SampleType side = SampleType{0.5} * width * (left[k] - right[k]);
367 out_l[k] = output_gain * (mid + side);
368 out_r[k] = output_gain * (mid - side);
369 };
370 if (ramping_) {
371 for (int k = 0; k < n; ++k)
372 mix(k, width_from_ + width_step_ * static_cast<SampleType>(phase + k + 1));
373 } else {
374 for (int k = 0; k < n; ++k)
375 mix(k, width_);
376 }
377 }
378
379 std::size_t read_index(std::size_t comb) const {
380 return (write_ - static_cast<std::size_t>(length_[comb])) & comb_mask_;
381 }
382
383 // Every comb outputs its delayed sample and stores the input plus its
384 // lowpassed delayed sample times its gain. Sixteen combs are four 4-lane
385 // vectors (combs 0-7 the left rail, 8-15 the right); each reads its own
386 // delayed row, all write the same row. While Ramp, each sample's gain and
387 // damping are the ramp's value at that sample's place in the period.
388 template <bool Ramp>
389 void run_combs(const SampleType* in_l, const SampleType* in_r, SampleType* left,
390 SampleType* right, int n, int phase) {
391 namespace lanes = detail::lanes;
392 using lanes::F4;
393 constexpr std::size_t kRow = kCombs;
394 const SampleType* read[kCombs];
395 for (std::size_t i = 0; i < static_cast<std::size_t>(kCombs); ++i)
396 read[i] = storage_.data() + read_index(i) * kRow + i;
397 SampleType* write = storage_.data() + write_ * kRow;
398 const F4 threshold = lanes::splat(snap_threshold<SampleType>());
399 const F4 zero = lanes::splat(SampleType{0});
400 F4 lp[4], damp[4], gain[4];
401 for (int j = 0; j < 4; ++j) {
402 lp[j] = lanes::load(&lp_[4 * static_cast<std::size_t>(j)]);
403 damp[j] = lanes::load(&damp_f_[4 * static_cast<std::size_t>(j)]);
404 gain[j] = lanes::load(&gain_f_[4 * static_cast<std::size_t>(j)]);
405 }
406 for (int k = 0; k < n; ++k) {
407 const std::size_t at = static_cast<std::size_t>(k) * kRow;
408 F4 delayed[4];
409 for (int j = 0; j < 4; ++j)
410 delayed[j] = lanes::make(read[4 * j][at], read[4 * j + 1][at], read[4 * j + 2][at],
411 read[4 * j + 3][at]);
412 const F4 feed[2] = {lanes::splat(in_l[k]), lanes::splat(in_r[k])};
413 if constexpr (Ramp) {
414 const F4 step = lanes::splat(static_cast<SampleType>(phase + k + 1));
415 for (int j = 0; j < 4; ++j) {
416 const auto u = 4 * static_cast<std::size_t>(j);
417 gain[j] = lanes::load(&gain_from_[u]) + lanes::load(&gain_step_[u]) * step;
418 damp[j] = lanes::load(&damp_from_[u]) + lanes::load(&damp_step_[u]) * step;
419 }
420 }
421 for (int j = 0; j < 4; ++j) {
422 lp[j] = delayed[j] + (lp[j] - delayed[j]) * damp[j];
423 // Snapped as it is stored: a loop gain above one half rounds
424 // the smallest subnormal back to itself, so an unsnapped
425 // comb recirculates subnormals forever without FTZ.
426 const F4 stored = feed[j / 2] + gain[j] * lp[j];
427 lanes::store(
428 write + at + 4 * static_cast<std::size_t>(j),
429 lanes::select(lanes::less(lanes::abs(stored), threshold), zero, stored));
430 }
431 const F4 l = delayed[0] + delayed[1];
432 const F4 r = delayed[2] + delayed[3];
433 left[k] =
434 (lanes::lane(l, 0) + lanes::lane(l, 1)) + (lanes::lane(l, 2) + lanes::lane(l, 3));
435 right[k] =
436 (lanes::lane(r, 0) + lanes::lane(r, 1)) + (lanes::lane(r, 2) + lanes::lane(r, 3));
437 }
438 for (int j = 0; j < 4; ++j)
439 lanes::store(&lp_[4 * static_cast<std::size_t>(j)], lp[j]);
440 }
441
442 // Advance past n rows written from write_: move rows written past the
443 // ring's end to its start, then refresh the mirror if the first rows
444 // changed.
445 void commit_combs(int n) {
446 constexpr std::size_t kRow = kCombs;
447 const std::size_t size = comb_mask_ + 1;
448 const std::size_t end = write_ + static_cast<std::size_t>(n);
449 SampleType* rows = storage_.data();
450 if (end > size)
451 std::copy(rows + size * kRow, rows + end * kRow, rows);
452 const auto mirror = static_cast<std::size_t>(fdn::kMaxBlockSamples);
453 if (write_ < mirror || end > size)
454 std::copy(rows, rows + mirror * kRow, rows + size * kRow);
455 write_ = end & comb_mask_;
456 }
457
458 // On the control grid: snap decayed loop filters and clear any that went
459 // non-finite. The comb writes are snapped as they are stored, so a silent
460 // tail reaches exact zero in the rings as well.
461 void heal() {
462 for (auto& v : lp_)
463 v = std::isfinite(v) ? snap_to_zero(v) : SampleType{0};
464 }
465
466 std::vector<SampleType> storage_{};
467 std::array<int, kCombs> length_{};
468 std::array<double, kCombs> gain_{};
469 std::array<SampleType, kCombs> gain_f_{};
470 std::array<SampleType, kCombs> damp_f_{};
471 std::array<SampleType, kCombs> lp_{};
472 std::array<fdn::AllpassCascade<SampleType>, 2> allpass_{};
473 std::array<fdn::FracDelayT<SampleType>, 2> predelay_{};
474 std::array<double, fdn::kNumParams> target_{};
475 std::array<std::array<double, fdn::kNumParams>, 2> smoothing_stages_{};
476 std::array<double, fdn::kNumParams> smoothed_{};
477 std::size_t comb_mask_ = 0;
478 // The size the comb and allpass lengths were last derived from.
479 double lengths_size_ = -1.0;
480 std::size_t write_ = 0;
481 double rate_ = 48000.0;
482 double smoothing_coeff_ = 1.0;
483 double predelay_samples_ = 2.0;
484 SampleType allpass_g_ = 0;
485 SampleType width_ = 1;
486 // The per-sample ramp toward the values derive() last produced.
487 std::array<SampleType, kCombs> gain_from_{};
488 std::array<SampleType, kCombs> gain_step_{};
489 std::array<SampleType, kCombs> damp_from_{};
490 std::array<SampleType, kCombs> damp_step_{};
491 SampleType width_from_ = 1;
492 SampleType width_step_ = 0;
493 bool ramping_ = false;
494 bool settling_ = true;
495 int control_countdown_ = 0;
496};
497
499
500} // namespace pulp::signal
Definition classic_reverb.hpp:88
fdn::Mode Mode
Definition classic_reverb.hpp:94
void set_parameter(Param p, double value)
Definition classic_reverb.hpp:143
int comb_length(int comb) const
Definition classic_reverb.hpp:190
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 prepare(double sample_rate, int max_block_size)
Definition classic_reverb.hpp:96
void reset()
Definition classic_reverb.hpp:173
bool combs_silent() const
Definition classic_reverb.hpp:158
void snap_parameters()
Definition classic_reverb.hpp:164
double comb_gain(int comb) const
Definition classic_reverb.hpp:193
fdn::Param Param
Definition classic_reverb.hpp:93
void set_mode(Mode mode)
Definition classic_reverb.hpp:133
double parameter(Param p) const
Definition classic_reverb.hpp:152
int latency_samples() const
Definition classic_reverb.hpp:185
@ mirror
Definition canvas.hpp:169
@ end
Definition editor_host.hpp:354
MidiEvent at(MidiEvent event, std::int32_t sample_offset) noexcept
Definition utility_contract.hpp:140
@ spread
Definition voicing.hpp:23
Definition classic_reverb.hpp:65
constexpr double kOutputGain
Definition classic_reverb.hpp:84
constexpr double kRailSpreadMs
Definition classic_reverb.hpp:79
constexpr int kCombs
Definition classic_reverb.hpp:68
constexpr int kCombsPerRail
Definition classic_reverb.hpp:67
constexpr double kInputGain
Definition classic_reverb.hpp:83
constexpr std::array< int, kCombsPerRail > kCombPrimes
Definition classic_reverb.hpp:76
constexpr double kAllpassBaseMs
Definition classic_reverb.hpp:80
constexpr std::array< double, kCombsPerRail > kCombMs
Definition classic_reverb.hpp:73
constexpr int kAllpassStages
Definition classic_reverb.hpp:69
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 double kDampCurve
Definition config.hpp:110
T finite_or_zero(T value)
Definition sanitize.hpp:12
constexpr std::array< ParamSpec, kNumParams > kParamSpecs
Definition config.hpp:241
constexpr double kSizeScaleMax
Definition config.hpp:56
constexpr double kDampHiMinHz
Definition config.hpp:99
Param
Definition config.hpp:214
@ width
Definition config.hpp:225
@ size
Definition config.hpp:216
constexpr double kSizeScaleMin
Definition config.hpp:55
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 kPreDiffusionG
Definition config.hpp:65
constexpr double kDampHiMaxHz
Definition config.hpp:105
std::uint64_t read(std::span< const std::uint8_t > b, std::size_t p, unsigned n) noexcept
Definition spectral_frame_codec.hpp:111
void write(std::uint8_t *b, std::size_t p, std::uint64_t v, unsigned n) noexcept
Definition spectral_frame_codec.hpp:117
Definition additive_bank.hpp:218
ClassicReverbT< float > ClassicReverb
Definition classic_reverb.hpp:498
constexpr T snap_threshold()
Threshold in absolute value below which a sample is treated as zero.
Definition denormal.hpp:36
@ right
Definition multi_channel_meter.hpp:67
@ left
Definition multi_channel_meter.hpp:66
@ feed
Definition finite_stretch_builder.hpp:40
@ c
Definition spectrum_trace.hpp:35
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
@ ratio
base_frequency_hz * ratio.
Definition fm_operator_engine.hpp:20
@ u
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
void scale(const float *a, float scalar, float *dst, std::size_t count) noexcept
@ l
Definition input_events.hpp:40
@ j
Definition input_events.hpp:40
@ r
Definition input_events.hpp:41
@ n
Definition input_events.hpp:40
@ p
Definition input_events.hpp:40
@ k
Definition input_events.hpp:40
@ v
Definition input_events.hpp:41
@ changed
Definition gesture.hpp:17
Definition config.hpp:231
double max
Definition config.hpp:234
bool stepped
Definition config.hpp:236
double min
Definition config.hpp:233