36 static_assert(std::is_floating_point_v<SampleType>);
61 apply_config(config_);
72 const bool detector_topology_changed =
73 next.detector != config_.detector || next.stereo_link != config_.stereo_link;
76 apply_config(config_);
77 if (detector_topology_changed)
80 current_gain_db_ = {config_.send_gain_db, config_.send_gain_db};
87 const Config&
config)
noexcept {
88 if (!finite(detector_level_db) || !valid(
config))
90 const SampleType over = std::max(SampleType{0}, detector_level_db -
config.threshold_db);
91 return config.send_gain_db - std::min(
config.range_db, over);
98 [[nodiscard]] std::array<SampleType, 2>
process(SampleType send_left, SampleType send_right,
99 SampleType detector_left,
100 SampleType detector_right)
noexcept {
101 if (!prepared_ || !finite(send_left) || !finite(send_right)) {
103 return {SampleType{0}, SampleType{0}};
105 if (!finite(detector_left) || !finite(detector_right)) {
111 return {send_left, send_right};
112 if (config_.range_db == SampleType{0})
113 return apply_current_gain(send_left, send_right);
114 return {SampleType{0}, SampleType{0}};
117 const auto envelope = detector_.process(detector_left, detector_right);
118 std::array<SampleType, 2> output{};
119 for (std::size_t channel = 0; channel < 2; ++channel) {
120 const SampleType detector_db = envelope[channel] > SampleType{0}
121 ? SampleType{20} * std::log10(envelope[channel])
124 duck_reduction_db_[channel] =
125 std::max(SampleType{0}, config_.send_gain_db - current_gain_db_[channel]);
126 const SampleType input = channel == 0 ? send_left : send_right;
127 if (bypassed_ || current_gain_db_[channel] == SampleType{0}) {
128 output[channel] = input;
130 output[channel] = bounded_multiply(input, db_to_linear(current_gain_db_[channel]));
138 void process_block(
const SampleType* send_left,
const SampleType* send_right,
139 const SampleType* detector_left,
const SampleType* detector_right,
140 SampleType* output_left, SampleType* output_right,
141 int num_samples)
noexcept {
142 if (num_samples <= 0 || send_left ==
nullptr || send_right ==
nullptr ||
143 detector_left ==
nullptr || detector_right ==
nullptr || output_left ==
nullptr ||
144 output_right ==
nullptr)
146 for (
int i = 0;
i < num_samples; ++
i) {
147 const SampleType input_left = send_left[
i];
148 const SampleType input_right = send_right[
i];
150 process(input_left, input_right, detector_left[
i], detector_right[
i]);
151 output_left[
i] = output[0];
152 output_right[
i] = output[1];
158 const SampleType* detector_left,
const SampleType* detector_right,
159 int num_samples)
noexcept {
160 process_block(send_left, send_right, detector_left, detector_right, send_left, send_right,
173 current_gain_db_ = {config_.send_gain_db, config_.send_gain_db};
174 duck_reduction_db_ = {};
177 [[nodiscard]]
const Config&
config() const noexcept {
193 return detector_.current();
196 return current_gain_db_;
204 static bool finite(SampleType value)
noexcept {
205 return std::isfinite(
static_cast<double>(value));
208 static bool valid(
const Config&
config)
noexcept {
213 config.attack_ms <= SampleType{2000} && finite(
config.release_ms) &&
214 config.release_ms >= SampleType{0.01} &&
config.release_ms <= SampleType{10000} &&
217 (
config.detector == DetectorMode::peak ||
config.detector == DetectorMode::rms) &&
222 static SampleType db_to_linear(SampleType db)
noexcept {
223 return std::pow(SampleType{10}, db / SampleType{20});
226 static SampleType bounded_multiply(SampleType input, SampleType gain)
noexcept {
227 const SampleType limit = std::numeric_limits<SampleType>::max();
228 if (gain > SampleType{1} && std::abs(input) > limit / gain)
229 return std::copysign(limit, input);
233 std::array<SampleType, 2> apply_current_gain(SampleType
left, SampleType
right)
const noexcept {
234 if (config_.send_gain_db == SampleType{0})
236 const SampleType gain = db_to_linear(config_.send_gain_db);
237 return {bounded_multiply(
left, gain), bounded_multiply(
right, gain)};
241 detector_.prepare(sample_rate_);
242 detector_.set_mode(
config.detector);
243 detector_.set_attack_ms(
config.attack_ms);
244 detector_.set_release_ms(
config.release_ms);
250 StereoEnvelopeFollowerT<SampleType> detector_{};
251 std::array<SampleType, 2> current_gain_db_{};
252 std::array<SampleType, 2> duck_reduction_db_{};
253 SampleType sample_rate_ = SampleType{0};
254 bool prepared_ =
false;
255 bool bypassed_ =
false;
Stereo send-gain processor for keeping an effect return out of the way of a source or sidechain signa...
Definition auto_ducked_send.hpp:34
std::array< SampleType, 2 > process(SampleType send_left, SampleType send_right, SampleType detector_left, SampleType detector_right) noexcept
Process a stereo send and an independent stereo detector sample.
Definition auto_ducked_send.hpp:98
float sample_rate() const noexcept
Definition auto_ducked_send.hpp:183
const Config & config() const noexcept
Definition auto_ducked_send.hpp:177
static SampleType gain_computer_db(SampleType detector_level_db, const Config &config) noexcept
Pure memoryless detector-level-to-send-gain transfer function.
Definition auto_ducked_send.hpp:86
AutoDuckedSendStatus configure(const Config &next) noexcept
Atomically adopts a complete valid configuration.
Definition auto_ducked_send.hpp:69
static constexpr float kMaxSendGainDb
Definition auto_ducked_send.hpp:54
AutoDuckedSendStatus prepare(SampleType sample_rate) noexcept
Definition auto_ducked_send.hpp:57
std::array< GainReduction, 2 > gain_reduction() const noexcept
Definition auto_ducked_send.hpp:198
bool bypassed() const noexcept
Definition auto_ducked_send.hpp:167
std::array< SampleType, 2 > current_gain_db() const noexcept
Definition auto_ducked_send.hpp:195
static constexpr int tail_samples() noexcept
Definition auto_ducked_send.hpp:189
std::array< SampleType, 2 > detector_envelope() const noexcept
Definition auto_ducked_send.hpp:192
static constexpr int latency_samples() noexcept
Definition auto_ducked_send.hpp:186
static constexpr float kMinSendGainDb
Definition auto_ducked_send.hpp:53
static constexpr float kMaxSampleRate
Definition auto_ducked_send.hpp:55
void process_block(const SampleType *send_left, const SampleType *send_right, const SampleType *detector_left, const SampleType *detector_right, SampleType *output_left, SampleType *output_right, int num_samples) noexcept
Out-of-place or in-place block processing.
Definition auto_ducked_send.hpp:138
static constexpr float kMinThresholdDb
Definition auto_ducked_send.hpp:50
void process_block(SampleType *send_left, SampleType *send_right, const SampleType *detector_left, const SampleType *detector_right, int num_samples) noexcept
Convenience in-place block path for the send buffers.
Definition auto_ducked_send.hpp:157
void reset() noexcept
Definition auto_ducked_send.hpp:171
void set_bypassed(bool bypassed) noexcept
Definition auto_ducked_send.hpp:164
typename EnvelopeFollowerT< SampleType >::Mode DetectorMode
Definition auto_ducked_send.hpp:38
static constexpr float kMaxThresholdDb
Definition auto_ducked_send.hpp:51
static constexpr float kMaxRangeDb
Definition auto_ducked_send.hpp:52
bool prepared() const noexcept
Definition auto_ducked_send.hpp:180
typename Base::Mode Mode
Definition dynamics_contract.hpp:76
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
@ right
Definition multi_channel_meter.hpp:67
@ left
Definition multi_channel_meter.hpp:66
AutoDuckedSendT< float > AutoDuckedSend
Definition auto_ducked_send.hpp:258
AutoDuckedSendT< double > AutoDuckedSend64
Definition auto_ducked_send.hpp:259
AutoDuckedSendStatus
Definition auto_ducked_send.hpp:18
@ 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
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 auto_ducked_send.hpp:40
SampleType threshold_db
Definition auto_ducked_send.hpp:41
SampleType release_ms
Definition auto_ducked_send.hpp:44
SampleType range_db
Definition auto_ducked_send.hpp:42
SampleType send_gain_db
Definition auto_ducked_send.hpp:45
DetectorMode detector
Definition auto_ducked_send.hpp:46
SampleType attack_ms
Definition auto_ducked_send.hpp:43
DynamicsStereoLink stereo_link
Definition auto_ducked_send.hpp:47