Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
reverb.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
6#include <array>
7#include <cmath>
8
9namespace pulp::signal {
10
11// Feedback Delay Network (FDN) reverb
12// 4-channel FDN with Hadamard mixing matrix.
13//
14// RT contract: `prepare()` allocates delay-line storage. After preparation,
15// setters, `process()`, and `reset()` allocate no memory.
16template <typename SampleType = float>
17class ReverbT {
18public:
19 void prepare(SampleType sample_rate) {
20 sample_rate_ = sample_rate;
21 // Prime delay lengths for maximum density (in samples)
22 int delays[] = {1087, 1283, 1481, 1693};
23 for (int i = 0; i < 4; ++i) {
24 lines_[i].prepare(
25 static_cast<int>(delays[i] * sample_rate / SampleType{44100.0f}) + 1);
26 delay_samples_[i] =
27 static_cast<int>(delays[i] * sample_rate / SampleType{44100.0f});
28 }
29 }
30
31 void set_decay(SampleType seconds) { decay_ = seconds; }
32 void set_damping(SampleType d) {
33 damping_ = std::clamp(d, SampleType{0.0f}, SampleType{0.99f});
34 }
35 void set_mix(SampleType m) { mix_ = m; }
36
37 struct StereoSample { SampleType left, right; };
38
39 StereoSample process(SampleType input) {
40 // Read from delay lines
41 SampleType s[4];
42 for (int i = 0; i < 4; ++i)
43 s[i] = lines_[i].read(delay_samples_[i]);
44
45 // Hadamard mixing (unitary, energy preserving)
46 SampleType h[4];
47 h[0] = SampleType{0.5f} * (s[0] + s[1] + s[2] + s[3]);
48 h[1] = SampleType{0.5f} * (s[0] - s[1] + s[2] - s[3]);
49 h[2] = SampleType{0.5f} * (s[0] + s[1] - s[2] - s[3]);
50 h[3] = SampleType{0.5f} * (s[0] - s[1] - s[2] + s[3]);
51
52 // Feedback with decay and damping
53 SampleType feedback = decay_feedback();
54 for (int i = 0; i < 4; ++i) {
55 // One-pole lowpass for damping. Snap the damping state so the
56 // (long) reverb tail flushes to zero instead of recirculating
57 // denormals through the feedback loop on FTZ-less targets (wasm,
58 // MSVC/ARM64). No-op above 1e-15.
59 lp_state_[i] = snap_to_zero(
60 lp_state_[i] + (h[i] - lp_state_[i]) * (SampleType{1.0f} - damping_));
61 lines_[i].push(input + lp_state_[i] * feedback);
62 }
63
64 // Stereo output (pick pairs)
65 SampleType wet_l = (s[0] + s[2]) * SampleType{0.5f};
66 SampleType wet_r = (s[1] + s[3]) * SampleType{0.5f};
67
68 SampleType dry = SampleType{1.0f} - mix_;
69 return {input * dry + wet_l * mix_,
70 input * dry + wet_r * mix_};
71 }
72
73 void reset() {
74 for (auto& l : lines_) l.reset();
75 lp_state_.fill(0);
76 }
77
78private:
79 SampleType sample_rate_ = SampleType{44100.0f};
80 SampleType decay_ = SampleType{2.0f};
81 SampleType damping_ = SampleType{0.3f};
82 SampleType mix_ = SampleType{0.3f};
83
84 std::array<DelayLineT<SampleType>, 4> lines_;
85 std::array<int, 4> delay_samples_{};
86 std::array<SampleType, 4> lp_state_{};
87
88 SampleType decay_feedback() const {
89 // Average delay time
90 SampleType avg_delay = 0;
91 for (int i = 0; i < 4; ++i)
92 avg_delay += delay_samples_[i];
93 avg_delay /= (SampleType{4.0f} * sample_rate_);
94
95 // RT60-based feedback
96 if (decay_ <= 0 || avg_delay <= 0) return 0;
97 return std::pow(SampleType{10.0f}, SampleType{-3.0f} * avg_delay / decay_);
98 }
99};
100
103
104} // namespace pulp::signal
Definition reverb.hpp:17
void prepare(SampleType sample_rate)
Definition reverb.hpp:19
void set_mix(SampleType m)
Definition reverb.hpp:35
void set_decay(SampleType seconds)
Definition reverb.hpp:31
StereoSample process(SampleType input)
Definition reverb.hpp:39
void reset()
Definition reverb.hpp:73
void set_damping(SampleType d)
Definition reverb.hpp:32
Definition additive_bank.hpp:218
@ feedback
Definition comb_filter.hpp:18
ReverbT< float > Reverb
Definition reverb.hpp:101
ReverbT< double > Reverb64
Definition reverb.hpp:102
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
@ i
Definition formant_filter_bank.hpp:20
Definition reverb.hpp:37
SampleType left
Definition reverb.hpp:37
SampleType right
Definition reverb.hpp:37