Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
auto_ducked_send.hpp
Go to the documentation of this file.
1#pragma once
2
5
8
9#include <algorithm>
10#include <array>
11#include <cmath>
12#include <cstddef>
13#include <limits>
14#include <type_traits>
15
16namespace pulp::signal {
17
19
34template <typename SampleType = float> class AutoDuckedSendT {
35 public:
36 static_assert(std::is_floating_point_v<SampleType>);
37
39
40 struct Config {
41 SampleType threshold_db = SampleType{-24};
42 SampleType range_db = SampleType{12};
43 SampleType attack_ms = SampleType{10};
44 SampleType release_ms = SampleType{250};
45 SampleType send_gain_db = SampleType{0};
46 DetectorMode detector = DetectorMode::peak;
48 };
49
50 static constexpr SampleType kMinThresholdDb = SampleType{-160};
51 static constexpr SampleType kMaxThresholdDb = SampleType{24};
52 static constexpr SampleType kMaxRangeDb = SampleType{96};
53 static constexpr SampleType kMinSendGainDb = SampleType{-160};
54 static constexpr SampleType kMaxSendGainDb = SampleType{24};
55 static constexpr SampleType kMaxSampleRate = SampleType{1536000};
56
57 [[nodiscard]] AutoDuckedSendStatus prepare(SampleType sample_rate) noexcept {
58 if (!finite(sample_rate) || !(sample_rate > SampleType{0}) || sample_rate > kMaxSampleRate)
60 sample_rate_ = sample_rate;
61 apply_config(config_);
62 prepared_ = true;
63 reset();
65 }
66
69 [[nodiscard]] AutoDuckedSendStatus configure(const Config& next) noexcept {
70 if (!valid(next))
72 const bool detector_topology_changed =
73 next.detector != config_.detector || next.stereo_link != config_.stereo_link;
74 config_ = next;
75 if (prepared_) {
76 apply_config(config_);
77 if (detector_topology_changed)
78 reset();
79 } else {
80 current_gain_db_ = {config_.send_gain_db, config_.send_gain_db};
81 }
83 }
84
86 [[nodiscard]] static SampleType gain_computer_db(SampleType detector_level_db,
87 const Config& config) noexcept {
88 if (!finite(detector_level_db) || !valid(config))
89 return SampleType{0};
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);
92 }
93
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)) {
102 reset();
103 return {SampleType{0}, SampleType{0}};
104 }
105 if (!finite(detector_left) || !finite(detector_right)) {
106 reset();
107 // Detector failure cannot override a path whose output does not
108 // depend on the detector. Bypass stays bit-exact; zero duck range
109 // still applies only the independent base send gain.
110 if (bypassed_)
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}};
115 }
116
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])
123 current_gain_db_[channel] = gain_computer_db(detector_db, config_);
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;
129 } else {
130 output[channel] = bounded_multiply(input, db_to_linear(current_gain_db_[channel]));
131 }
132 }
133 return output;
134 }
135
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)
145 return;
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];
149 const auto output =
150 process(input_left, input_right, detector_left[i], detector_right[i]);
151 output_left[i] = output[0];
152 output_right[i] = output[1];
153 }
154 }
155
157 void process_block(SampleType* send_left, SampleType* send_right,
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,
161 num_samples);
162 }
163
164 void set_bypassed(bool bypassed) noexcept {
165 bypassed_ = bypassed;
166 }
167 [[nodiscard]] bool bypassed() const noexcept {
168 return bypassed_;
169 }
170
171 void reset() noexcept {
172 detector_.reset();
173 current_gain_db_ = {config_.send_gain_db, config_.send_gain_db};
174 duck_reduction_db_ = {};
175 }
176
177 [[nodiscard]] const Config& config() const noexcept {
178 return config_;
179 }
180 [[nodiscard]] bool prepared() const noexcept {
181 return prepared_;
182 }
183 [[nodiscard]] SampleType sample_rate() const noexcept {
184 return sample_rate_;
185 }
186 [[nodiscard]] static constexpr int latency_samples() noexcept {
187 return 0;
188 }
189 [[nodiscard]] static constexpr int tail_samples() noexcept {
190 return 0;
191 }
192 [[nodiscard]] std::array<SampleType, 2> detector_envelope() const noexcept {
193 return detector_.current();
194 }
195 [[nodiscard]] std::array<SampleType, 2> current_gain_db() const noexcept {
196 return current_gain_db_;
197 }
198 [[nodiscard]] std::array<GainReduction, 2> gain_reduction() const noexcept {
199 return {GainReduction::from_magnitude_db(duck_reduction_db_[0]),
200 GainReduction::from_magnitude_db(duck_reduction_db_[1])};
201 }
202
203 private:
204 static bool finite(SampleType value) noexcept {
205 return std::isfinite(static_cast<double>(value));
206 }
207
208 static bool valid(const Config& config) noexcept {
211 config.range_db >= SampleType{0} && config.range_db <= kMaxRangeDb &&
212 finite(config.attack_ms) && config.attack_ms >= SampleType{0.01} &&
213 config.attack_ms <= SampleType{2000} && finite(config.release_ms) &&
214 config.release_ms >= SampleType{0.01} && config.release_ms <= SampleType{10000} &&
215 finite(config.send_gain_db) && config.send_gain_db >= kMinSendGainDb &&
216 config.send_gain_db <= kMaxSendGainDb &&
217 (config.detector == DetectorMode::peak || config.detector == DetectorMode::rms) &&
218 (config.stereo_link == DynamicsStereoLink::independent ||
220 }
221
222 static SampleType db_to_linear(SampleType db) noexcept {
223 return std::pow(SampleType{10}, db / SampleType{20});
224 }
225
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);
230 return snap_to_zero(input * gain);
231 }
232
233 std::array<SampleType, 2> apply_current_gain(SampleType left, SampleType right) const noexcept {
234 if (config_.send_gain_db == SampleType{0})
235 return {left, right};
236 const SampleType gain = db_to_linear(config_.send_gain_db);
237 return {bounded_multiply(left, gain), bounded_multiply(right, gain)};
238 }
239
240 void apply_config(const Config& config) noexcept {
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);
245 detector_.set_link(config.stereo_link == DynamicsStereoLink::peak_linked ? SampleType{1}
246 : SampleType{0});
247 }
248
249 Config config_{};
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;
256};
257
260
261} // namespace pulp::signal
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