Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
finite_stretch_builder.hpp
Go to the documentation of this file.
1#pragma once
2
6
9
10#include <algorithm>
11#include <array>
12#include <cmath>
13#include <cstdint>
14#include <limits>
15
16namespace pulp::signal {
17
29
31
39
42
48
50 const RealtimePitchTimeConfig& config, std::uint64_t input_frames) noexcept {
51 const bool quality = config.quality == PitchTimeQuality::quality;
52 const auto fft_size = static_cast<std::uint64_t>(
53 config.fft_size > 0 ? config.fft_size : (quality ? 4096 : 1024));
54 const auto analysis_hop = static_cast<std::uint64_t>(
55 config.analysis_hop > 0 ? config.analysis_hop : (quality ? 512 : 256));
56 const auto signed_max = static_cast<std::uint64_t>(
57 std::numeric_limits<std::int64_t>::max());
58 std::uint64_t eof_padding = 0;
59 std::uint64_t padded_input = 0;
60 if (!checked_capacity_product(analysis_hop, 2u, signed_max, eof_padding)
61 || !checked_capacity_sum(fft_size, eof_padding, signed_max, eof_padding)
62 || !checked_capacity_sum(input_frames, eof_padding, signed_max, padded_input))
64 const long double maximum_hop_value =
65 std::ceil(static_cast<long double>(config.max_time_ratio)
66 * static_cast<long double>(analysis_hop)) + 1.0L;
67 if (!std::isfinite(maximum_hop_value)
68 || maximum_hop_value > static_cast<long double>(std::numeric_limits<int>::max()))
70 std::uint64_t frame_count = 0;
71 std::uint64_t maximum_output = 0;
72 if (!checked_capacity_sum(padded_input / std::max<std::uint64_t>(analysis_hop, 1u),
73 2u, signed_max, frame_count)
74 || !checked_capacity_product(frame_count,
75 static_cast<std::uint64_t>(maximum_hop_value),
76 signed_max, maximum_output))
78 // Every emitted hop is positive, so the accumulator's negative correction
79 // debt can never have a larger magnitude than this same bounded output
80 // position. One proof therefore admits both the synthesis counter and its
81 // exact signed correction debt.
83}
84
89 float (*)(void* context, std::uint64_t input_frame) noexcept;
90
91template <typename SampleType = float>
93 double sample_rate = 48000.0;
95 const SampleType* const* input = nullptr;
96 std::uint64_t input_frames = 0;
97 SampleType* const* output = nullptr;
98 std::uint64_t output_capacity_frames = 0;
99 std::uint64_t target_frames = 0;
102 void* ratio_context = nullptr;
104};
105
106template <typename SampleType = float>
108public:
110
113 processor_prepare_status_ = PitchTimePrepareStatus::invalid_sample_rate;
114 input_consumed_ = 0;
115 output_written_ = 0;
116 pending_ = {};
117 drain_before_pending_ = false;
118 done_ = false;
120 last_work_unit_ = FiniteStretchWorkUnit::none;
121 last_work_outcome_ = FiniteStretchWorkOutcome::none;
122 prepare_status_ = validate(config);
123 if (prepare_status_ != FiniteStretchPrepareStatus::prepared) return prepare_status_;
124
125 processor_prepare_status_ =
126 processor_.prepare(config.sample_rate, config.processor, config.target_max_bytes);
127 if (processor_prepare_status_ != PitchTimePrepareStatus::prepared) {
129 return prepare_status_;
130 }
131
132 config_ = config;
133 return prepare_status_;
134 }
135
137 last_work_unit_ = FiniteStretchWorkUnit::none;
138 last_work_outcome_ = FiniteStretchWorkOutcome::none;
139 if (prepare_status_ != FiniteStretchPrepareStatus::prepared
140 || failure_ != FiniteStretchFailure::none)
142 if (done_) return FiniteStretchStepStatus::complete;
143
144 if (drain_before_pending_) return drain_one();
145 if (pending_.kind != PendingKind::none) return execute_pending();
146 if (input_consumed_ < config_.input_frames) return begin_feed();
147 return finalize_one();
148 }
149
150 FiniteStretchPrepareStatus prepare_status() const noexcept { return prepare_status_; }
152 return processor_prepare_status_;
153 }
154 FiniteStretchFailure failure() const noexcept { return failure_; }
155 FiniteStretchWorkUnit last_work_unit() const noexcept { return last_work_unit_; }
156 FiniteStretchWorkOutcome last_work_outcome() const noexcept { return last_work_outcome_; }
157 std::uint64_t input_frames_consumed() const noexcept { return input_consumed_; }
158 std::uint64_t output_frames_written() const noexcept { return output_written_; }
159 bool complete() const noexcept { return done_; }
160
161private:
162 static FiniteStretchPrepareStatus validate(const Config& config) noexcept {
163 if (config.processor.mode != PitchTimeMode::time_stretch)
165 if (config.processor.channels < 1
166 || config.processor.channels > kRealtimePitchTimeMaximumChannels)
168 if (config.input_frames > 0 && config.input == nullptr)
170 if (config.target_frames > config.output_capacity_frames)
172 if (config.output_capacity_frames > 0 && config.output == nullptr)
174 if (config.ratio_at_input_frame == nullptr
175 && std::isfinite(config.processor.max_time_ratio)
176 && config.processor.max_time_ratio >= 1.0f) {
177 const float minimum = 1.0f / config.processor.max_time_ratio;
178 if (!std::isfinite(config.constant_time_ratio)
179 || config.constant_time_ratio < minimum
180 || config.constant_time_ratio > config.processor.max_time_ratio)
182 }
183 for (int channel = 0; channel < config.processor.channels; ++channel) {
184 if (config.input_frames > 0 && config.input[channel] == nullptr)
186 if (config.output_capacity_frames > 0 && config.output[channel] == nullptr)
188 }
189 const auto maximum_index = static_cast<std::uint64_t>(
190 std::min(std::numeric_limits<std::size_t>::max(),
191 static_cast<std::size_t>(std::numeric_limits<std::ptrdiff_t>::max())));
192 if (config.input_frames > maximum_index || config.output_capacity_frames > maximum_index
193 || config.target_frames
194 > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max()))
196 // Input and output are independent caller-owned planar allocations.
197 // Validate each channel span, not their aggregate, against the target
198 // addressability ceiling.
199 std::uint64_t input_bytes = 0;
200 std::uint64_t output_bytes = 0;
201 if (!checked_allocation_bytes<SampleType>(config.input_frames,
202 config.target_max_bytes, &input_bytes)
203 || !checked_allocation_bytes<SampleType>(config.output_capacity_frames,
204 config.target_max_bytes, &output_bytes))
206 if (std::isfinite(config.processor.max_time_ratio)
207 && config.processor.max_time_ratio >= 1.0f
208 && checked_finite_stretch_counter_geometry(config.processor, config.input_frames)
211
212 const auto range_representable = [](std::uintptr_t address,
213 std::uint64_t bytes) noexcept {
214 std::uint64_t end = 0;
215 return checked_capacity_sum(address, bytes,
216 std::numeric_limits<std::uintptr_t>::max(), end);
217 };
218 const auto ranges_overlap = [](std::uintptr_t first, std::uint64_t first_bytes,
219 std::uintptr_t second, std::uint64_t second_bytes) noexcept {
220 if (first_bytes == 0 || second_bytes == 0) return false;
221 std::uint64_t first_end = 0;
222 std::uint64_t second_end = 0;
223 if (!checked_capacity_sum(first, first_bytes,
224 std::numeric_limits<std::uintptr_t>::max(), first_end)
225 || !checked_capacity_sum(second, second_bytes,
226 std::numeric_limits<std::uintptr_t>::max(), second_end))
227 return true;
228 return first < second_end && second < first_end;
229 };
230 for (int channel = 0; channel < config.processor.channels; ++channel) {
231 if (input_bytes > 0
232 && !range_representable(reinterpret_cast<std::uintptr_t>(config.input[channel]),
233 input_bytes))
235 if (output_bytes > 0
236 && !range_representable(
237 reinterpret_cast<std::uintptr_t>(config.output[channel]), output_bytes))
239 }
240 for (int out_channel = 0; output_bytes > 0 && out_channel < config.processor.channels;
241 ++out_channel) {
242 const auto out_address = reinterpret_cast<std::uintptr_t>(config.output[out_channel]);
243 for (int input_channel = 0;
244 input_bytes > 0 && input_channel < config.processor.channels; ++input_channel)
245 if (ranges_overlap(out_address, output_bytes,
246 reinterpret_cast<std::uintptr_t>(config.input[input_channel]),
247 input_bytes))
249 for (int prior = 0; prior < out_channel; ++prior)
250 if (ranges_overlap(out_address, output_bytes,
251 reinterpret_cast<std::uintptr_t>(config.output[prior]),
252 output_bytes))
254 }
256 }
257
258 bool resolve_ratio(std::uint64_t boundary, float& resolved) noexcept {
259 const auto position = std::min(boundary, config_.input_frames);
260 const float ratio = config_.ratio_at_input_frame != nullptr
261 ? config_.ratio_at_input_frame(config_.ratio_context, position)
262 : config_.constant_time_ratio;
263 const float minimum = 1.0f / config_.processor.max_time_ratio;
264 if (!std::isfinite(ratio) || ratio < minimum || ratio > config_.processor.max_time_ratio) {
266 return false;
267 }
268 resolved = ratio;
269 return true;
270 }
271
272 FiniteStretchStepStatus begin_feed() noexcept {
273 const int until_boundary = processor_.samples_until_next_analysis_frame();
274 if (until_boundary <= 0) return fail(FiniteStretchFailure::processor_protocol_error);
275 const auto remaining = config_.input_frames - input_consumed_;
276 pending_.kind = PendingKind::feed;
277 pending_.count = static_cast<int>(std::min<std::uint64_t>(
278 remaining, static_cast<std::uint64_t>(
279 std::min(config_.processor.max_block, until_boundary))));
280 if (pending_.count <= 0) return fail(FiniteStretchFailure::processor_protocol_error);
281 pending_.apply_ratio = pending_.count == until_boundary;
282 const auto boundary = input_consumed_ + static_cast<std::uint64_t>(pending_.count);
283 if (pending_.apply_ratio && !resolve_ratio(boundary, pending_.ratio))
285 return execute_pending();
286 }
287
288 FiniteStretchStepStatus execute_pending() noexcept {
289 if (pending_.apply_ratio) processor_.set_time_ratio(pending_.ratio);
290 if (pending_.kind == PendingKind::finalize) return execute_finalize();
291 if (pending_.kind != PendingKind::feed)
293 for (int channel = 0; channel < config_.processor.channels; ++channel)
294 input_ptrs_[static_cast<std::size_t>(channel)] =
295 config_.input[channel] + static_cast<std::size_t>(input_consumed_);
296 last_work_unit_ = FiniteStretchWorkUnit::feed;
297 const auto status = processor_.feed(input_ptrs_.data(), pending_.count);
299 input_consumed_ += static_cast<std::uint64_t>(pending_.count);
300 pending_ = {};
301 last_work_outcome_ = FiniteStretchWorkOutcome::advanced;
303 }
305 drain_before_pending_ = true;
306 last_work_outcome_ = FiniteStretchWorkOutcome::backpressure;
308 }
310 }
311
312 FiniteStretchStepStatus drain_one() noexcept {
313 const int available = processor_.available_stretched();
314 if (available <= 0) {
315 drain_before_pending_ = false;
317 }
318 const auto remaining = config_.target_frames - output_written_;
319 if (remaining == 0) return fail(FiniteStretchFailure::output_too_long);
320 const int take = static_cast<int>(std::min<std::uint64_t>(
321 static_cast<std::uint64_t>(
322 std::min(available, config_.processor.max_block)),
323 remaining));
324 for (int channel = 0; channel < config_.processor.channels; ++channel)
325 output_ptrs_[static_cast<std::size_t>(channel)] =
326 config_.output[channel] + static_cast<std::size_t>(output_written_);
327 last_work_unit_ = FiniteStretchWorkUnit::drain;
328 const int read = processor_.read_stretched(output_ptrs_.data(), take);
329 if (read != take) return fail(FiniteStretchFailure::processor_protocol_error);
330 last_work_outcome_ = FiniteStretchWorkOutcome::advanced;
331 output_written_ += static_cast<std::uint64_t>(read);
332 drain_before_pending_ = processor_.available_stretched() > 0;
333 if (static_cast<std::uint64_t>(available) > remaining)
336 }
337
338 FiniteStretchStepStatus finalize_one() noexcept {
339 const int until_boundary = processor_.samples_until_next_analysis_frame();
340 if (until_boundary <= 0) return fail(FiniteStretchFailure::processor_protocol_error);
341 const auto plan = processor_.plan_finalize(
342 std::min(config_.processor.max_block, until_boundary));
344 if (output_written_ != config_.target_frames)
346 done_ = true;
348 }
350 if (processor_.available_stretched() <= 0)
352 drain_before_pending_ = true;
353 return drain_one();
354 }
357 pending_.kind = PendingKind::finalize;
358 pending_.count = plan.samples;
359 pending_.apply_ratio = pending_.count == until_boundary;
360 const auto boundary = input_consumed_ + static_cast<std::uint64_t>(pending_.count);
361 if (pending_.apply_ratio && !resolve_ratio(boundary, pending_.ratio))
363 return execute_pending();
364 }
365
366 FiniteStretchStepStatus execute_finalize() noexcept {
367 last_work_unit_ = FiniteStretchWorkUnit::finalize;
368 const auto status = processor_.finalize(pending_.count);
370 pending_ = {};
371 last_work_outcome_ = FiniteStretchWorkOutcome::advanced;
372 if (processor_.available_stretched() != 0)
374 if (output_written_ != config_.target_frames)
376 done_ = true;
378 }
380 pending_ = {};
381 last_work_outcome_ = FiniteStretchWorkOutcome::advanced;
382 drain_before_pending_ = processor_.available_stretched() > 0;
384 }
386 }
387
389 failure_ = failure;
391 }
392
393 Config config_ {};
394 RealtimePitchTimeProcessorT<SampleType> processor_;
395 std::array<const SampleType*, kRealtimePitchTimeMaximumChannels> input_ptrs_ {};
396 std::array<SampleType*, kRealtimePitchTimeMaximumChannels> output_ptrs_ {};
397 std::uint64_t input_consumed_ = 0;
398 std::uint64_t output_written_ = 0;
399 enum class PendingKind { none, feed, finalize };
400 struct PendingWork {
401 PendingKind kind = PendingKind::none;
402 int count = 0;
403 float ratio = 1.0f;
404 bool apply_ratio = false;
405 };
406 PendingWork pending_ {};
407 bool drain_before_pending_ = false;
408 bool done_ = false;
414};
415
420
421} // namespace pulp::signal
Definition finite_stretch_builder.hpp:107
FiniteStretchFailure failure() const noexcept
Definition finite_stretch_builder.hpp:154
FiniteStretchPrepareStatus prepare_status() const noexcept
Definition finite_stretch_builder.hpp:150
std::uint64_t output_frames_written() const noexcept
Definition finite_stretch_builder.hpp:158
bool complete() const noexcept
Definition finite_stretch_builder.hpp:159
FiniteStretchWorkOutcome last_work_outcome() const noexcept
Definition finite_stretch_builder.hpp:156
FiniteStretchStepStatus step() noexcept
Definition finite_stretch_builder.hpp:136
FiniteStretchConfigT< SampleType > Config
Definition finite_stretch_builder.hpp:109
PitchTimePrepareStatus processor_prepare_status() const noexcept
Definition finite_stretch_builder.hpp:151
FiniteStretchPrepareStatus prepare(const Config &config)
Definition finite_stretch_builder.hpp:111
std::uint64_t input_frames_consumed() const noexcept
Definition finite_stretch_builder.hpp:157
FiniteStretchWorkUnit last_work_unit() const noexcept
Definition finite_stretch_builder.hpp:155
@ config
Per-user settings root (an XDG distinction; equals app_data off Linux).
Definition special_dir.hpp:65
std::uint64_t read(std::span< const std::uint8_t > b, std::size_t p, unsigned n) noexcept
Definition spectral_frame_codec.hpp:111
Definition additive_bank.hpp:218
@ time_stretch
Definition realtime_pitch_time_geometry.hpp:26
@ first
Definition dither.hpp:23
@ second
Definition dither.hpp:24
FiniteStretchBuilderT< double > FiniteStretchBuilder64
Definition finite_stretch_builder.hpp:419
FiniteStretchWorkUnit
Definition finite_stretch_builder.hpp:40
@ feed
Definition finite_stretch_builder.hpp:40
@ finalize
Definition finite_stretch_builder.hpp:40
@ none
Definition finite_stretch_builder.hpp:40
@ drain
Definition finite_stretch_builder.hpp:40
FiniteStretchWorkOutcome
Definition finite_stretch_builder.hpp:41
@ none
Definition finite_stretch_builder.hpp:41
@ advanced
Definition finite_stretch_builder.hpp:41
@ backpressure
Definition finite_stretch_builder.hpp:41
@ needs_drain
Definition realtime_pitch_time_geometry.hpp:39
@ ready
Definition realtime_pitch_time_geometry.hpp:38
@ complete
Definition realtime_pitch_time_geometry.hpp:40
@ draining
Definition realtime_pitch_time_geometry.hpp:31
@ complete
Definition realtime_pitch_time_geometry.hpp:33
bool checked_capacity_product(std::uint64_t lhs, std::uint64_t rhs, std::uint64_t maximum, std::uint64_t &product) noexcept
Definition checked_allocation.hpp:12
@ quality
Definition realtime_pitch_time_geometry.hpp:25
bool checked_capacity_sum(std::uint64_t lhs, std::uint64_t rhs, std::uint64_t maximum, std::uint64_t &sum) noexcept
Definition checked_allocation.hpp:21
FiniteStretchBuilderT< float > FiniteStretchBuilder
Definition finite_stretch_builder.hpp:418
FiniteStretchFailure
Definition finite_stretch_builder.hpp:32
@ output_too_long
Definition finite_stretch_builder.hpp:36
@ none
Definition finite_stretch_builder.hpp:33
@ processor_protocol_error
Definition finite_stretch_builder.hpp:37
@ invalid_ratio
Definition finite_stretch_builder.hpp:34
@ output_too_short
Definition finite_stretch_builder.hpp:35
@ ratio
base_frequency_hz * ratio.
Definition fm_operator_engine.hpp:20
FiniteStretchCounterGeometryStatus checked_finite_stretch_counter_geometry(const RealtimePitchTimeConfig &config, std::uint64_t input_frames) noexcept
Definition finite_stretch_builder.hpp:49
FiniteStretchCounterGeometryStatus
Definition finite_stretch_builder.hpp:43
@ output_counter_overflow
Definition finite_stretch_builder.hpp:46
@ input_counter_overflow
Definition finite_stretch_builder.hpp:45
@ representable
Definition finite_stretch_builder.hpp:44
constexpr int kRealtimePitchTimeMaximumChannels
Definition realtime_pitch_time_geometry.hpp:79
constexpr std::uint64_t kTargetAddressMaximumBytes
Definition checked_allocation.hpp:9
@ none
Definition dither.hpp:17
float(*)(void *context, std::uint64_t input_frame) noexcept FiniteStretchRatioAtInputFrame
Called for the analysis frame ending at input_frame.
Definition finite_stretch_builder.hpp:88
FiniteStretchPrepareStatus
Definition finite_stretch_builder.hpp:18
@ unrepresentable_capacity
Definition finite_stretch_builder.hpp:26
@ invalid_input
Definition finite_stretch_builder.hpp:21
@ processor_prepare_failed
Definition finite_stretch_builder.hpp:27
@ invalid_mode
Definition finite_stretch_builder.hpp:20
@ invalid_output
Definition finite_stretch_builder.hpp:22
@ invalid_ratio
Definition finite_stretch_builder.hpp:25
@ prepared
Definition finite_stretch_builder.hpp:19
@ invalid_output_layout
Definition finite_stretch_builder.hpp:23
@ invalid_target
Definition finite_stretch_builder.hpp:24
FiniteStretchConfigT< float > FiniteStretchConfig
Definition finite_stretch_builder.hpp:416
FiniteStretchConfigT< double > FiniteStretchConfig64
Definition finite_stretch_builder.hpp:417
FiniteStretchStepStatus
Definition finite_stretch_builder.hpp:30
@ failed
Definition finite_stretch_builder.hpp:30
@ progress
Definition finite_stretch_builder.hpp:30
@ complete
Definition finite_stretch_builder.hpp:30
PitchTimePrepareStatus
Definition realtime_pitch_time_geometry.hpp:50
@ invalid_sample_rate
Definition realtime_pitch_time_geometry.hpp:52
@ prepared
Definition realtime_pitch_time_geometry.hpp:51
@ accepted
Definition realtime_pitch_time_geometry.hpp:29
@ backpressure
Definition realtime_pitch_time_geometry.hpp:29
bool checked_allocation_bytes(std::uint64_t elements, std::uint64_t target_max_bytes, std::uint64_t *bytes=nullptr) noexcept
Definition checked_allocation.hpp:31
float minimum(const float *data, std::size_t count) noexcept
@ available
A value was read. It may legitimately be zero.
Definition latency_evidence.hpp:57
Realtime pitch shifting and time-scale modification for coherent channel groups, built on SpectralFra...
Definition finite_stretch_builder.hpp:92
SampleType *const * output
Definition finite_stretch_builder.hpp:97
const SampleType *const * input
Definition finite_stretch_builder.hpp:95
RealtimePitchTimeConfig processor
Definition finite_stretch_builder.hpp:94
std::uint64_t target_frames
Definition finite_stretch_builder.hpp:99
FiniteStretchRatioAtInputFrame ratio_at_input_frame
Definition finite_stretch_builder.hpp:101
std::uint64_t output_capacity_frames
Definition finite_stretch_builder.hpp:98
double sample_rate
Definition finite_stretch_builder.hpp:93
std::uint64_t target_max_bytes
Definition finite_stretch_builder.hpp:103
std::uint64_t input_frames
Definition finite_stretch_builder.hpp:96
float constant_time_ratio
Definition finite_stretch_builder.hpp:100
void * ratio_context
Definition finite_stretch_builder.hpp:102
Definition realtime_pitch_time_geometry.hpp:61