38#ifndef SBL_DSP_MODULES_PLATE_REVERB_HPP_
39#define SBL_DSP_MODULES_PLATE_REVERB_HPP_
127struct AlgoReverbTestAccess;
133template<u
int32_t ScalePercent = 100>
136 static constexpr uint32_t S(uint32_t base) {
137 return base * ScalePercent / 100;
141 static constexpr float MOD_DEPTH =
143 static constexpr uint32_t MOD_MARGIN =
144 static_cast<uint32_t
>(MOD_DEPTH) + 2;
180 static constexpr float kMaxDecay = 0.99f;
181 static constexpr float kMinBandwidthHz = 100.0f;
182 static constexpr float kMaxBandwidthHz = 20000.0f;
183 static constexpr float kBandwidthOctaves = 7.644f;
184 static constexpr float kMaxTankHpHz = 500.0f;
185 static constexpr float kMinTankHpFilterHz = 1.0f;
186 static constexpr float kMaxDiffusion1 = 0.95f;
189 static constexpr float kMinOutputScale = 0.001f;
196 INPUT_AP1 + INPUT_AP2 + INPUT_AP3 + INPUT_AP4 +
197 TANK_L_AP1 + (TANK_L_DELAY1 + MOD_MARGIN) +
198 TANK_L_AP2 + (TANK_L_DELAY2 + MOD_MARGIN) +
199 TANK_R_AP1 + (TANK_R_DELAY1 + MOD_MARGIN) +
200 TANK_R_AP2 + (TANK_R_DELAY2 + MOD_MARGIN) +
210 void init(
float* pool, uint32_t pool_size) {
214 memset(pool, 0, pool_size *
sizeof(
float));
218 predelay_.
init(p, PREDELAY_MAX); p += PREDELAY_MAX;
220 input_ap_[0].
init(p, INPUT_AP1); p += INPUT_AP1;
221 input_ap_[1].
init(p, INPUT_AP2); p += INPUT_AP2;
222 input_ap_[2].
init(p, INPUT_AP3); p += INPUT_AP3;
223 input_ap_[3].
init(p, INPUT_AP4); p += INPUT_AP4;
231 tank_l_ap1_.
init(p, TANK_L_AP1); p += TANK_L_AP1;
233 tank_l_dl1_.
init(p, TANK_L_DELAY1 + MOD_MARGIN);
234 p += TANK_L_DELAY1 + MOD_MARGIN;
236 tank_l_ap2_.
init(p, TANK_L_AP2); p += TANK_L_AP2;
238 tank_l_dl2_.
init(p, TANK_L_DELAY2 + MOD_MARGIN);
239 p += TANK_L_DELAY2 + MOD_MARGIN;
244 tank_r_ap1_.
init(p, TANK_R_AP1); p += TANK_R_AP1;
246 tank_r_dl1_.
init(p, TANK_R_DELAY1 + MOD_MARGIN);
247 p += TANK_R_DELAY1 + MOD_MARGIN;
249 tank_r_ap2_.
init(p, TANK_R_AP2); p += TANK_R_AP2;
251 tank_r_dl2_.
init(p, TANK_R_DELAY2 + MOD_MARGIN);
252 p += TANK_R_DELAY2 + MOD_MARGIN;
257 pool_size_ = pool_size;
268 if (su < 0.0f) su = 0.0f;
269 if (su > 1.0f) su = 1.0f;
270 decay_ = su * kMaxDecay;
275 if (su < 0.0f) su = 0.0f;
276 if (su > 1.0f) su = 1.0f;
282 if (su < 0.0f) su = 0.0f;
283 if (su > 1.0f) su = 1.0f;
284 target_predelay_ = su *
static_cast<float>(PREDELAY_MAX - 1);
289 if (su < 0.0f) su = 0.0f;
290 if (su > 1.0f) su = 1.0f;
296 if (su < 0.0f) su = 0.0f;
297 if (su > 1.0f) su = 1.0f;
305 if (su < 0.0f) su = 0.0f;
306 if (su > 1.0f) su = 1.0f;
314 if (su < 0.0f) su = 0.0f;
315 if (su > 1.0f) su = 1.0f;
322 if (su < 0.0f) su = 0.0f;
323 if (su > 1.0f) su = 1.0f;
324 diffusion_d1_ = su * kMaxDiffusion1;
325 diffusion_d2_ = su * kMaxDiffusion2;
330 if (su < 0.0f) su = 0.0f;
331 if (su > 1.0f) su = 1.0f;
332 output_scale_ = kMinOutputScale + su * (1.0f - kMinOutputScale);
339 float decay()
const {
return decay_; }
341 float mix()
const {
return mix_; }
342 float width()
const {
return width_; }
346 return static_cast<float>(PREDELAY_MAX) * 1000.0f
353 void process(
float* left,
float* right, uint16_t frames) {
355 if (!smoothers_configured_) configure_smoothers();
357 for (uint16_t i = 0; i < frames; ++i) {
359 float sd = op_decay_.
process(decay_);
360 float sdamp = op_damping_.
process(damping_);
361 float smix = op_mix_.
process(mix_);
362 float swidth = op_width_.
process(width_);
363 float spd = op_predelay_.
process(target_predelay_);
364 float soscale = op_output_scale_.
process(output_scale_);
367 float sbw_su = op_bandwidth_su_.
process(bandwidth_su_);
371 float sthp_su = op_tank_hp_su_.
process(tank_hp_su_);
374 float sd1 = op_diffusion_d1_.
process(diffusion_d1_);
375 float sd2 = op_diffusion_d2_.
process(diffusion_d2_);
389 float damp_coeff = 1.0f - sdamp;
393 const float wet = smix;
394 const float dry = 1.0f - wet;
396 float in_l = left[i];
397 float in_r = right[i];
399 float input = (in_l + in_r) * 0.5f;
406 predelayed = predelay_.
read(spd);
408 predelay_.
write(input);
411 float limited = bandwidth_.
process(predelayed);
414 float diffused = limited;
415 for (
int ap = 0; ap < 4; ++ap) {
416 diffused = input_ap_[ap].
process(diffused);
420 mod_sin_ += mod_inc_ * mod_cos_;
421 mod_cos_ -= mod_inc_ * mod_sin_;
422 float mod_dl1_l = mod_sin_ * MOD_DEPTH;
423 float mod_dl2_l = mod_cos_ * MOD_DEPTH;
424 float mod_dl1_r = -mod_sin_ * MOD_DEPTH;
425 float mod_dl2_r = -mod_cos_ * MOD_DEPTH;
428 float fb_l = tank_r_out_;
429 float fb_r = tank_l_out_;
430 if (sthp_su > 0.0f) {
433 fb_l = tank_hp_l_.
process(fb_l);
434 fb_r = tank_hp_r_.
process(fb_r);
436 float tank_l_in = diffused + sd * fb_l;
437 float tank_r_in = diffused + sd * fb_r;
440 float tl = tank_l_ap1_.
process(tank_l_in);
442 static_cast<float>(TANK_L_DELAY1) + mod_dl1_l);
443 tank_l_dl1_.
write(tl);
446 static_cast<float>(TANK_L_DELAY2) + mod_dl2_l);
447 tank_l_dl2_.
write(tl);
448 tank_l_out_ = tl_dl2;
451 float tr = tank_r_ap1_.
process(tank_r_in);
453 static_cast<float>(TANK_R_DELAY1) + mod_dl1_r);
454 tank_r_dl1_.
write(tr);
457 static_cast<float>(TANK_R_DELAY2) + mod_dl2_r);
458 tank_r_dl2_.
write(tr);
459 tank_r_out_ = tr_dl2;
462 float out_l = tap_from(tank_r_ap1_buf_, TANK_R_AP1,
464 + tap_from(tank_r_dl1_buf_,
465 TANK_R_DELAY1 + MOD_MARGIN,
467 - tap_from(tank_r_ap2_buf_, TANK_R_AP2,
469 + tap_from(tank_r_dl2_buf_,
470 TANK_R_DELAY2 + MOD_MARGIN,
472 - tap_from(tank_l_dl1_buf_,
473 TANK_L_DELAY1 + MOD_MARGIN,
475 - tap_from(tank_l_ap2_buf_, TANK_L_AP2,
477 - tap_from(tank_l_dl2_buf_,
478 TANK_L_DELAY2 + MOD_MARGIN,
481 float out_r = tap_from(tank_l_ap1_buf_, TANK_L_AP1,
483 + tap_from(tank_l_dl1_buf_,
484 TANK_L_DELAY1 + MOD_MARGIN,
486 - tap_from(tank_l_ap2_buf_, TANK_L_AP2,
488 + tap_from(tank_l_dl2_buf_,
489 TANK_L_DELAY2 + MOD_MARGIN,
491 - tap_from(tank_r_dl1_buf_,
492 TANK_R_DELAY1 + MOD_MARGIN,
494 - tap_from(tank_r_ap2_buf_, TANK_R_AP2,
496 - tap_from(tank_r_dl2_buf_,
497 TANK_R_DELAY2 + MOD_MARGIN,
503 float mono = (out_l + out_r) * 0.5f;
504 out_l = mono + swidth * (out_l - mono);
505 out_r = mono + swidth * (out_r - mono);
507 left[i] = dry * in_l + wet * out_l;
508 right[i] = dry * in_r + wet * out_r;
514 for (uint32_t i = 0; i < pool_size_; ++i) pool_[i] = 0.0f;
517 for (
int i = 0; i < 4; ++i) input_ap_[i].
reset();
532 tank_l_damp_.
reset();
533 tank_r_damp_.
reset();
542 op_decay_.
reset(decay_);
543 op_damping_.
reset(damping_);
545 op_width_.
reset(width_);
546 op_predelay_.
reset(target_predelay_);
547 op_bandwidth_su_.
reset(bandwidth_su_);
548 op_tank_hp_su_.
reset(tank_hp_su_);
549 op_diffusion_d1_.
reset(diffusion_d1_);
550 op_diffusion_d2_.
reset(diffusion_d2_);
551 op_output_scale_.
reset(output_scale_);
561 void set_decay(
float decay) {
565 op_decay_.
reset(decay_);
568 void set_damping(
float damp) {
569 if (damp > 1.0f) damp = 1.0f;
570 if (damp < 0.0f) damp = 0.0f;
572 op_damping_.
reset(damping_);
575 void set_predelay(
float ms) {
577 if (samples < 0.0f) samples = 0.0f;
578 if (samples >=
static_cast<float>(PREDELAY_MAX)) {
579 samples =
static_cast<float>(PREDELAY_MAX - 1);
581 target_predelay_ = samples;
582 op_predelay_.
reset(target_predelay_);
585 void set_width(
float width) {
589 op_width_.
reset(width_);
592 void set_mix(
float mix) {
593 if (
mix > 1.0f)
mix = 1.0f;
594 if (
mix < 0.0f)
mix = 0.0f;
603 void set_tank_hp(
float freq_hz) {
606 if (freq_hz > 0.0f) {
607 su = log2f(freq_hz / kMinTankHpFilterHz)
608 / log2f(kMaxTankHpHz / kMinTankHpFilterHz);
609 if (su < 0.0f) su = 0.0f;
610 if (su > 1.0f) su = 1.0f;
613 op_tank_hp_su_.
reset(su);
624 void set_bandwidth(
float freq_hz) {
625 if (freq_hz < kMinBandwidthHz) freq_hz = kMinBandwidthHz;
627 float su = log2f(freq_hz / kMinBandwidthHz) / kBandwidthOctaves;
628 if (su > 1.0f) su = 1.0f;
631 op_bandwidth_su_.
reset(su);
639 void set_input_diffusion(
float d1,
float d2) {
640 if (d1 < 0.0f) d1 = 0.0f;
641 if (d1 > 0.95f) d1 = 0.95f;
642 if (d2 < 0.0f) d2 = 0.0f;
643 if (d2 > 0.95f) d2 = 0.95f;
650 op_diffusion_d1_.
reset(d1);
651 op_diffusion_d2_.
reset(d2);
658 void set_output_scale(
float scale) {
659 if (scale < 0.001f) scale = 0.001f;
660 if (scale > 1.0f) scale = 1.0f;
661 output_scale_ = scale;
662 op_output_scale_.
reset(scale);
669 void configure_smoothers() {
674 bandwidth_.
set_range(kMinBandwidthHz, kMaxBandwidthHz);
677 tank_hp_l_.
set_range(kMinTankHpFilterHz, kMaxTankHpHz);
678 tank_hp_r_.
set_range(kMinTankHpFilterHz, kMaxTankHpHz);
693 op_decay_.
reset(decay_);
694 op_damping_.
reset(damping_);
696 op_width_.
reset(width_);
697 op_predelay_.
reset(target_predelay_);
698 op_bandwidth_su_.
reset(bandwidth_su_);
699 op_tank_hp_su_.
reset(tank_hp_su_);
700 op_diffusion_d1_.
reset(diffusion_d1_);
701 op_diffusion_d2_.
reset(diffusion_d2_);
702 op_output_scale_.
reset(output_scale_);
703 smoothers_configured_ =
true;
710 static float tap_from(
const float* buf, uint32_t buf_size,
711 uint32_t write_pos, uint32_t tap_offset) {
712 if (tap_offset >= buf_size) tap_offset = buf_size - 1;
713 uint32_t pos = (write_pos >= tap_offset)
714 ? write_pos - tap_offset
715 : write_pos + buf_size - tap_offset;
723 float* pool_ =
nullptr;
724 uint32_t pool_size_ = 0;
726 float* tank_l_ap1_buf_ =
nullptr;
727 float* tank_l_dl1_buf_ =
nullptr;
728 float* tank_l_ap2_buf_ =
nullptr;
729 float* tank_l_dl2_buf_ =
nullptr;
731 float* tank_r_ap1_buf_ =
nullptr;
732 float* tank_r_dl1_buf_ =
nullptr;
733 float* tank_r_ap2_buf_ =
nullptr;
734 float* tank_r_dl2_buf_ =
nullptr;
736 primitives::AllpassFilter input_ap_[4];
737 primitives::AllpassFilter tank_l_ap1_;
738 primitives::AllpassFilter tank_l_ap2_;
739 primitives::AllpassFilter tank_r_ap1_;
740 primitives::AllpassFilter tank_r_ap2_;
742 primitives::DelayLine tank_l_dl1_;
743 primitives::DelayLine tank_l_dl2_;
744 primitives::DelayLine tank_r_dl1_;
745 primitives::DelayLine tank_r_dl2_;
747 primitives::DelayLine predelay_;
749 float tank_l_out_ = 0.0f;
750 float tank_r_out_ = 0.0f;
752 primitives::OnePole tank_l_damp_;
753 primitives::OnePole tank_r_damp_;
754 primitives::OnePole bandwidth_;
756 primitives::OnePoleHP tank_hp_l_;
757 primitives::OnePoleHP tank_hp_r_;
759 float mod_sin_ = 0.0f;
760 float mod_cos_ = 1.0f;
761 float mod_inc_ = 0.0f;
765 float damping_ = 0.3f;
769 float target_predelay_ = 0.0f;
770 float bandwidth_su_ = 0.869f;
771 float tank_hp_su_ = 0.0f;
776 primitives::ParameterSmoother op_decay_;
777 primitives::ParameterSmoother op_damping_;
778 primitives::ParameterSmoother op_mix_;
779 primitives::ParameterSmoother op_width_;
780 primitives::ParameterSmoother op_predelay_;
781 primitives::ParameterSmoother op_bandwidth_su_;
782 primitives::ParameterSmoother op_tank_hp_su_;
783 primitives::ParameterSmoother op_diffusion_d1_;
784 primitives::ParameterSmoother op_diffusion_d2_;
785 primitives::ParameterSmoother op_output_scale_;
786 bool smoothers_configured_ =
false;
Schroeder allpass filter.
void set_predelay_su(float su)
Set pre-delay [0,1] → [0, max] samples linear (structural, 50ms settling)
float max_predelay_ms() const
Maximum pre-delay in milliseconds for this scale.
void set_damping_su(float su)
Set damping [0,1] → [0, 1.0] linear (structural, 50ms settling)
void process(float *left, float *right, uint16_t frames)
Process a stereo block in-place (float)
void set_decay_su(float su)
Set decay [0,1] → [0, 0.99] linear (structural, 50ms settling)
void set_bandwidth_su(float su)
void set_tank_hp_su(float su)
void set_mix_su(float su)
Set wet/dry mix [0,1] → [0, 1.0] linear (gain, 15ms settling)
static constexpr uint32_t BUFFER_SIZE
Total float buffer size required for init()
void set_output_scale_su(float su)
Set output tap scale [0,1] → [0.001, 1.0] linear (gain, 15ms)
void set_input_diffusion_su(float su)
void init(float *pool, uint32_t pool_size)
Initialize with a pre-allocated float buffer pool.
void set_width_su(float su)
Set stereo width [0,1] → [0, 1.0] linear (timbral, 20ms settling)
friend struct AlgoReverbTestAccess
uint32_t write_pos() const
Current write position (for external tap reads)
void set_feedback(float g)
Set feedback coefficient.
void init(float *buffer, uint32_t delay)
Initialize after default construction.
float process(float x)
Process a single sample.
void reset()
Zero the buffer and reset write position.
float read(float delay_samples) const
Read at fractional delay with linear interpolation.
void write(float sample)
Write a sample to the delay line.
float read_cubic(float delay_samples) const
Read at fractional delay with 4-point Hermite cubic interpolation.
void init(float *buffer, uint32_t max_delay)
Initialize after default construction.
uint32_t write_pos() const
Current write position (for external tap reads)
void reset()
Zero all samples in the buffer and reset write position.
void set_cutoff_su(float su)
void set_range(float min_hz, float max_hz)
void set_coefficient(float a)
Set filter coefficient directly.
void reset()
Reset filter state to zero.
void set_range(float min_hz, float max_hz)
Set frequency range for _su mapping (configuration, not a signal)
float process(float x)
Process a single sample.
void set_cutoff_su(float su)
Set cutoff frequency via signal unit.
void reset()
Reset to zero.
void set_time_ms(float ms, float rate_hz)
Compute coefficient from settling time in milliseconds.
float process(float target)
Process one sample toward target.
The numbers every layer reaches for.
Circular buffer delay line.
Fast analytical approximations (Audio Stack — Atoms)
Fixed-point constants and audio sample types.
constexpr float BASE_MOD_RATE_HZ
constexpr uint32_t BASE_TAP_R_DL1_A
constexpr float BASE_MOD_DEPTH
constexpr uint32_t BASE_TANK_L_AP2
constexpr uint32_t BASE_TANK_R_AP2
constexpr uint32_t BASE_INPUT_AP3
constexpr float DECAY_DIFFUSION1
constexpr uint32_t BASE_TAP_R_AP2_A
constexpr uint32_t BASE_TANK_L_AP1
constexpr float TAP_SUM_SCALE
constexpr float DECAY_DIFFUSION2_BASE
constexpr uint32_t BASE_TANK_L_DELAY2
constexpr uint32_t BASE_INPUT_AP1
constexpr float INPUT_DIFFUSION1
constexpr uint32_t BASE_TANK_R_DELAY2
constexpr uint32_t BASE_PREDELAY_MAX
constexpr uint32_t BASE_TAP_L_DL1_A
constexpr uint32_t BASE_INPUT_AP4
constexpr float DECAY_DIFFUSION2_SCALE
constexpr uint32_t BASE_TAP_L_DL2_B
constexpr uint32_t BASE_TANK_R_DELAY1
constexpr float INPUT_DIFFUSION2
constexpr uint32_t BASE_TAP_L_DL2_A
constexpr uint32_t BASE_TAP_L_DL1_B
constexpr uint32_t BASE_TAP_L_AP2_A
constexpr float DEFAULT_BANDWIDTH_HZ
constexpr uint32_t BASE_TAP_L_AP1_A
constexpr uint32_t BASE_TAP_R_DL2_B
constexpr uint32_t BASE_TANK_R_AP1
constexpr uint32_t BASE_TAP_R_AP1_A
constexpr uint32_t BASE_INPUT_AP2
constexpr uint32_t BASE_TANK_L_DELAY1
constexpr uint32_t BASE_TAP_R_DL1_B
constexpr uint32_t BASE_TAP_R_DL2_A
Single-pole IIR filters (LP and HP)
One-pole parameter smoother.