27template <
typename SampleType =
float>
class ExpanderT {
34 SampleType
ratio = SampleType{2};
45 static constexpr SampleType
kMinRatio = SampleType{1};
46 static constexpr SampleType
kMaxRatio = SampleType{20};
55 apply_config(config_);
66 const bool detector_topology_changed =
67 next.detector != config_.detector || next.stereo_link != config_.stereo_link;
70 apply_config(config_);
71 if (detector_topology_changed)
79 const Config&
config)
noexcept {
80 if (!finite(level_db) || !valid(
config))
82 const SampleType x = level_db -
config.threshold_db;
83 const SampleType slope =
config.ratio - SampleType{1};
86 if (slope > SampleType{0} && x <= -
config.range_db / slope)
88 if (!(
config.knee_db > SampleType{0})) {
89 gain = x < SampleType{0} ? slope * x : SampleType{0};
91 const SampleType half =
config.knee_db / SampleType{2};
94 }
else if (x < half) {
95 const SampleType distance = half - x;
96 gain = -slope * distance * distance / (SampleType{2} *
config.knee_db);
99 return std::max(-
config.range_db, gain);
102 if (slope > SampleType{0} && x >=
config.range_db / slope)
104 if (!(
config.knee_db > SampleType{0})) {
105 gain = x > SampleType{0} ? slope * x : SampleType{0};
107 const SampleType half =
config.knee_db / SampleType{2};
110 }
else if (x > -half) {
111 const SampleType distance = x + half;
112 gain = slope * distance * distance / (SampleType{2} *
config.knee_db);
115 return std::min(
config.range_db, gain);
119 if (!prepared_ || !finite(
left) || !finite(
right)) {
121 return {SampleType{0}, SampleType{0}};
124 const auto envelope = process_detector(
left,
right);
125 std::array<SampleType, 2> output{};
126 for (std::size_t channel = 0; channel < 2; ++channel) {
127 if (envelope[channel] > SampleType{0}) {
128 const SampleType level = SampleType{20} * std::log10(envelope[channel]);
131 current_gain_db_[channel] =
136 output[channel] = bounded_multiply(channel == 0 ?
left :
right,
137 db_to_linear(current_gain_db_[channel]));
145 if (num_samples <= 0 ||
left ==
nullptr ||
right ==
nullptr)
147 for (
int i = 0;
i < num_samples; ++
i) {
162 detector_state_ = {};
163 current_gain_db_ = {};
166 [[nodiscard]]
const Config&
config() const noexcept {
182 return current_gain_db_;
190 static bool finite(SampleType value)
noexcept {
191 return std::isfinite(
static_cast<double>(value));
194 static bool valid(
const Config&
config)
noexcept {
202 config.attack_ms >= SampleType{0.01} &&
config.attack_ms <= SampleType{2000} &&
203 finite(
config.release_ms) &&
config.release_ms >= SampleType{0.01} &&
204 config.release_ms <= SampleType{10000} &&
205 (
config.detector == DetectorMode::peak ||
config.detector == DetectorMode::rms) &&
210 static SampleType db_to_linear(SampleType db)
noexcept {
211 return std::pow(SampleType{10}, db / SampleType{20});
214 static SampleType bounded_multiply(SampleType input, SampleType gain)
noexcept {
215 const SampleType limit = std::numeric_limits<SampleType>::max();
216 if (gain > SampleType{1} && std::abs(input) > limit / gain)
217 return std::copysign(limit, input);
222 attack_coefficient_ =
224 release_coefficient_ =
228 std::array<SampleType, 2> process_detector(SampleType
left, SampleType
right)
noexcept {
229 std::array<SampleType, 2> magnitudes{std::abs(
left), std::abs(
right)};
231 const SampleType
shared = std::max(magnitudes[0], magnitudes[1]);
235 std::array<SampleType, 2>
envelope{};
236 for (std::size_t channel = 0; channel < 2; ++channel) {
237 SampleType detector_value = magnitudes[channel];
238 if (config_.detector == DetectorMode::rms) {
239 const SampleType square_limit = std::sqrt(std::numeric_limits<SampleType>::max());
240 detector_value = magnitudes[channel] > square_limit
241 ? std::numeric_limits<SampleType>::max()
242 : magnitudes[channel] * magnitudes[channel];
244 const SampleType coefficient = detector_value > detector_state_[channel]
245 ? attack_coefficient_
246 : release_coefficient_;
247 detector_state_[channel] += coefficient * (detector_value - detector_state_[channel]);
248 if (!finite(detector_state_[channel]) || detector_state_[channel] < SampleType{0}) {
252 if (detector_state_[channel] < std::numeric_limits<SampleType>::min())
253 detector_state_[channel] = SampleType{0};
254 envelope[channel] = config_.detector == DetectorMode::rms
255 ? std::sqrt(detector_state_[channel])
256 : detector_state_[channel];
262 std::array<SampleType, 2> detector_state_{};
263 std::array<SampleType, 2> current_gain_db_{};
264 SampleType attack_coefficient_ = SampleType{1};
265 SampleType release_coefficient_ = SampleType{1};
266 SampleType sample_rate_ = SampleType{0};
267 bool prepared_ =
false;
268 bool bypassed_ =
false;
static SampleType coefficient_for_time_ms(SampleType ms, SampleType sample_rate)
Definition dynamics_contract.hpp:80
typename Base::Mode Mode
Definition dynamics_contract.hpp:76
Stereo upward/downward expander with a continuous bounded gain law.
Definition expander.hpp:27
typename EnvelopeFollowerT< SampleType >::Mode DetectorMode
Definition expander.hpp:29
static constexpr float kMaxThresholdDb
Definition expander.hpp:44
static constexpr float kMaxRangeDb
Definition expander.hpp:47
void reset() noexcept
Definition expander.hpp:161
int latency_samples() const noexcept
Definition expander.hpp:175
bool bypassed() const noexcept
Definition expander.hpp:157
void set_bypassed(bool bypassed) noexcept
Definition expander.hpp:154
ExpanderStatus prepare(SampleType sample_rate) noexcept
Definition expander.hpp:51
static constexpr float kMaxKneeDb
Definition expander.hpp:48
ExpanderStatus configure(const Config &next) noexcept
Adopt a complete valid configuration.
Definition expander.hpp:63
static SampleType gain_computer_db(SampleType level_db, const Config &config) noexcept
Pure memoryless curve used by the processor after envelope detection.
Definition expander.hpp:78
std::array< GainReduction, 2 > gain_reduction() const noexcept
Definition expander.hpp:184
bool prepared() const noexcept
Definition expander.hpp:169
float sample_rate() const noexcept
Definition expander.hpp:172
std::array< SampleType, 2 > current_gain_db() const noexcept
Definition expander.hpp:181
const Config & config() const noexcept
Definition expander.hpp:166
static constexpr float kMaxRatio
Definition expander.hpp:46
static constexpr float kMinThresholdDb
Definition expander.hpp:43
void process(SampleType *left, SampleType *right, int num_samples) noexcept
Definition expander.hpp:144
std::array< SampleType, 2 > process(SampleType left, SampleType right) noexcept
Definition expander.hpp:118
static constexpr float kMaxSampleRate
Definition expander.hpp:49
int tail_samples() const noexcept
Definition expander.hpp:178
static constexpr float kMinRatio
Definition expander.hpp:45
static GainReduction from_signed_db(double signed_db) noexcept
Adapt a signed gain value, where attenuation is negative dB.
Definition dynamics_contract.hpp:32
Shared envelope and gain-reduction vocabulary for dynamics processors.
@ shared
Definition device.hpp:32
Definition additive_bank.hpp:218
@ right
Definition multi_channel_meter.hpp:67
@ left
Definition multi_channel_meter.hpp:66
ExpanderT< double > Expander64
Definition expander.hpp:272
ExpanderT< float > Expander
Definition expander.hpp:271
ExpansionMode
Definition expander.hpp:16
@ upward
Definition expander.hpp:16
@ downward
Definition expander.hpp:16
@ invalid_config
Definition auto_ducked_send.hpp:18
@ invalid_sample_rate
Definition auto_ducked_send.hpp:18
@ ready
Definition auto_ducked_send.hpp:18
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
ExpanderStatus
Definition expander.hpp:17
@ invalid_config
Definition expander.hpp:17
@ invalid_sample_rate
Definition expander.hpp:17
@ ready
Definition expander.hpp:17
DynamicsStereoLink
Shared policy for dynamics processors with two detector channels.
Definition dynamics_contract.hpp:15
@ peak_linked
Definition dynamics_contract.hpp:15
@ independent
Definition dynamics_contract.hpp:15
Definition expander.hpp:31
SampleType attack_ms
Definition expander.hpp:37
ExpansionMode mode
Definition expander.hpp:32
SampleType threshold_db
Definition expander.hpp:33
SampleType release_ms
Definition expander.hpp:38
DynamicsStereoLink stereo_link
Definition expander.hpp:40
SampleType ratio
Definition expander.hpp:34
DetectorMode detector
Definition expander.hpp:39
SampleType knee_db
Definition expander.hpp:36
SampleType range_db
Definition expander.hpp:35