30template <
typename SampleType =
float>
class DeEsserT {
31 static_assert(std::is_floating_point_v<SampleType>);
67 if (!prepared_ || !valid_configuration(sample_rate_,
params))
75 const Params&
params() const noexcept {
79 return prepared_ ? sample_rate_ : SampleType{0};
107 SampleType
process(SampleType input)
noexcept {
108 if (!prepared_ || !std::isfinite(input))
109 return recover(input);
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);
116 detector_level_db_ = linear_to_db(detector_level);
118 std::clamp(detector_level_db_ - params_.threshold_db, SampleType{0}, params_.range_db);
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);
130 return detector_band;
134 void process(
const SampleType* input, SampleType* output, std::size_t num_samples)
noexcept {
135 if (input ==
nullptr || output ==
nullptr)
137 for (std::size_t
i = 0;
i < num_samples; ++
i)
147 return reduction_db_;
153 return detector_level_db_;
159 detector_filter_.reset();
160 detector_envelope_.reset();
162 detector_level_db_ = minimum_detector_db;
163 reduction_db_ = SampleType{0};
164 healthy_ = prepared_;
168 static constexpr SampleType minimum_detector_db = SampleType{-160};
170 static bool valid_configuration(SampleType
sample_rate,
const Params&
params)
noexcept {
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};
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);
195 void configure_filters() noexcept {
197 params_.detector_frequency_hz, params_.detector_q,
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_);
206 SampleType recover(SampleType input)
noexcept {
211 return std::isfinite(input) ? input : SampleType{0};
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};
222 bool prepared_ =
false;
223 bool healthy_ =
false;
224 bool bypassed_ =
false;
225 std::uint64_t fault_count_ = 0;
@ 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 ¶ms={}) 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 ¶ms) 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