Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
de_esser.hpp
Go to the documentation of this file.
1#pragma once
2
5
9
10#include <algorithm>
11#include <cmath>
12#include <cstddef>
13#include <cstdint>
14#include <limits>
15#include <type_traits>
16
17namespace pulp::signal {
18
30template <typename SampleType = float> class DeEsserT {
31 static_assert(std::is_floating_point_v<SampleType>);
32
33 public:
38
39 struct Params {
40 SampleType threshold_db = SampleType{-24};
41 SampleType range_db = SampleType{12};
42 SampleType attack_ms = SampleType{1};
43 SampleType release_ms = SampleType{80};
44 SampleType detector_frequency_hz = SampleType{6500};
45 SampleType detector_q = SampleType{1.5};
46 SampleType split_frequency_hz = SampleType{4500};
47 };
48
51 bool prepare(SampleType sample_rate, const Params& params = {}) noexcept {
52 if (!valid_configuration(sample_rate, params))
53 return false;
54
55 sample_rate_ = sample_rate;
56 params_ = params;
57 configure_filters();
58 prepared_ = true;
59 healthy_ = true;
60 reset();
61 return true;
62 }
63
66 bool set_params(const Params& params) noexcept {
67 if (!prepared_ || !valid_configuration(sample_rate_, params))
68 return false;
69 params_ = params;
70 configure_filters();
71 reset();
72 return true;
73 }
74
75 const Params& params() const noexcept {
76 return params_;
77 }
78 SampleType sample_rate() const noexcept {
79 return prepared_ ? sample_rate_ : SampleType{0};
80 }
81 bool prepared() const noexcept {
82 return prepared_;
83 }
84 bool healthy() const noexcept {
85 return healthy_;
86 }
87 std::uint64_t fault_count() const noexcept {
88 return fault_count_;
89 }
90
93 void set_bypassed(bool bypassed) noexcept {
94 bypassed_ = bypassed;
95 }
96 bool bypassed() const noexcept {
97 return bypassed_;
98 }
99
100 void set_output_mode(OutputMode mode) noexcept {
101 output_mode_ = mode;
102 }
103 OutputMode output_mode() const noexcept {
104 return output_mode_;
105 }
106
107 SampleType process(SampleType input) noexcept {
108 if (!prepared_ || !std::isfinite(input))
109 return recover(input);
110
111 const SampleType detector_band = detector_filter_.process(input);
112 const SampleType detector_level = detector_envelope_.process(detector_band);
113 if (!std::isfinite(detector_band) || !std::isfinite(detector_level))
114 return recover(input);
115
116 detector_level_db_ = linear_to_db(detector_level);
117 reduction_db_ =
118 std::clamp(detector_level_db_ - params_.threshold_db, SampleType{0}, params_.range_db);
119
120 const auto split = crossover_.process(input);
121 const SampleType gain = std::pow(SampleType{10}, -reduction_db_ / SampleType{20});
122 const SampleType processed = split.low + split.high * gain;
123 if (!std::isfinite(processed))
124 return recover(input);
125
126 healthy_ = true;
127 if (bypassed_)
128 return input;
129 if (output_mode_ == OutputMode::detector_listen)
130 return detector_band;
131 return processed;
132 }
133
134 void process(const SampleType* input, SampleType* output, std::size_t num_samples) noexcept {
135 if (input == nullptr || output == nullptr)
136 return;
137 for (std::size_t i = 0; i < num_samples; ++i)
138 output[i] = process(input[i]);
139 }
140
141 void process(SampleType* buffer, std::size_t num_samples) noexcept {
142 process(buffer, buffer, num_samples);
143 }
144
146 SampleType gain_reduction_db() const noexcept {
147 return reduction_db_;
148 }
149 GainReduction gain_reduction() const noexcept {
150 return GainReduction::from_magnitude_db(static_cast<double>(reduction_db_));
151 }
152 SampleType detector_level_db() const noexcept {
153 return detector_level_db_;
154 }
155
158 void reset() noexcept {
159 detector_filter_.reset();
160 detector_envelope_.reset();
161 crossover_.reset();
162 detector_level_db_ = minimum_detector_db;
163 reduction_db_ = SampleType{0};
164 healthy_ = prepared_;
165 }
166
167 private:
168 static constexpr SampleType minimum_detector_db = SampleType{-160};
169
170 static bool valid_configuration(SampleType sample_rate, const Params& params) noexcept {
171 if (!(std::isfinite(sample_rate) && sample_rate >= SampleType{1000}))
172 return false;
173 const SampleType nyquist = sample_rate * SampleType{0.5};
174 return std::isfinite(params.threshold_db) && params.threshold_db >= SampleType{-120} &&
175 params.threshold_db <= SampleType{24} && std::isfinite(params.range_db) &&
176 params.range_db >= SampleType{0} && params.range_db <= SampleType{60} &&
177 std::isfinite(params.attack_ms) && params.attack_ms >= SampleType{0.01} &&
178 params.attack_ms <= SampleType{1000} && std::isfinite(params.release_ms) &&
179 params.release_ms >= SampleType{0.01} && params.release_ms <= SampleType{5000} &&
180 std::isfinite(params.detector_frequency_hz) &&
181 params.detector_frequency_hz >= SampleType{20} &&
182 params.detector_frequency_hz < nyquist * SampleType{0.98} &&
183 std::isfinite(params.detector_q) && params.detector_q >= SampleType{0.1} &&
184 params.detector_q <= SampleType{20} && std::isfinite(params.split_frequency_hz) &&
185 params.split_frequency_hz >= SampleType{20} &&
186 params.split_frequency_hz < nyquist * SampleType{0.98};
187 }
188
189 static SampleType linear_to_db(SampleType value) noexcept {
190 const SampleType bounded = std::max(value, std::numeric_limits<SampleType>::min());
191 const SampleType db = SampleType{20} * std::log10(bounded);
192 return std::max(minimum_detector_db, db);
193 }
194
195 void configure_filters() noexcept {
196 detector_filter_.set_coefficients(BiquadT<SampleType>::Type::bandpass,
197 params_.detector_frequency_hz, params_.detector_q,
198 sample_rate_);
199 detector_envelope_.prepare(sample_rate_);
200 detector_envelope_.set_mode(EnvelopeFollowerT<SampleType>::Mode::peak);
201 detector_envelope_.set_attack_ms(params_.attack_ms);
202 detector_envelope_.set_release_ms(params_.release_ms);
203 crossover_.set_frequency_precise(params_.split_frequency_hz, sample_rate_);
204 }
205
206 SampleType recover(SampleType input) noexcept {
207 if (prepared_)
208 reset();
209 healthy_ = false;
210 ++fault_count_;
211 return std::isfinite(input) ? input : SampleType{0};
212 }
213
214 Params params_{};
215 SampleType sample_rate_ = SampleType{0};
216 BiquadT<SampleType> detector_filter_{};
217 EnvelopeFollowerT<SampleType> detector_envelope_{};
218 LinkwitzRileyT<SampleType> crossover_{};
219 SampleType detector_level_db_ = minimum_detector_db;
220 SampleType reduction_db_ = SampleType{0};
221 OutputMode output_mode_ = OutputMode::normal;
222 bool prepared_ = false;
223 bool healthy_ = false;
224 bool bypassed_ = false;
225 std::uint64_t fault_count_ = 0;
226};
227
230
231} // namespace pulp::signal
@ bandpass
Definition biquad.hpp:58
A bounded mono de-esser primitive.
Definition de_esser.hpp:30
SampleType detector_level_db() const noexcept
Definition de_esser.hpp:152
SampleType process(SampleType input) noexcept
Definition de_esser.hpp:107
OutputMode output_mode() const noexcept
Definition de_esser.hpp:103
OutputMode
Definition de_esser.hpp:34
@ detector_listen
Definition de_esser.hpp:36
@ normal
Definition de_esser.hpp:35
bool prepare(SampleType sample_rate, const Params &params={}) noexcept
Establish sample rate and coefficients.
Definition de_esser.hpp:51
void process(SampleType *buffer, std::size_t num_samples) noexcept
Definition de_esser.hpp:141
bool set_params(const Params &params) noexcept
Atomically adopt a new valid configuration.
Definition de_esser.hpp:66
void process(const SampleType *input, SampleType *output, std::size_t num_samples) noexcept
Definition de_esser.hpp:134
SampleType gain_reduction_db() const noexcept
Positive attenuation magnitude, suitable for direct display.
Definition de_esser.hpp:146
bool healthy() const noexcept
Definition de_esser.hpp:84
bool bypassed() const noexcept
Definition de_esser.hpp:96
bool prepared() const noexcept
Definition de_esser.hpp:81
std::uint64_t fault_count() const noexcept
Definition de_esser.hpp:87
float sample_rate() const noexcept
Definition de_esser.hpp:78
void set_bypassed(bool bypassed) noexcept
Bypass is sample-exact while the detector and split continue tracking, so leaving bypass does not res...
Definition de_esser.hpp:93
const Params & params() const noexcept
Definition de_esser.hpp:75
void set_output_mode(OutputMode mode) noexcept
Definition de_esser.hpp:100
GainReduction gain_reduction() const noexcept
Definition de_esser.hpp:149
void reset() noexcept
Clear signal history while retaining configuration, bypass, and output mode.
Definition de_esser.hpp:158
Canonical gain-reduction meter value.
Definition dynamics_contract.hpp:27
static GainReduction from_magnitude_db(double magnitude_db) noexcept
Adapt an attenuation magnitude, where reduction is positive dB.
Definition dynamics_contract.hpp:37
Shared envelope and gain-reduction vocabulary for dynamics processors.
Definition additive_bank.hpp:218
DeEsserT< double > DeEsser64
Definition de_esser.hpp:229
@ buffer
A borrowed static buffer with a traversable playhead.
Definition granular.hpp:110
@ bounded
Definition fm_operator_engine.hpp:35
@ i
Definition formant_filter_bank.hpp:20
DeEsserT< float > DeEsser
Definition de_esser.hpp:228
Definition de_esser.hpp:39
SampleType range_db
Definition de_esser.hpp:41
SampleType attack_ms
Definition de_esser.hpp:42
SampleType detector_frequency_hz
Definition de_esser.hpp:44
SampleType release_ms
Definition de_esser.hpp:43
SampleType split_frequency_hz
Definition de_esser.hpp:46
SampleType threshold_db
Definition de_esser.hpp:40
SampleType detector_q
Definition de_esser.hpp:45