Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
ballistics_filter.hpp
Go to the documentation of this file.
1#pragma once
2
5
7
8#include <algorithm>
9#include <cmath>
10#include <limits>
11
12namespace pulp::signal {
13
31template <typename SampleType = float> class BallisticsFilterT {
32 public:
33 enum class Mode { peak, rms };
35
36 BallisticsFilterT() = default;
37
38 explicit BallisticsFilterT(TimeConvention time_convention)
39 : time_convention_(time_convention) {}
40
41 void prepare(SampleType sample_rate) {
42 sample_rate_ = sample_rate;
43 update_coefficients();
44 }
45
46 void set_attack_ms(SampleType ms) {
47 if (!std::isfinite(static_cast<double>(ms)))
48 return;
49 attack_ms_ = std::max(SampleType{0.01f}, ms);
50 update_coefficients();
51 }
52
53 void set_release_ms(SampleType ms) {
54 if (!std::isfinite(static_cast<double>(ms)))
55 return;
56 release_ms_ = std::max(SampleType{0.01f}, ms);
57 update_coefficients();
58 }
59
60 void set_mode(Mode m) {
61 mode_ = m;
62 }
63
64 SampleType process(SampleType input) {
65 if (!std::isfinite(static_cast<double>(input))) {
66 reset();
67 return SampleType{0};
68 }
69 if (!std::isfinite(static_cast<double>(state_)) || state_ < SampleType{0})
70 reset();
71
72 const SampleType magnitude = std::abs(input);
73 SampleType value = magnitude;
74 if (mode_ == Mode::rms) {
75 const SampleType square_limit = std::sqrt(std::numeric_limits<SampleType>::max());
76 value = magnitude > square_limit ? std::numeric_limits<SampleType>::max()
77 : magnitude * magnitude;
78 }
79 SampleType coeff = (value > state_) ? attack_coeff_ : release_coeff_;
80 // Snap the envelope state where subnormals are slow, so a long release
81 // into silence does not stall in the denormal range (see
82 // snap_recursion_state()). No-op above 1e-15.
83 state_ = snap_recursion_state(state_ + coeff * (value - state_));
84 if (!std::isfinite(static_cast<double>(state_)) || state_ < SampleType{0}) {
85 reset();
86 return SampleType{0};
87 }
88 return (mode_ == Mode::rms) ? std::sqrt(state_) : state_;
89 }
90
91 void process(const SampleType* input, SampleType* output, int num_samples) {
92 for (int i = 0; i < num_samples; ++i) {
93 output[i] = process(input[i]);
94 }
95 }
96
97 SampleType current() const {
98 if (!std::isfinite(static_cast<double>(state_)) || state_ < SampleType{0})
99 return SampleType{0};
100 return (mode_ == Mode::rms) ? std::sqrt(state_) : state_;
101 }
102
107 SampleType current_db(SampleType floor_db = SampleType{-160}) const {
108 if (!std::isfinite(static_cast<double>(floor_db)) || floor_db > SampleType{0})
109 floor_db = SampleType{-160};
110 const SampleType value = current();
111 if (!(value > SampleType{0}))
112 return floor_db;
113 const SampleType db = SampleType{20} * std::log10(value);
114 return std::isfinite(static_cast<double>(db)) ? std::max(floor_db, db) : floor_db;
115 }
116
117 SampleType attack_ms() const noexcept {
118 return attack_ms_;
119 }
120 SampleType release_ms() const noexcept {
121 return release_ms_;
122 }
123 SampleType attack_coefficient() const noexcept {
124 return attack_coeff_;
125 }
126 SampleType release_coefficient() const noexcept {
127 return release_coeff_;
128 }
129
135 static SampleType coefficient_for_time_ms(SampleType ms, SampleType sample_rate) {
136 if (!std::isfinite(static_cast<double>(ms)) ||
137 !std::isfinite(static_cast<double>(sample_rate)) || !(sample_rate > SampleType{0}))
138 return SampleType{0};
139 if (!(ms > SampleType{0.01f}))
140 return SampleType{1};
141 return SampleType{1} -
142 std::exp(SampleType{-2.2f} / (ms * SampleType{0.001f} * sample_rate));
143 }
144
146 static SampleType exact_coefficient_for_time_ms(SampleType ms, SampleType sample_rate) {
147 if (!std::isfinite(static_cast<double>(ms)) ||
148 !std::isfinite(static_cast<double>(sample_rate)) || !(sample_rate > SampleType{0}))
149 return SampleType{0};
150 if (!(ms > SampleType{0.01f}))
151 return SampleType{1};
152 return SampleType{1} -
153 std::exp(-std::log(SampleType{9}) / (ms * SampleType{0.001} * sample_rate));
154 }
155
156 void reset() {
157 state_ = SampleType{0.0f};
158 }
159
160 protected:
161 SampleType detector_state() const noexcept {
162 return state_;
163 }
164
165 void synchronize_detector_state(SampleType state) noexcept {
166 state_ = std::isfinite(static_cast<double>(state)) && state >= SampleType{0}
167 ? state
168 : SampleType{0};
169 }
170
171 private:
172 SampleType sample_rate_ = SampleType{44100.0f};
173 SampleType attack_ms_ = SampleType{1.0f};
174 SampleType release_ms_ = SampleType{100.0f};
175 SampleType attack_coeff_ = SampleType{0.0f};
176 SampleType release_coeff_ = SampleType{0.0f};
177 SampleType state_ = SampleType{0.0f};
178 Mode mode_ = Mode::peak;
180
181 void update_coefficients() {
182 if (sample_rate_ <= 0)
183 return;
184 attack_coeff_ = time_constant(attack_ms_);
185 release_coeff_ = time_constant(release_ms_);
186 }
187
188 SampleType time_constant(SampleType ms) const {
189 return time_convention_ == TimeConvention::exact_10_to_90
190 ? exact_coefficient_for_time_ms(ms, sample_rate_)
191 : coefficient_for_time_ms(ms, sample_rate_);
192 }
193};
194
197
198} // namespace pulp::signal
Envelope follower with configurable attack and release times.
Definition ballistics_filter.hpp:31
SampleType current() const
Definition ballistics_filter.hpp:97
SampleType release_coefficient() const noexcept
Definition ballistics_filter.hpp:126
static SampleType exact_coefficient_for_time_ms(SampleType ms, SampleType sample_rate)
Pure coefficient for an exact 10-to-90-percent state-domain interval.
Definition ballistics_filter.hpp:146
void set_mode(Mode m)
Definition ballistics_filter.hpp:60
void set_attack_ms(SampleType ms)
Definition ballistics_filter.hpp:46
SampleType current_db(SampleType floor_db=SampleType{-160}) const
Current envelope in dBFS, floored at floor_db.
Definition ballistics_filter.hpp:107
void prepare(SampleType sample_rate)
Definition ballistics_filter.hpp:41
void set_release_ms(SampleType ms)
Definition ballistics_filter.hpp:53
TimeConvention
Definition ballistics_filter.hpp:34
@ exact_10_to_90
Definition ballistics_filter.hpp:34
@ legacy_nominal
Definition ballistics_filter.hpp:34
Mode
Definition ballistics_filter.hpp:33
@ peak
Definition ballistics_filter.hpp:33
@ rms
Definition ballistics_filter.hpp:33
SampleType attack_coefficient() const noexcept
Definition ballistics_filter.hpp:123
BallisticsFilterT(TimeConvention time_convention)
Definition ballistics_filter.hpp:38
static SampleType coefficient_for_time_ms(SampleType ms, SampleType sample_rate)
Legacy nominal coefficient retained for bit-identical BallisticsFilter output.
Definition ballistics_filter.hpp:135
void process(const SampleType *input, SampleType *output, int num_samples)
Definition ballistics_filter.hpp:91
SampleType attack_ms() const noexcept
Definition ballistics_filter.hpp:117
void reset()
Definition ballistics_filter.hpp:156
SampleType release_ms() const noexcept
Definition ballistics_filter.hpp:120
SampleType detector_state() const noexcept
Definition ballistics_filter.hpp:161
void synchronize_detector_state(SampleType state) noexcept
Definition ballistics_filter.hpp:165
SampleType process(SampleType input)
Definition ballistics_filter.hpp:64
Definition additive_bank.hpp:218
BallisticsFilterT< float > BallisticsFilter
Definition ballistics_filter.hpp:195
BallisticsFilterT< double > BallisticsFilter64
Definition ballistics_filter.hpp:196
@ i
Definition formant_filter_bank.hpp:20
T snap_recursion_state(T x)
Snap a per-sample recursion's state: snap_to_zero() where subnormals are slow, the identity where kSu...
Definition denormal.hpp:84
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48