Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
decay_envelope.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <algorithm>
6#include <cmath>
7
8namespace pulp::signal {
9
15inline double exp_decay_coefficient(double ms, double sample_rate) {
16 const double samples = 0.001 * std::max(ms, 1e-6) * sample_rate;
17 return std::exp(-1.0 / std::max(samples, 1e-9));
18}
19
27inline double t60_decay_coefficient(double ms, double sample_rate) {
28 const double samples = 0.001 * std::max(ms, 1e-6) * sample_rate;
29 return std::pow(10.0, -3.0 / std::max(samples, 1e-9));
30}
31
51template <typename SampleType = float>
53public:
56 static constexpr double silence_floor = 1e-5;
57
61 DecayEnvelopeT() { update(); }
62
63 void set_sample_rate(double sr) {
64 sample_rate_ = sr > 0.0 ? sr : sample_rate_;
65 update();
66 }
67
70 void set_attack_ms(double ms) {
71 attack_ms_ = std::max(ms, 0.0);
72 update();
73 }
74
76 void set_hold_ms(double ms) {
77 hold_ms_ = std::max(ms, 0.0);
78 update();
79 }
80
83 decay_ms_ = std::max(ms, 0.0);
84 decay_is_t60_ = false;
85 update();
86 }
87
89 void set_decay_t60_ms(double ms) {
90 decay_ms_ = std::max(ms, 0.0);
91 decay_is_t60_ = true;
92 update();
93 }
94
95 double attack_ms() const { return attack_ms_; }
96 double hold_ms() const { return hold_ms_; }
97 double decay_ms() const { return decay_ms_; }
98
101 void trigger(SampleType peak = SampleType{1}) {
102 peak_ = static_cast<double>(peak);
103 level_ = 0.0;
104 attack_phase_ = 0.0;
105 hold_remaining_ = hold_samples_;
106 stage_ = attack_samples_ > 0.0 ? Stage::attack : Stage::hold;
107 if (stage_ == Stage::hold) level_ = 1.0;
108 active_ = peak_ != 0.0;
109 }
110
114 void reset() {
115 level_ = 0.0;
116 attack_phase_ = 0.0;
117 hold_remaining_ = 0;
118 stage_ = Stage::idle;
119 active_ = false;
120 }
121
122 bool is_active() const { return active_; }
123
125 SampleType process() {
126 if (!active_) return SampleType{0};
127
128 switch (stage_) {
129 case Stage::attack:
130 attack_phase_ += attack_step_;
131 if (attack_phase_ >= 1.0) {
132 attack_phase_ = 1.0;
133 stage_ = Stage::hold;
134 }
135 level_ = attack_phase_;
136 break;
137
138 case Stage::hold:
139 level_ = 1.0;
140 if (hold_remaining_ > 0) {
141 --hold_remaining_;
142 } else {
143 stage_ = Stage::decay;
144 }
145 break;
146
147 case Stage::decay:
148 level_ = snap_to_zero(level_ * decay_coef_);
149 if (level_ < silence_floor) {
150 level_ = 0.0;
151 active_ = false;
152 stage_ = Stage::idle;
153 }
154 break;
155
156 case Stage::idle:
157 level_ = 0.0;
158 break;
159 }
160
161 return static_cast<SampleType>(level_ * peak_);
162 }
163
168 SampleType value() const { return static_cast<SampleType>(level_ * peak_); }
169
170private:
171 enum class Stage { idle, attack, hold, decay };
172
173 void update() {
174 attack_samples_ = 0.001 * attack_ms_ * sample_rate_;
175 attack_step_ = attack_samples_ > 0.0 ? 1.0 / attack_samples_ : 1.0;
176 hold_samples_ = static_cast<long>(0.001 * hold_ms_ * sample_rate_);
177 decay_coef_ = decay_is_t60_ ? t60_decay_coefficient(decay_ms_, sample_rate_)
178 : exp_decay_coefficient(decay_ms_, sample_rate_);
179 }
180
181 double sample_rate_ = 44100.0;
182 double attack_ms_ = 0.5;
183 double hold_ms_ = 0.0;
184 double decay_ms_ = 200.0;
185 bool decay_is_t60_ = false;
186
187 double attack_samples_ = 0.0;
188 double attack_step_ = 1.0;
189 long hold_samples_ = 0;
190 double decay_coef_ = 0.0;
191
192 Stage stage_ = Stage::idle;
193 double level_ = 0.0;
194 double peak_ = 1.0;
195 double attack_phase_ = 0.0;
196 long hold_remaining_ = 0;
197 bool active_ = false;
198};
199
202
203} // namespace pulp::signal
A one-shot percussion envelope: linear attack ramp, optional hold, then exponential decay.
Definition decay_envelope.hpp:52
double hold_ms() const
Definition decay_envelope.hpp:96
SampleType value() const
Current level without advancing, scaled by the trigger peak.
Definition decay_envelope.hpp:168
DecayEnvelopeT()
A default-constructed envelope has usable coefficients at 44.1 kHz, so triggering one before any sett...
Definition decay_envelope.hpp:61
void set_decay_t60_ms(double ms)
Decay expressed as a T60 (-60 dB after ms).
Definition decay_envelope.hpp:89
void set_decay_time_constant_ms(double ms)
Decay expressed as an RC time constant (1/e after ms).
Definition decay_envelope.hpp:82
void set_attack_ms(double ms)
Length of the anti-click ramp.
Definition decay_envelope.hpp:70
double attack_ms() const
Definition decay_envelope.hpp:95
bool is_active() const
Definition decay_envelope.hpp:122
double decay_ms() const
Definition decay_envelope.hpp:97
void set_hold_ms(double ms)
Time at full scale between the attack and the decay.
Definition decay_envelope.hpp:76
void set_sample_rate(double sr)
Definition decay_envelope.hpp:63
static constexpr double silence_floor
Definition decay_envelope.hpp:56
SampleType process()
Next envelope sample in [0, peak].
Definition decay_envelope.hpp:125
void trigger(SampleType peak=SampleType{1})
Starts the envelope from silence.
Definition decay_envelope.hpp:101
void reset()
Silences the envelope immediately without a release ramp.
Definition decay_envelope.hpp:114
Definition additive_bank.hpp:218
double t60_decay_coefficient(double ms, double sample_rate)
Per-sample multiplier for a decay that reaches -60 dB after ms.
Definition decay_envelope.hpp:27
@ peak
Sample peak. Literal, fastest-responding.
Definition feedforward_compressor.hpp:119
double exp_decay_coefficient(double ms, double sample_rate)
Per-sample multiplier for an exponential decay with time constant ms.
Definition decay_envelope.hpp:15
@ decay
Definition envelope.hpp:60
@ hold
Definition envelope.hpp:59
@ attack
Definition envelope.hpp:58
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
@ e
Definition formant_filter_bank.hpp:20
DecayEnvelopeT< float > DecayEnvelope
Definition decay_envelope.hpp:200
DecayEnvelopeT< double > DecayEnvelope64
Definition decay_envelope.hpp:201