8#include <pulp/signal/detail/audio_range.hpp>
27template <
typename SampleType =
float>
38template <
typename SampleType =
float>
54template <
typename SampleType =
float>
55std::optional<PreparedParallelDynamicsConfigT<SampleType>>
57 std::size_t max_latency_samples)
noexcept {
58 static_assert(std::is_floating_point_v<SampleType>);
59 static_assert(std::atomic<std::size_t>::is_always_lock_free,
60 "parallel dynamics reporting must be lock-free");
61 constexpr SampleType kMinOutputGainDb = SampleType{-120};
62 constexpr SampleType kMaxOutputGainDb = SampleType{24};
64 if (!std::isfinite(config.wet_mix) || config.wet_mix < SampleType{0} ||
65 config.wet_mix > SampleType{1} || !std::isfinite(config.output_gain_db) ||
66 config.output_gain_db < kMinOutputGainDb ||
67 config.output_gain_db > kMaxOutputGainDb ||
68 config.dry_latency_samples > max_latency_samples ||
69 config.wet_latency_samples > max_latency_samples ||
76 if (config.wet_mix == SampleType{0}) {
79 }
else if (config.wet_mix == SampleType{1}) {
87 if (!std::isfinite(result.
output_gain))
return std::nullopt;
90 std::max(config.dry_latency_samples, config.wet_latency_samples);
91 const auto dry_compensation =
93 const auto wet_compensation =
95 if (config.dry_tail_samples >
96 std::numeric_limits<std::size_t>::max() - dry_compensation ||
97 config.wet_tail_samples >
98 std::numeric_limits<std::size_t>::max() - wet_compensation)
100 const auto aligned_dry_tail = config.dry_tail_samples + dry_compensation;
101 const auto aligned_wet_tail = config.wet_tail_samples + wet_compensation;
102 if (config.wet_mix == SampleType{0})
104 else if (config.wet_mix == SampleType{1})
107 result.
tail_samples = std::max(aligned_dry_tail, aligned_wet_tail);
123template <
typename SampleType =
float>
129 static_assert(std::is_floating_point_v<SampleType>);
134 max_block_size > std::numeric_limits<std::size_t>::max() / kStreamCount)
138 auto replacement_scratch =
139 std::unique_ptr<SampleType[]>(
new (std::nothrow) SampleType[scratch_samples]);
140 if (!replacement_scratch)
return false;
141 std::fill_n(replacement_scratch.get(), scratch_samples, SampleType{0});
147 scratch_ = std::move(replacement_scratch);
148 aligner_ = std::move(replacement_aligner);
149 max_latency_samples_ = max_latency_samples;
151 configured_dry_latency_ = 0;
152 configured_wet_latency_ = 0;
153 reported_latency_samples_.store(0, std::memory_order_relaxed);
154 reported_tail_samples_.store(0, std::memory_order_relaxed);
158 auto initial = default_config();
159 initial.generation = next_generation();
160 published_.write(initial);
165 if (!prepared_)
return false;
168 if (!
prepared || config.dry_latency_samples != configured_dry_latency_ ||
169 config.wet_latency_samples != configured_wet_latency_)
171 prepared->generation = next_generation();
178 if (!prepared_)
return false;
182 const std::array<std::size_t, 2> latencies{
183 config.dry_latency_samples, config.wet_latency_samples};
184 if (!aligner_.configure_latencies(latencies))
return false;
185 configured_dry_latency_ = config.dry_latency_samples;
186 configured_wet_latency_ = config.wet_latency_samples;
187 prepared->generation = next_generation();
195 const SampleType*
const* wet,
196 SampleType*
const* output,
197 std::size_t frames)
noexcept {
198 if (!validate_layout(dry, wet, output, frames))
return false;
199 if (!sync_config())
return false;
200 if (frames == 0)
return true;
202 std::array<const SampleType*, kStreamCount> inputs{
203 dry[0], dry[1], wet[0], wet[1]};
204 std::array<SampleType*, kStreamCount> aligned{};
205 for (std::size_t stream = 0; stream < kStreamCount; ++stream)
206 aligned[stream] = scratch_.get() + stream * max_block_size_;
208 if (!aligner_.process(inputs.data(), inputs.size(), aligned.data(),
209 aligned.size(), frames))
212 for (std::size_t channel = 0; channel < kChannelCount; ++channel) {
213 const auto* aligned_dry = aligned[channel];
214 const auto* aligned_wet = aligned[kChannelCount + channel];
215 for (std::size_t frame = 0; frame < frames; ++frame) {
216 const auto dry_sample = finite_or_zero(aligned_dry[frame]);
217 const auto wet_sample = finite_or_zero(aligned_wet[frame]);
218 const auto mixed = (dry_sample * active_.dry_gain +
219 wet_sample * active_.wet_gain) *
221 output[channel][frame] = finite_or_zero(mixed);
228 if (!prepared_)
return;
229 if (!sync_config())
return;
231 std::fill_n(scratch_.get(), max_block_size_ * kStreamCount, SampleType{0});
235 return reported_latency_samples_.load(std::memory_order_acquire);
238 return reported_tail_samples_.load(std::memory_order_acquire);
241 bool prepared() const noexcept {
return prepared_; }
244 static constexpr std::size_t kChannelCount = 2;
245 static constexpr std::size_t kStreamCount = 4;
251 std::uint64_t next_generation() noexcept {
252 ++writer_generation_;
253 if (writer_generation_ == 0) ++writer_generation_;
254 return writer_generation_;
257 static SampleType finite_or_zero(SampleType sample)
noexcept {
258 return std::isfinite(sample) ? sample : SampleType{0};
261 bool sync_config() noexcept {
262 const auto& latest = published_.read();
263 if (latest.generation == active_.generation)
return true;
265 publish_reporting(active_);
270 reported_latency_samples_.store(
config.latency_samples,
271 std::memory_order_release);
272 reported_tail_samples_.store(
config.tail_samples,
273 std::memory_order_release);
276 bool validate_layout(
const SampleType*
const* dry,
277 const SampleType*
const* wet,
278 SampleType*
const* output,
279 std::size_t frames)
const noexcept {
280 if (!prepared_ || frames > max_block_size_ || dry ==
nullptr ||
281 wet ==
nullptr || output ==
nullptr)
283 for (std::size_t channel = 0; channel < kChannelCount; ++channel)
284 if (dry[channel] ==
nullptr || wet[channel] ==
nullptr ||
285 output[channel] ==
nullptr)
288 if (frames == 0)
return true;
289 const std::array<const SampleType*, 3>
left{dry[0], wet[0], output[0]};
290 const std::array<const SampleType*, 3>
right{dry[1], wet[1], output[1]};
291 for (
const auto* left_range :
left)
292 for (
const auto* right_range :
right)
293 if (detail::audio_ranges_overlap(left_range, right_range, frames))
296 for (std::size_t channel = 0; channel < kChannelCount; ++channel) {
297 if (dry[channel] != wet[channel] &&
298 detail::audio_ranges_overlap(dry[channel], wet[channel], frames))
300 if (output[channel] != dry[channel] &&
301 detail::audio_ranges_overlap(output[channel], dry[channel], frames))
303 if (output[channel] != wet[channel] &&
304 detail::audio_ranges_overlap(output[channel], wet[channel], frames))
310 PathLatencyAlignerT<SampleType, 2, 2> aligner_;
311 std::unique_ptr<SampleType[]> scratch_;
312 pulp::runtime::TripleBuffer<PreparedConfig> published_;
314 std::size_t max_latency_samples_ = 0;
315 std::size_t max_block_size_ = 0;
316 std::size_t configured_dry_latency_ = 0;
317 std::size_t configured_wet_latency_ = 0;
318 std::atomic<std::size_t> reported_latency_samples_{0};
319 std::atomic<std::size_t> reported_tail_samples_{0};
320 std::uint64_t writer_generation_ = 0;
321 bool prepared_ =
false;
Blend true-stereo dry input with an already-processed true-stereo wet path.
Definition parallel_dynamics.hpp:124
ParallelDynamicsConfigT< SampleType > Config
Definition parallel_dynamics.hpp:126
ParallelDynamicsMixerT() noexcept
Definition parallel_dynamics.hpp:130
void reset() noexcept
Definition parallel_dynamics.hpp:227
bool prepared() const noexcept
Definition parallel_dynamics.hpp:241
bool process(const SampleType *const *dry, const SampleType *const *wet, SampleType *const *output, std::size_t frames) noexcept
Definition parallel_dynamics.hpp:194
bool publish_config(const Config &config) noexcept
Definition parallel_dynamics.hpp:164
std::size_t tail_samples() const noexcept
Definition parallel_dynamics.hpp:237
PreparedParallelDynamicsConfigT< SampleType > PreparedConfig
Definition parallel_dynamics.hpp:127
bool configure_latency_and_publish(const Config &config) noexcept
Change alignment and publish a validated snapshot while audio is stopped.
Definition parallel_dynamics.hpp:177
std::size_t latency_samples() const noexcept
Definition parallel_dynamics.hpp:234
std::size_t max_block_size() const noexcept
Definition parallel_dynamics.hpp:240
bool prepare(std::size_t max_latency_samples, std::size_t max_block_size)
Definition parallel_dynamics.hpp:132
Aligns N already-processed paths to the largest declared intrinsic latency.
Definition path_latency_aligner.hpp:28
bool prepare(std::size_t paths, std::size_t channels, std::size_t max_latency_samples, std::size_t max_block_size)
Definition path_latency_aligner.hpp:37
The one shared old→new crossfade gain law for the SIGNAL side.
T db_to_linear(T db)
Amplitude dB → linear gain. 0 dB → 1, −6.0206 dB → 0.5.
Definition units.hpp:71
Definition additive_bank.hpp:218
@ right
Definition multi_channel_meter.hpp:67
@ left
Definition multi_channel_meter.hpp:66
ParallelDynamicsConfigT< double > ParallelDynamicsConfig64
Definition parallel_dynamics.hpp:325
ParallelDynamicsConfigT< float > ParallelDynamicsConfig
Definition parallel_dynamics.hpp:324
PreparedParallelDynamicsConfigT< float > PreparedParallelDynamicsConfig
Definition parallel_dynamics.hpp:326
ParallelDynamicsMixerT< float > ParallelDynamicsMixer
Definition parallel_dynamics.hpp:328
CrossfadeGainLaw
The old→new gain split applied to a shaped progress u ∈ [0,1].
Definition crossfade.hpp:41
@ EqualPower
(cos(u·π/2), sin(u·π/2)) — power sum == 1.
Definition crossfade.hpp:43
@ EqualGain
(1−u, u) — amplitude sum == 1.
Definition crossfade.hpp:42
void crossfade_gains(T u, CrossfadeGainLaw law, T &old_gain, T &new_gain) noexcept
Old/new blend gains for an ALREADY-SHAPED progress u (the caller applies any smoothstep/clamp first).
Definition crossfade.hpp:61
std::optional< PreparedParallelDynamicsConfigT< SampleType > > prepare_parallel_dynamics_config(const ParallelDynamicsConfigT< SampleType > &config, std::size_t max_latency_samples) noexcept
Validate a complete control snapshot and derive its audio-rate gains.
Definition parallel_dynamics.hpp:56
ParallelDynamicsMixerT< double > ParallelDynamicsMixer64
Definition parallel_dynamics.hpp:329
PreparedParallelDynamicsConfigT< double > PreparedParallelDynamicsConfig64
Definition parallel_dynamics.hpp:327
Definition parallel_dynamics.hpp:28
std::size_t wet_tail_samples
Definition parallel_dynamics.hpp:35
std::size_t wet_latency_samples
Definition parallel_dynamics.hpp:33
SampleType wet_mix
Definition parallel_dynamics.hpp:29
SampleType output_gain_db
Definition parallel_dynamics.hpp:31
std::size_t dry_tail_samples
Definition parallel_dynamics.hpp:34
CrossfadeGainLaw mix_law
Definition parallel_dynamics.hpp:30
std::size_t dry_latency_samples
Definition parallel_dynamics.hpp:32
Definition parallel_dynamics.hpp:39
ParallelDynamicsConfigT< SampleType > config
Definition parallel_dynamics.hpp:40
SampleType wet_gain
Definition parallel_dynamics.hpp:42
SampleType dry_gain
Definition parallel_dynamics.hpp:41
SampleType output_gain
Definition parallel_dynamics.hpp:43
std::size_t latency_samples
Definition parallel_dynamics.hpp:44
std::uint64_t generation
Definition parallel_dynamics.hpp:46
std::size_t tail_samples
Definition parallel_dynamics.hpp:45
Lock-free latest-value publication between two threads.
The real-unit vocabulary shared by every DSP block in the catalog.