Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
sample_heritage_src.hpp
Go to the documentation of this file.
1#pragma once
2
6
7#include <algorithm>
8#include <array>
9#include <cmath>
10#include <cstddef>
11#include <cstdint>
12#include <limits>
13#include <span>
14#include <vector>
15
16namespace pulp::audio {
17
28
36
45public:
51
52 SampleHeritageSrcStatus prepare(double source_frames_per_output,
53 std::size_t channel_count,
55 bool identity) noexcept {
56 release();
57 if (!(source_frames_per_output > 0.0) ||
58 !std::isfinite(source_frames_per_output) || channel_count == 0 ||
59 channel_count > kSampleHeritageMaximumChannels ||
60 (!identity && !selection.valid())) {
62 }
63 identity_ = identity;
64 ratio_ = source_frames_per_output;
65 channels_ = channel_count;
66 selection_ = selection;
67 if (!identity_) {
68 tap_count_ = selection.wider.tap_count();
69 history_capacity_ = tap_count_;
70 half_width_ = selection.wider.half_width();
71 if (tap_count_ == 0 ||
72 channels_ > std::numeric_limits<std::size_t>::max() / tap_count_) {
73 release();
75 }
76 try {
77 history_.assign(channels_ * tap_count_, 0.0f);
78 } catch (...) {
79 release();
81 }
82 const auto integer = std::floor(ratio_);
83 if (integer > static_cast<double>(std::numeric_limits<std::int64_t>::max())) {
84 release();
86 }
87 step_integer_ = static_cast<std::int64_t>(integer);
88 step_fraction_ = ratio_ - integer;
89 }
90 prepared_ = true;
91 reset();
93 }
94
96 double default_source_frames_per_output,
97 double minimum_source_frames_per_output,
98 double maximum_source_frames_per_output,
99 std::size_t channel_count,
100 SampleSincKernelBankView bank) noexcept {
101 if (!bank.valid() || !(minimum_source_frames_per_output > 0.0) ||
102 !std::isfinite(minimum_source_frames_per_output) ||
103 minimum_source_frames_per_output > default_source_frames_per_output ||
104 !(maximum_source_frames_per_output > 0.0) ||
105 !std::isfinite(maximum_source_frames_per_output) ||
106 default_source_frames_per_output > maximum_source_frames_per_output ||
107 !bank.select(maximum_source_frames_per_output).valid())
109 const auto selection = bank.select(default_source_frames_per_output);
110 const auto status = prepare(default_source_frames_per_output, channel_count,
111 selection, false);
112 if (status != SampleHeritageSrcStatus::Ok) return status;
113 variable_bank_ = bank;
114 maximum_ratio_ = maximum_source_frames_per_output;
115 minimum_ratio_ = minimum_source_frames_per_output;
116 fixed_latency_output_frames_ = std::ceil(
117 static_cast<double>(half_width_) / minimum_source_frames_per_output);
118 const auto history_frames = std::ceil(
119 fixed_latency_output_frames_ * maximum_source_frames_per_output) +
120 static_cast<double>(tap_count_) + 2.0;
121 if (!std::isfinite(history_frames) ||
122 history_frames >
123 static_cast<double>(std::numeric_limits<std::size_t>::max())) {
124 release();
126 }
127 history_capacity_ = static_cast<std::size_t>(history_frames);
128 if (channels_ > std::numeric_limits<std::size_t>::max() /
129 history_capacity_) {
130 release();
132 }
133 try {
134 history_.assign(channels_ * history_capacity_, 0.0f);
135 } catch (...) {
136 release();
138 }
139 variable_ratio_ = true;
141 }
142
143 void release() noexcept {
144 std::vector<float>().swap(history_);
145 selection_ = {};
146 variable_bank_ = {};
147 ratio_ = 0.0;
148 maximum_ratio_ = 0.0;
149 minimum_ratio_ = 0.0;
150 fixed_latency_output_frames_ = 0.0;
151 step_fraction_ = 0.0;
152 step_integer_ = 0;
153 channels_ = 0;
154 tap_count_ = 0;
155 history_capacity_ = 0;
156 half_width_ = 0;
157 prepared_ = false;
158 identity_ = false;
159 variable_ratio_ = false;
160 started_ = false;
161 accepted_frames_ = 0;
162 center_index_ = 0;
163 center_fraction_ = 0.0;
164 history_head_ = 0;
165 history_count_ = 0;
166 history_start_ = 0;
167 acquisition_index_ = 0;
168 acquisition_fraction_ = 0.0;
169 active_ratio_ = 0.0;
170 source_end_frame_ = 0;
171 last_valid_output_frames_ = 0;
172 source_ended_ = false;
173 }
174
175 void reset() noexcept {
176 if (!prepared_)
177 return;
178 std::fill(history_.begin(), history_.end(), 0.0f);
179 accepted_frames_ = 0;
180 center_index_ = identity_ ? 0 : -static_cast<std::int64_t>(half_width_);
181 center_fraction_ = 0.0;
182 history_head_ = 0;
183 history_count_ = 0;
184 history_start_ = 0;
185 started_ = false;
186 acquisition_index_ = 0;
187 acquisition_fraction_ = 0.0;
188 active_ratio_ = 0.0;
189 source_end_frame_ = 0;
190 last_valid_output_frames_ = 0;
191 source_ended_ = false;
192 }
193
194 bool prepared() const noexcept { return prepared_; }
195 bool identity() const noexcept { return identity_; }
196 double ratio() const noexcept { return ratio_; }
197 std::uint32_t half_width() const noexcept { return half_width_; }
198
199 SampleHeritageSrcPlan plan(std::size_t output_frames) const noexcept {
200 return plan(output_frames, ratio_);
201 }
202
203 SampleHeritageSrcPlan plan(std::size_t output_frames,
204 double source_frames_per_output) const noexcept {
206 result.output_frames = output_frames;
207 if (!prepared_) return result;
208 if (!(source_frames_per_output > 0.0) ||
209 !std::isfinite(source_frames_per_output) ||
210 (!variable_ratio_ && source_frames_per_output != ratio_) ||
211 (variable_ratio_ && source_frames_per_output > maximum_ratio_)) {
213 return result;
214 }
216 if (identity_) {
217 result.input_frames = output_frames;
218 return result;
219 }
220 if (variable_ratio_) {
221 auto center = center_index_;
222 auto fraction = center_fraction_;
223 if (!adjust_position_for_ratio(source_frames_per_output, center,
224 fraction)) {
226 return result;
227 }
228 auto acquisition = acquisition_index_;
229 auto acquisition_fraction = acquisition_fraction_;
230 for (std::size_t frame = 0; frame < output_frames; ++frame) {
231 if (!advance(acquisition, acquisition_fraction,
232 source_frames_per_output)) {
234 return result;
235 }
236 }
237 auto required_through = static_cast<std::uint64_t>(acquisition);
238 if (acquisition_fraction > 0.0) ++required_through;
239 if (required_through > accepted_frames_) {
240 const auto needed = required_through - accepted_frames_;
241 if (needed > std::numeric_limits<std::size_t>::max()) {
243 return result;
244 }
245 result.input_frames = static_cast<std::size_t>(needed);
246 }
247 return result;
248 }
249 auto center = center_index_;
250 auto fraction = center_fraction_;
251 initial_position(source_frames_per_output, center, fraction);
252 if (!adjust_position_for_ratio(source_frames_per_output, center,
253 fraction)) {
255 return result;
256 }
257 std::int64_t highest = -1;
258 for (std::size_t frame = 0; frame < output_frames; ++frame) {
259 if (center > std::numeric_limits<std::int64_t>::max() -
260 static_cast<std::int64_t>(half_width_)) {
262 return result;
263 }
264 highest = center + static_cast<std::int64_t>(half_width_);
265 if (!advance(center, fraction, source_frames_per_output)) {
267 return result;
268 }
269 }
270 if (highest < 0) return result;
271 const auto required_through = static_cast<std::uint64_t>(highest) + 1u;
272 if (required_through > accepted_frames_) {
273 const auto needed = required_through - accepted_frames_;
274 if (needed > std::numeric_limits<std::size_t>::max()) {
276 return result;
277 }
278 result.input_frames = static_cast<std::size_t>(needed);
279 }
280 return result;
281 }
282
284 BufferView<float> output) noexcept {
285 return process(input, output, ratio_);
286 }
287
289 double source_frames_per_output) noexcept {
290 return process_impl(input, output, source_frames_per_output, input.num_samples(), false,
291 false);
292 }
293
297 BufferView<float> output, std::size_t valid_input_frames,
298 bool end_of_source) noexcept {
299 return process_impl(input, output, ratio_, valid_input_frames, end_of_source, true);
300 }
301
303 const BufferView<const float>& input, BufferView<float> output,
304 std::size_t valid_input_frames, bool end_of_source,
305 double source_frames_per_output) noexcept {
306 return process_impl(input, output, source_frames_per_output,
307 valid_input_frames, end_of_source, true);
308 }
309
310 std::size_t last_valid_output_frames() const noexcept {
311 return last_valid_output_frames_;
312 }
313
314 std::uint64_t remaining_valid_output_frames() const noexcept {
315 if (!source_ended_ || identity_ || source_end_frame_ == 0)
316 return 0;
317 const auto next_position =
318 static_cast<long double>(center_index_) + static_cast<long double>(center_fraction_);
319 const auto end_position = static_cast<long double>(source_end_frame_) -
320 static_cast<long double>(selection_.wider.first_offset());
321 const auto distance = end_position - next_position;
322 if (!(distance > 0.0L))
323 return 0;
324 const auto frames = std::ceil(distance / static_cast<long double>(ratio_));
325 if (!std::isfinite(frames) ||
326 frames >= static_cast<long double>(std::numeric_limits<std::uint64_t>::max())) {
327 return std::numeric_limits<std::uint64_t>::max();
328 }
329 return static_cast<std::uint64_t>(frames);
330 }
331
332 bool source_drained() const noexcept {
333 return source_ended_ && remaining_valid_output_frames() == 0;
334 }
335
336 private:
337 SampleHeritageSrcStatus process_impl(const BufferView<const float>& input,
338 BufferView<float> output, double source_frames_per_output,
339 std::size_t valid_input_frames, bool end_of_source,
340 bool finite_source) noexcept {
341 const auto expected = plan(output.num_samples(), source_frames_per_output);
342 if (!expected.valid())
343 return expected.status;
344 if (input.num_channels() != channels_ || output.num_channels() != channels_) {
346 }
347 if (input.num_samples() != expected.input_frames)
349 if (finite_source && (valid_input_frames > input.num_samples() ||
350 (!end_of_source && valid_input_frames != input.num_samples()) ||
351 (source_ended_ && valid_input_frames != 0))) {
353 }
354 last_valid_output_frames_ = output.num_samples();
355 if (finite_source && !source_ended_) {
356 if (valid_input_frames > std::numeric_limits<std::uint64_t>::max() - accepted_frames_) {
358 }
359 source_end_frame_ = accepted_frames_ + valid_input_frames;
360 source_ended_ = end_of_source;
361 }
362 if (finite_source && source_ended_)
363 last_valid_output_frames_ = 0;
364
365 if (identity_) {
366 for (std::size_t channel = 0; channel < channels_; ++channel)
367 std::copy(input.channel(channel).begin(), input.channel(channel).end(),
368 output.channel(channel).begin());
369 if (finite_source && source_ended_)
370 last_valid_output_frames_ = valid_input_frames;
372 }
373 if (input.num_samples() > std::numeric_limits<std::uint64_t>::max() - accepted_frames_) {
375 }
376
377 std::array<float, 128> taps{};
378 auto center = center_index_;
379 auto fraction = center_fraction_;
380 initial_position(source_frames_per_output, center, fraction);
381 if (!adjust_position_for_ratio(source_frames_per_output, center, fraction))
383 const auto new_input_start = accepted_frames_;
384 if (output.num_samples() != 0) {
385 const auto first_required =
386 center + static_cast<std::int64_t>(selection_.wider.first_offset());
387 if (first_required >= 0 &&
388 static_cast<std::uint64_t>(first_required) < new_input_start &&
389 static_cast<std::uint64_t>(first_required) < history_start_) {
391 }
392 }
393 for (std::size_t frame = 0; frame < output.num_samples(); ++frame) {
394 if (finite_source && source_ended_ && source_end_frame_ != 0) {
395 const auto first_required =
396 center + static_cast<std::int64_t>(selection_.wider.first_offset());
397 const auto last_required =
398 first_required + static_cast<std::int64_t>(tap_count_ - 1);
399 if (last_required >= 0 &&
400 (first_required < 0 ||
401 static_cast<std::uint64_t>(first_required) < source_end_frame_)) {
402 last_valid_output_frames_ = frame + 1;
403 }
404 }
405 for (std::size_t channel = 0; channel < channels_; ++channel) {
406 for (std::size_t tap = 0; tap < tap_count_; ++tap) {
407 const auto source_index =
408 center + static_cast<std::int64_t>(selection_.wider.first_offset()) +
409 static_cast<std::int64_t>(tap);
410 taps[tap] = source_sample(channel, source_index, input, new_input_start);
411 }
412 const auto selection =
413 variable_ratio_ ? variable_bank_.select(source_frames_per_output) : selection_;
414 if (!selection.valid())
416 output.channel(channel)[frame] = selection.apply(
417 std::span<const float>(taps.data(), tap_count_), fraction);
418 }
419 if (!advance(center, fraction, source_frames_per_output))
421 }
422
423 append_history(input);
424 accepted_frames_ += input.num_samples();
425 center_index_ = center;
426 center_fraction_ = fraction;
427 if (variable_ratio_) {
428 for (std::size_t frame = 0; frame < output.num_samples(); ++frame) {
429 if (!advance(acquisition_index_, acquisition_fraction_,
430 source_frames_per_output))
432 }
433 }
434 started_ = true;
435 active_ratio_ = source_frames_per_output;
437 }
438
439 void initial_position(double ratio, std::int64_t& center,
440 double& fraction) const noexcept {
441 if (!variable_ratio_ || started_) return;
442 const auto position = -fixed_latency_output_frames_ * ratio;
443 const auto integer = std::floor(position);
444 center = static_cast<std::int64_t>(integer);
445 fraction = position - integer;
446 }
447
448 bool adjust_position_for_ratio(double ratio, std::int64_t& center,
449 double& fraction) const noexcept {
450 if (!variable_ratio_ || !started_ || ratio == active_ratio_) return true;
451 const auto position = static_cast<long double>(acquisition_index_) +
452 acquisition_fraction_ -
453 static_cast<long double>(fixed_latency_output_frames_) * ratio;
454 const auto integer = std::floor(position);
455 if (integer < static_cast<long double>(
456 std::numeric_limits<std::int64_t>::min()) ||
457 integer > static_cast<long double>(
458 std::numeric_limits<std::int64_t>::max()))
459 return false;
460 center = static_cast<std::int64_t>(integer);
461 fraction = static_cast<double>(position - integer);
462 return true;
463 }
464
465 bool advance(std::int64_t& center, double& fraction,
466 double ratio) const noexcept {
467 const auto integer_part = std::floor(ratio);
468 if (integer_part >
469 static_cast<double>(std::numeric_limits<std::int64_t>::max()))
470 return false;
471 const auto step_integer = static_cast<std::int64_t>(integer_part);
472 const auto step_fraction = ratio - integer_part;
473 auto next_fraction = fraction + step_fraction;
474 std::int64_t carry = 0;
475 if (next_fraction >= 1.0) {
476 next_fraction -= 1.0;
477 carry = 1;
478 }
479 if (step_integer > std::numeric_limits<std::int64_t>::max() - carry ||
480 center > std::numeric_limits<std::int64_t>::max() -
481 (step_integer + carry)) {
482 return false;
483 }
484 center += step_integer + carry;
485 fraction = next_fraction;
486 return true;
487 }
488
489 float source_sample(std::size_t channel,
490 std::int64_t source_index,
491 const BufferView<const float>& input,
492 std::uint64_t new_input_start) const noexcept {
493 if (source_index < 0) return 0.0f;
494 const auto absolute = static_cast<std::uint64_t>(source_index);
495 if (absolute >= new_input_start) {
496 const auto offset = absolute - new_input_start;
497 if (offset < input.num_samples())
498 return input.channel(channel)[static_cast<std::size_t>(offset)];
499 return 0.0f;
500 }
501 if (absolute < history_start_ ||
502 absolute - history_start_ >= history_count_) {
503 return 0.0f;
504 }
505 const auto offset = static_cast<std::size_t>(absolute - history_start_);
506 const auto slot = (history_head_ + offset) % history_capacity_;
507 return history_[channel * history_capacity_ + slot];
508 }
509
510 void append_history(const BufferView<const float>& input) noexcept {
511 for (std::size_t frame = 0; frame < input.num_samples(); ++frame) {
512 std::size_t slot = 0;
513 if (history_count_ < history_capacity_) {
514 slot = (history_head_ + history_count_) % history_capacity_;
515 ++history_count_;
516 } else {
517 slot = history_head_;
518 history_head_ = (history_head_ + 1) % history_capacity_;
519 ++history_start_;
520 }
521 for (std::size_t channel = 0; channel < channels_; ++channel)
522 history_[channel * history_capacity_ + slot] = input.channel(channel)[frame];
523 }
524 }
525
526 std::vector<float> history_;
527 SampleSincKernelSelection selection_{};
528 SampleSincKernelBankView variable_bank_{};
529 double ratio_ = 0.0;
530 double maximum_ratio_ = 0.0;
531 double minimum_ratio_ = 0.0;
532 double fixed_latency_output_frames_ = 0.0;
533 double step_fraction_ = 0.0;
534 std::int64_t step_integer_ = 0;
535 std::size_t channels_ = 0;
536 std::size_t tap_count_ = 0;
537 std::size_t history_capacity_ = 0;
538 std::uint32_t half_width_ = 0;
539 bool prepared_ = false;
540 bool identity_ = false;
541 bool variable_ratio_ = false;
542 bool started_ = false;
543 std::uint64_t accepted_frames_ = 0;
544 std::int64_t center_index_ = 0;
545 double center_fraction_ = 0.0;
546 std::size_t history_head_ = 0;
547 std::size_t history_count_ = 0;
548 std::uint64_t history_start_ = 0;
549 std::int64_t acquisition_index_ = 0;
550 double acquisition_fraction_ = 0.0;
551 double active_ratio_ = 0.0;
552 std::uint64_t source_end_frame_ = 0;
553 std::size_t last_valid_output_frames_ = 0;
554 bool source_ended_ = false;
555};
556
557} // namespace pulp::audio
Non-owning view over multi-channel audio sample data.
Definition buffer.hpp:30
SampleHeritageSrcPlan plan(std::size_t output_frames, double source_frames_per_output) const noexcept
Definition sample_heritage_src.hpp:203
SampleHeritageCausalSrc(SampleHeritageCausalSrc &&)=delete
SampleHeritageSrcStatus process(const BufferView< const float > &input, BufferView< float > output) noexcept
Definition sample_heritage_src.hpp:283
bool identity() const noexcept
Definition sample_heritage_src.hpp:195
std::uint64_t remaining_valid_output_frames() const noexcept
Definition sample_heritage_src.hpp:314
SampleHeritageCausalSrc & operator=(SampleHeritageCausalSrc &&)=delete
bool prepared() const noexcept
Definition sample_heritage_src.hpp:194
void release() noexcept
Definition sample_heritage_src.hpp:143
double ratio() const noexcept
Definition sample_heritage_src.hpp:196
SampleHeritageSrcStatus prepare(double source_frames_per_output, std::size_t channel_count, SampleSincKernelSelection selection, bool identity) noexcept
Definition sample_heritage_src.hpp:52
SampleHeritageSrcStatus process_source(const BufferView< const float > &input, BufferView< float > output, std::size_t valid_input_frames, bool end_of_source) noexcept
Tracks the valid prefix of a zero-padded final input block and exposes every causal output frame whos...
Definition sample_heritage_src.hpp:296
SampleHeritageCausalSrc & operator=(const SampleHeritageCausalSrc &)=delete
std::uint32_t half_width() const noexcept
Definition sample_heritage_src.hpp:197
SampleHeritageCausalSrc(const SampleHeritageCausalSrc &)=delete
bool source_drained() const noexcept
Definition sample_heritage_src.hpp:332
SampleHeritageSrcStatus process(const BufferView< const float > &input, BufferView< float > output, double source_frames_per_output) noexcept
Definition sample_heritage_src.hpp:288
SampleHeritageSrcPlan plan(std::size_t output_frames) const noexcept
Definition sample_heritage_src.hpp:199
SampleHeritageSrcStatus process_source(const BufferView< const float > &input, BufferView< float > output, std::size_t valid_input_frames, bool end_of_source, double source_frames_per_output) noexcept
Definition sample_heritage_src.hpp:302
void reset() noexcept
Definition sample_heritage_src.hpp:175
SampleHeritageSrcStatus prepare_variable(double default_source_frames_per_output, double minimum_source_frames_per_output, double maximum_source_frames_per_output, std::size_t channel_count, SampleSincKernelBankView bank) noexcept
Definition sample_heritage_src.hpp:95
std::size_t last_valid_output_frames() const noexcept
Definition sample_heritage_src.hpp:310
Definition analyzer_provider.hpp:12
@ AllocationFailed
Definition finite_time_stretch.hpp:27
SampleHeritageSrcStatus
Definition sample_heritage_src.hpp:18
@ SizeOverflow
Definition sample_heritage_src.hpp:22
@ HistoryUnavailable
Definition sample_heritage_src.hpp:26
@ InvalidDimensions
Definition sample_heritage_src.hpp:24
@ NotPrepared
Definition sample_heritage_src.hpp:20
@ InputFrameMismatch
Definition sample_heritage_src.hpp:25
@ Ok
Definition sample_heritage_src.hpp:19
@ AllocationFailed
Definition sample_heritage_src.hpp:23
@ InvalidConfiguration
Definition sample_heritage_src.hpp:21
@ HistoryUnavailable
Definition sample_heritage_pitch.hpp:23
@ SizeOverflow
Definition sample_heritage_engine.hpp:44
@ InvalidDimensions
Definition sample_heritage_bus_dsp.hpp:23
@ NotPrepared
Definition sample_heritage_bus_dsp.hpp:21
@ Ok
Definition loop_types.hpp:34
@ InputFrameMismatch
Definition sample_heritage_engine.hpp:86
@ InvalidConfiguration
Definition sample_heritage_live_cyclic.hpp:24
constexpr std::size_t kSampleHeritageMaximumChannels
Definition sample_heritage_schema.hpp:27
@ center
Definition canvas.hpp:221
@ selection
Definition rmlui_host_bridge.hpp:27
Definition sample_heritage_src.hpp:29
SampleHeritageSrcStatus status
Definition sample_heritage_src.hpp:30
std::size_t input_frames
Definition sample_heritage_src.hpp:32
std::size_t output_frames
Definition sample_heritage_src.hpp:31
bool valid() const noexcept
Definition sample_heritage_src.hpp:34
Definition sample_sinc_kernel.hpp:229
Definition sample_sinc_kernel.hpp:202