Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
beat_repeat_kernel.hpp
Go to the documentation of this file.
1#pragma once
2
17
22
23#include <algorithm>
24#include <cmath>
25#include <cstddef>
26#include <cstdint>
27#include <limits>
28#include <new>
29#include <stdexcept>
30#include <utility>
31#include <vector>
32
33namespace pulp::signal {
34
47
55
59 constexpr explicit operator bool() const noexcept {
61 }
62};
63
64namespace detail {
65
66inline bool beat_repeat_checked_add(std::int64_t lhs, std::int64_t rhs,
67 std::int64_t& result) noexcept {
68 constexpr auto minimum = std::numeric_limits<std::int64_t>::min();
69 constexpr auto maximum = std::numeric_limits<std::int64_t>::max();
70 if ((rhs > 0 && lhs > maximum - rhs) || (rhs < 0 && lhs < minimum - rhs))
71 return false;
72 result = lhs + rhs;
73 return true;
74}
75
76inline bool beat_repeat_checked_subtract(std::int64_t lhs, std::int64_t rhs,
77 std::int64_t& result) noexcept {
78 constexpr auto minimum = std::numeric_limits<std::int64_t>::min();
79 constexpr auto maximum = std::numeric_limits<std::int64_t>::max();
80 if ((rhs > 0 && lhs < minimum + rhs) || (rhs < 0 && lhs > maximum + rhs))
81 return false;
82 result = lhs - rhs;
83 return true;
84}
85
86inline bool beat_repeat_ceil_tick(std::int64_t value, std::int64_t quantum,
87 std::int64_t& result) noexcept {
88 if (quantum <= 0)
89 return false;
90 const auto remainder = value % quantum;
91 if (remainder == 0) {
92 result = value;
93 return true;
94 }
95 const auto delta = remainder < 0 ? -remainder : quantum - remainder;
96 return beat_repeat_checked_add(value, delta, result);
97}
98
99} // namespace detail
100
104inline BeatRepeatPlanResult
106 timebase::SamplePosition requested_sample) noexcept {
107 const auto quantum_result = timebase::division_ticks(division);
108 if (!quantum_result)
110 const auto quantum = quantum_result.value().value;
111 auto candidate_source = tempo.samples_to_ticks(requested_sample).value;
112 std::int64_t candidate = 0;
113 if (!detail::beat_repeat_ceil_tick(candidate_source, quantum, candidate))
115
116 auto edge = tempo.ticks_to_samples({candidate});
117 if (edge.value == std::numeric_limits<std::int64_t>::min() ||
118 edge.value == std::numeric_limits<std::int64_t>::max())
120 if (edge.value <= requested_sample.value) {
121 const auto candidate_bits = static_cast<std::uint64_t>(candidate);
122 const auto maximum_bits =
123 static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max());
124 const auto maximum_steps =
125 (maximum_bits - candidate_bits) / static_cast<std::uint64_t>(quantum);
126 if (maximum_steps == 0)
128 const auto tick_at_step = [&](std::uint64_t step) noexcept {
129 const auto offset = step * static_cast<std::uint64_t>(quantum);
130 return static_cast<std::int64_t>(candidate_bits + offset);
131 };
132
133 std::uint64_t lower_step = 0;
134 std::uint64_t upper_step = 1;
135 for (;;) {
136 upper_step = std::min(upper_step, maximum_steps);
137 const auto probe = tick_at_step(upper_step);
138 edge = tempo.ticks_to_samples({probe});
139 if (edge.value > requested_sample.value)
140 break;
141 if (upper_step == maximum_steps)
143 lower_step = upper_step;
144 upper_step = upper_step > maximum_steps / 2 ? maximum_steps : upper_step * 2;
145 }
146 while (upper_step - lower_step > 1) {
147 const auto middle_step = lower_step + (upper_step - lower_step) / 2;
148 const auto middle = tick_at_step(middle_step);
149 if (tempo.ticks_to_samples({middle}).value <= requested_sample.value)
150 lower_step = middle_step;
151 else
152 upper_step = middle_step;
153 }
154 candidate = tick_at_step(upper_step);
155 edge = tempo.ticks_to_samples({candidate});
156 }
157
158 if (edge.value == std::numeric_limits<std::int64_t>::min() ||
159 edge.value == std::numeric_limits<std::int64_t>::max())
161
162 std::int64_t previous_tick = 0;
163 if (!detail::beat_repeat_checked_subtract(candidate, quantum, previous_tick))
165 const auto previous_sample = tempo.ticks_to_samples({previous_tick});
166 if (previous_sample.value == std::numeric_limits<std::int64_t>::min() ||
167 previous_sample.value == std::numeric_limits<std::int64_t>::max())
169 std::int64_t capture_frames = 0;
170 if (!detail::beat_repeat_checked_subtract(edge.value, previous_sample.value, capture_frames) ||
171 capture_frames <= 0 || capture_frames > std::numeric_limits<int>::max())
173 return {{requested_sample, edge, {candidate}, static_cast<int>(capture_frames)},
175}
176
177template <typename SampleType = float> class BeatRepeatKernelT {
178 public:
179 enum class ReverseMode {
183 };
184
192
199
206
207 struct Snapshot {
208 bool resumable = true;
211 SampleType gate = SampleType{1};
215 bool alternate_repeat = false;
216 bool active = false;
222 std::vector<SampleType> sampler;
223 std::vector<SampleType> last_output;
224 std::vector<SampleType> transition_old;
225 };
226
227 BeatRepeatKernelT() = default;
231 swap(other);
232 }
234 if (this != &other) {
235 BeatRepeatKernelT replacement(std::move(other));
236 swap(replacement);
237 }
238 return *this;
239 }
240
241 bool prepare(const Config& config) {
242 if (config.channels <= 0 || config.history_capacity_frames <= 0 ||
243 config.transition_frames < 0 || !config.sample_rate.valid())
244 return false;
245 const auto normalized_rate = config.sample_rate.normalized();
246
247#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
248 try {
249#endif
250 FreezeLoopSamplerT<SampleType> replacement_sampler;
251 replacement_sampler.prepare(config.channels, config.history_capacity_frames, 0);
252 if (replacement_sampler.channels() != config.channels ||
253 replacement_sampler.capacity() != config.history_capacity_frames)
254 return false;
255 std::vector<const SampleType*> replacement_inputs(
256 static_cast<std::size_t>(config.channels), nullptr);
257 std::vector<SampleType> replacement_last(static_cast<std::size_t>(config.channels),
258 SampleType{});
259 std::vector<SampleType> replacement_transition(
260 static_cast<std::size_t>(config.channels), SampleType{});
261
262 sampler_ = std::move(replacement_sampler);
263 history_inputs_.swap(replacement_inputs);
264 last_output_.swap(replacement_last);
265 transition_old_.swap(replacement_transition);
266 transition_frames_ = config.transition_frames;
267 sample_rate_ = normalized_rate;
268 prepared_ = true;
269 reset();
270 return true;
271#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
272 } catch (const std::bad_alloc&) {
273 return false;
274 } catch (const std::length_error&) {
275 return false;
276 }
277#endif
278 }
279
280 bool prepared() const noexcept {
281 return prepared_;
282 }
283 int channels() const noexcept {
284 return sampler_.channels();
285 }
286 int history_capacity_frames() const noexcept {
287 return sampler_.capacity();
288 }
289 int available_history_frames() const noexcept {
290 return sampler_.available_history();
291 }
292 Status status() const noexcept {
293 return status_;
294 }
295 bool last_capture_rejected() const noexcept {
296 return last_capture_rejected_;
297 }
298 bool can_snapshot() const noexcept {
299 return !pending_capture_;
300 }
301 int loop_length() const noexcept {
302 return active_loop_ ? sampler_.loop_length() : 0;
303 }
304 int loop_position() const noexcept {
305 return loop_position_;
306 }
307 int completed_repeats() const noexcept {
308 return completed_repeats_;
309 }
310 int latency_samples() const noexcept {
311 return 0;
312 }
313 int max_tail_samples() const noexcept {
314 return active_transition_frames();
315 }
316 std::size_t retained_bytes() const noexcept {
317 return sampler_.retained_bytes() + (history_inputs_.capacity() * sizeof(const SampleType*) +
318 last_output_.capacity() * sizeof(SampleType) +
319 transition_old_.capacity() * sizeof(SampleType));
320 }
321
322 bool set_repeat_count(int count) noexcept {
323 if (count < 0)
324 return false;
325 repeat_count_ = count;
326 if (active_loop_ && status_ != Status::Releasing && count > 0 &&
327 completed_repeats_ >= count) {
328 pending_capture_ = false;
329 status_ = Status::Releasing;
330 begin_held_transition(TransitionKind::HeldToDry);
331 }
332 return true;
333 }
334
335 bool set_gate(SampleType duty) noexcept {
336 if (!std::isfinite(static_cast<double>(duty)))
337 return false;
338 gate_ = std::clamp(duty, SampleType{}, SampleType{1});
339 return true;
340 }
341
342 bool set_reverse(ReverseMode mode) noexcept {
343 if (mode != ReverseMode::Off && mode != ReverseMode::On && mode != ReverseMode::Alternate)
344 return false;
345 if (mode != reverse_ && active_loop_ && status_ != Status::Releasing)
346 begin_held_transition(TransitionKind::HeldToWet);
347 reverse_ = mode;
348 return true;
349 }
350
351 bool set_transition_frames(int frames) noexcept {
352 if (frames < 0)
353 return false;
354 if (frames != transition_frames_ && transition_kind_ != TransitionKind::None)
355 return false;
356 transition_frames_ = frames;
357 return true;
358 }
359
361 timebase::BeatDivision division,
362 timebase::SamplePosition requested_sample) noexcept {
363 if (!prepared_)
365 if (tempo.sample_rate().normalized() != sample_rate_)
367 if (status_ == Status::Releasing)
369 const auto lowered = lower_beat_repeat_capture(tempo, division, requested_sample);
370 if (!lowered)
371 return lowered;
372 if (lowered.plan.capture_frames > sampler_.capacity())
374 if (have_expected_sample_ && lowered.plan.edge_sample.value < expected_sample_)
376 pending_plan_ = lowered.plan;
377 pending_capture_ = true;
378 last_capture_rejected_ = false;
379 status_ = Status::Armed;
380 return lowered;
381 }
382
383 void stop() noexcept {
384 pending_capture_ = false;
385 if (!active_loop_) {
386 status_ = Status::Idle;
387 transition_kind_ = TransitionKind::None;
388 return;
389 }
390 if (transition_kind_ == TransitionKind::HeldToDry)
391 return;
392 status_ = Status::Releasing;
393 begin_held_transition(TransitionKind::HeldToDry);
394 }
395
396 bool seek(int captured_frame) noexcept {
397 if (!active_loop_ || status_ == Status::Releasing || sampler_.loop_length() <= 0)
398 return false;
399 const auto clamped = std::clamp(captured_frame, 0, sampler_.loop_length() - 1);
400 loop_position_ =
401 reverse_for_current_repeat() ? sampler_.loop_length() - 1 - clamped : clamped;
402 begin_held_transition(TransitionKind::HeldToWet);
403 return true;
404 }
405
406 void reset() noexcept {
407 sampler_.reset();
408 status_ = Status::Idle;
409 pending_capture_ = false;
410 active_loop_ = false;
411 last_capture_rejected_ = false;
412 loop_position_ = 0;
413 completed_repeats_ = 0;
414 alternate_repeat_ = false;
415 transition_kind_ = TransitionKind::None;
416 transition_position_ = 0;
417 have_expected_sample_ = false;
418 std::fill(last_output_.begin(), last_output_.end(), SampleType{});
419 std::fill(transition_old_.begin(), transition_old_.end(), SampleType{});
420 }
421
424 void transport_discontinuity() noexcept {
425 reset();
426 }
427
428 void process(const SampleType* const* input, SampleType* const* output, int frames,
429 timebase::SamplePosition block_start) noexcept {
430 if (!prepared_ || frames <= 0 || input == nullptr || output == nullptr)
431 return;
432 for (int channel = 0; channel < sampler_.channels(); ++channel) {
433 if (input[channel] == nullptr || output[channel] == nullptr)
434 return;
435 }
436 std::int64_t block_end = 0;
437 if (!detail::beat_repeat_checked_add(block_start.value, frames, block_end)) {
439 copy_dry(input, output, 0, frames);
440 return;
441 }
442 if (have_expected_sample_ && block_start.value != expected_sample_) {
444 }
445
446 int capture_offset = -1;
447 if (pending_capture_ && pending_plan_.edge_sample.value >= block_start.value &&
448 pending_plan_.edge_sample.value < block_end) {
449 capture_offset = static_cast<int>(pending_plan_.edge_sample.value - block_start.value);
450 } else if (pending_capture_ && pending_plan_.edge_sample.value < block_start.value) {
451 pending_capture_ = false;
452 status_ = active_loop_ ? Status::Active : Status::Idle;
453 }
454
455 if (capture_offset >= 0) {
456 write_history(input, 0, capture_offset);
457 render_segment(input, output, 0, capture_offset);
458 if (pending_capture_)
459 capture_pending();
460 write_history(input, capture_offset, frames - capture_offset);
461 render_segment(input, output, capture_offset, frames - capture_offset);
462 } else {
463 write_history(input, 0, frames);
464 render_segment(input, output, 0, frames);
465 }
466 expected_sample_ = block_end;
467 have_expected_sample_ = true;
468 }
469
470 Snapshot snapshot() const {
471 Snapshot result;
472 result.resumable = can_snapshot();
473 result.sample_rate = sample_rate_;
474 result.repeat_count = repeat_count_;
475 result.gate = gate_;
476 result.reverse = reverse_;
477 // A release is a transient route back to dry audio, not persistent
478 // gesture state. Serializing it as inactive prevents state recall from
479 // reviving a gesture that was already stopped.
480 result.active = active_loop_ && status_ != Status::Releasing;
481 result.alternate_repeat = result.active && alternate_repeat_;
482 result.last_capture_rejected = last_capture_rejected_;
483 result.transition_frames = transition_frames_;
484 if (result.active) {
485 result.loop_position = loop_position_;
486 result.completed_repeats = completed_repeats_;
487 result.sampler = sampler_.snapshot();
488 result.transition = transition_kind_;
489 result.transition_position = transition_position_;
490 result.transition_length = active_transition_frames();
491 result.transition_old = transition_old_;
492 result.last_output = last_output_;
493 } else {
494 result.last_output.assign(last_output_.size(), SampleType{});
495 }
496 return result;
497 }
498
499 bool restore(const Snapshot& value) {
500 if (!prepared_ || !value.resumable || value.repeat_count < 0 ||
501 !value.sample_rate.valid() || value.sample_rate.normalized() != sample_rate_ ||
502 value.transition_frames < 0 || !std::isfinite(static_cast<double>(value.gate)) ||
503 value.gate < SampleType{} || value.gate > SampleType{1} ||
504 (value.reverse != ReverseMode::Off && value.reverse != ReverseMode::On &&
505 value.reverse != ReverseMode::Alternate) ||
506 value.last_output.size() != last_output_.size())
507 return false;
508 if (!value.active &&
509 (!value.sampler.empty() || value.loop_position != 0 || value.completed_repeats != 0 ||
510 value.alternate_repeat || value.transition != TransitionKind::None ||
511 value.transition_position != 0 || value.transition_length != 0 ||
512 !value.transition_old.empty()))
513 return false;
514 for (const auto sample : value.last_output) {
515 if (!std::isfinite(static_cast<double>(sample)))
516 return false;
517 }
518 if (value.active) {
519 if (value.sampler.size() < 4 || !std::isfinite(static_cast<double>(value.sampler[0])) ||
520 !std::isfinite(static_cast<double>(value.sampler[1])) ||
521 value.sampler[0] != static_cast<SampleType>(sampler_.channels()) ||
522 value.sampler[1] < SampleType{1} ||
523 value.sampler[1] > static_cast<SampleType>(sampler_.capacity()) ||
524 std::trunc(value.sampler[1]) != value.sampler[1] || value.loop_position < 0 ||
525 value.loop_position >= static_cast<int>(value.sampler[1]) ||
526 value.completed_repeats < 0 ||
527 (value.completed_repeats < std::numeric_limits<int>::max() &&
528 value.alternate_repeat != (value.completed_repeats % 2 != 0)) ||
529 (value.repeat_count > 0 && value.completed_repeats >= value.repeat_count))
530 return false;
531 const auto loop_length = static_cast<int>(value.sampler[1]);
532 const auto expected_size =
533 static_cast<std::size_t>(4) + static_cast<std::size_t>(sampler_.channels()) *
534 static_cast<std::size_t>(loop_length);
535 if (value.sampler.size() != expected_size ||
536 !std::isfinite(static_cast<double>(value.sampler[3])) ||
537 std::trunc(value.sampler[3]) != value.sampler[3] ||
538 value.sampler[3] < SampleType{} ||
539 value.sampler[3] >= static_cast<SampleType>(loop_length))
540 return false;
541 const auto transition_length = std::min(value.transition_frames, loop_length / 2);
542 if ((value.transition != TransitionKind::None &&
543 value.transition != TransitionKind::DryToWet &&
544 value.transition != TransitionKind::HeldToWet) ||
545 value.transition_old.size() != transition_old_.size() ||
546 value.transition_position < 0 || value.transition_length < 0 ||
547 value.transition_length != transition_length ||
548 (value.transition == TransitionKind::None && value.transition_position != 0) ||
549 (value.transition != TransitionKind::None &&
550 (transition_length == 0 || value.transition_position >= transition_length)))
551 return false;
552 for (const auto sample : value.transition_old) {
553 if (!std::isfinite(static_cast<double>(sample)))
554 return false;
555 }
556 for (const auto sample : value.sampler) {
557 if (!std::isfinite(static_cast<double>(sample)))
558 return false;
559 }
560 if (!sampler_.restore(value.sampler))
561 return false;
562 } else {
563 sampler_.release();
564 }
565 sampler_.clear_history();
566 repeat_count_ = value.repeat_count;
567 gate_ = value.gate;
568 reverse_ = value.reverse;
569 transition_frames_ = value.transition_frames;
570 loop_position_ = value.active ? value.loop_position : 0;
571 completed_repeats_ = value.active ? value.completed_repeats : 0;
572 alternate_repeat_ = value.active && value.alternate_repeat;
573 active_loop_ = value.active;
574 last_capture_rejected_ = value.last_capture_rejected;
575 last_output_ = value.last_output;
576 transition_kind_ = value.active ? value.transition : TransitionKind::None;
577 transition_position_ = value.active ? value.transition_position : 0;
578 if (value.active)
579 transition_old_ = value.transition_old;
580 else
581 std::fill(transition_old_.begin(), transition_old_.end(), SampleType{});
582 pending_capture_ = false;
583 status_ = last_capture_rejected_ ? Status::CaptureRejectedInsufficientHistory
584 : (active_loop_ ? Status::Active : Status::Idle);
585 have_expected_sample_ = false;
586 return true;
587 }
588
589 private:
590 void swap(BeatRepeatKernelT& other) noexcept {
591 using std::swap;
592 swap(sampler_, other.sampler_);
593 swap(sample_rate_, other.sample_rate_);
594 swap(history_inputs_, other.history_inputs_);
595 swap(last_output_, other.last_output_);
596 swap(transition_old_, other.transition_old_);
597 swap(pending_plan_, other.pending_plan_);
598 swap(repeat_count_, other.repeat_count_);
599 swap(gate_, other.gate_);
600 swap(reverse_, other.reverse_);
601 swap(status_, other.status_);
602 swap(transition_kind_, other.transition_kind_);
603 swap(transition_frames_, other.transition_frames_);
604 swap(transition_position_, other.transition_position_);
605 swap(loop_position_, other.loop_position_);
606 swap(completed_repeats_, other.completed_repeats_);
607 swap(expected_sample_, other.expected_sample_);
608 swap(prepared_, other.prepared_);
609 swap(pending_capture_, other.pending_capture_);
610 swap(active_loop_, other.active_loop_);
611 swap(alternate_repeat_, other.alternate_repeat_);
612 swap(last_capture_rejected_, other.last_capture_rejected_);
613 swap(have_expected_sample_, other.have_expected_sample_);
614 }
615
616 int active_transition_frames() const noexcept {
617 if (!active_loop_ || sampler_.loop_length() <= 0)
618 return transition_frames_;
619 return std::min(transition_frames_, sampler_.loop_length() / 2);
620 }
621
622 void begin_held_transition(TransitionKind kind) noexcept {
623 std::copy(last_output_.begin(), last_output_.end(), transition_old_.begin());
624 transition_kind_ = active_transition_frames() == 0 ? TransitionKind::None : kind;
625 transition_position_ = 0;
626 if (transition_kind_ == TransitionKind::None && kind == TransitionKind::HeldToDry)
627 finish_release();
628 }
629
630 void capture_pending() noexcept {
631 pending_capture_ = false;
632 const auto capture = sampler_.try_freeze_recent(pending_plan_.capture_frames);
634 last_capture_rejected_ = true;
636 return;
637 }
638 const bool replacing = active_loop_;
639 active_loop_ = true;
640 loop_position_ = 0;
641 completed_repeats_ = 0;
642 alternate_repeat_ = false;
643 status_ = Status::Active;
644 last_capture_rejected_ = false;
645 if (replacing) {
646 begin_held_transition(TransitionKind::HeldToWet);
647 } else {
648 transition_kind_ =
649 active_transition_frames() == 0 ? TransitionKind::None : TransitionKind::DryToWet;
650 transition_position_ = 0;
651 }
652 }
653
654 void write_history(const SampleType* const* input, int offset, int frames) noexcept {
655 if (frames <= 0)
656 return;
657 for (int channel = 0; channel < sampler_.channels(); ++channel)
658 history_inputs_[static_cast<std::size_t>(channel)] = input[channel] + offset;
659 sampler_.write(history_inputs_.data(), frames);
660 }
661
662 void copy_dry(const SampleType* const* input, SampleType* const* output, int offset,
663 int frames) noexcept {
664 for (int channel = 0; channel < sampler_.channels(); ++channel) {
665 for (int frame = 0; frame < frames; ++frame)
666 output[channel][offset + frame] = finite_sample(input[channel][offset + frame]);
667 }
668 }
669
670 bool reverse_for_current_repeat() const noexcept {
671 return reverse_ == ReverseMode::On ||
672 (reverse_ == ReverseMode::Alternate && alternate_repeat_);
673 }
674
675 SampleType gate_gain() const noexcept {
676 const int length = sampler_.loop_length();
677 if (length <= 0 || gate_ <= SampleType{})
678 return SampleType{};
679 if (gate_ >= SampleType{1})
680 return SampleType{1};
681 const int audible = std::clamp(
682 static_cast<int>(std::llround(static_cast<double>(gate_) * length)), 0, length);
683 if (audible <= 0 || loop_position_ >= audible)
684 return SampleType{};
685 const int fade = std::min(active_transition_frames(), audible / 2);
686 if (fade <= 1)
687 return SampleType{1};
688 if (loop_position_ < fade) {
689 const auto t =
690 static_cast<SampleType>(loop_position_) / static_cast<SampleType>(fade - 1);
691 SampleType old_gain{};
692 SampleType new_gain{};
694 new_gain);
695 return new_gain;
696 }
697 if (loop_position_ >= audible - fade) {
698 const auto t = static_cast<SampleType>(loop_position_ - (audible - fade)) /
699 static_cast<SampleType>(fade - 1);
700 SampleType old_gain{};
701 SampleType new_gain{};
703 new_gain);
704 return old_gain;
705 }
706 return SampleType{1};
707 }
708
709 SampleType wet_sample(int channel) const noexcept {
710 const int length = sampler_.loop_length();
711 if (!active_loop_ || length <= 0)
712 return SampleType{};
713 const int index =
714 reverse_for_current_repeat() ? length - 1 - loop_position_ : loop_position_;
715 return sampler_.loop_sample(channel, index) * gate_gain();
716 }
717
718 void render_segment(const SampleType* const* input, SampleType* const* output, int offset,
719 int frames) noexcept {
720 for (int frame = 0; frame < frames; ++frame) {
721 SampleType old_gain{};
722 SampleType new_gain{SampleType{1}};
723 if (transition_kind_ != TransitionKind::None) {
724 const auto length = active_transition_frames();
725 const auto t = length <= 1 ? SampleType{1}
726 : static_cast<SampleType>(transition_position_) /
727 static_cast<SampleType>(length - 1);
729 new_gain);
730 }
731
732 for (int channel = 0; channel < sampler_.channels(); ++channel) {
733 const auto dry = finite_sample(input[channel][offset + frame]);
734 const auto wet = wet_sample(channel);
735 SampleType rendered = active_loop_ ? wet : dry;
736 switch (transition_kind_) {
738 break;
740 rendered = dry * old_gain + wet * new_gain;
741 break;
743 rendered = transition_old_[static_cast<std::size_t>(channel)] * old_gain +
744 wet * new_gain;
745 break;
747 rendered = transition_old_[static_cast<std::size_t>(channel)] * old_gain +
748 dry * new_gain;
749 break;
750 }
751 rendered = finite_sample(rendered);
752 output[channel][offset + frame] = rendered;
753 last_output_[static_cast<std::size_t>(channel)] = rendered;
754 }
755
756 advance_transition();
757 if (active_loop_)
758 advance_loop();
759 }
760 }
761
762 void advance_transition() noexcept {
763 if (transition_kind_ == TransitionKind::None)
764 return;
765 const auto completed_kind = transition_kind_;
766 if (++transition_position_ < active_transition_frames())
767 return;
768 transition_kind_ = TransitionKind::None;
769 transition_position_ = 0;
770 if (completed_kind == TransitionKind::HeldToDry)
771 finish_release();
772 }
773
774 static SampleType finite_sample(SampleType value) noexcept {
775 return std::isfinite(static_cast<double>(value)) ? value : SampleType{};
776 }
777
778 void advance_loop() noexcept {
779 if (++loop_position_ < sampler_.loop_length())
780 return;
781 loop_position_ = 0;
782 if (completed_repeats_ < std::numeric_limits<int>::max())
783 ++completed_repeats_;
784 alternate_repeat_ = !alternate_repeat_;
785 if (repeat_count_ > 0 && completed_repeats_ >= repeat_count_) {
786 pending_capture_ = false;
787 status_ = Status::Releasing;
788 if (transition_kind_ != TransitionKind::HeldToDry)
789 begin_held_transition(TransitionKind::HeldToDry);
790 return;
791 }
792 if (transition_kind_ == TransitionKind::None)
793 begin_held_transition(TransitionKind::HeldToWet);
794 }
795
796 void finish_release() noexcept {
797 active_loop_ = false;
798 sampler_.release();
799 status_ = pending_capture_ ? Status::Armed : Status::Idle;
800 loop_position_ = 0;
801 completed_repeats_ = 0;
802 alternate_repeat_ = false;
803 }
804
805 FreezeLoopSamplerT<SampleType> sampler_;
806 timebase::RationalRate sample_rate_{48'000, 1};
807 std::vector<const SampleType*> history_inputs_;
808 std::vector<SampleType> last_output_;
809 std::vector<SampleType> transition_old_;
810 BeatRepeatCapturePlan pending_plan_{};
811 int repeat_count_ = 4;
812 SampleType gate_ = SampleType{1};
813 ReverseMode reverse_ = ReverseMode::Off;
814 Status status_ = Status::Idle;
815 TransitionKind transition_kind_ = TransitionKind::None;
816 int transition_frames_ = 144;
817 int transition_position_ = 0;
818 int loop_position_ = 0;
819 int completed_repeats_ = 0;
820 std::int64_t expected_sample_ = 0;
821 bool prepared_ = false;
822 bool pending_capture_ = false;
823 bool active_loop_ = false;
824 bool alternate_repeat_ = false;
825 bool last_capture_rejected_ = false;
826 bool have_expected_sample_ = false;
827};
828
831
832} // namespace pulp::signal
Definition beat_repeat_kernel.hpp:177
bool set_gate(SampleType duty) noexcept
Definition beat_repeat_kernel.hpp:335
int loop_length() const noexcept
Definition beat_repeat_kernel.hpp:301
void transport_discontinuity() noexcept
A seek, loop jump, or clock-domain replacement invalidates both pending grid ownership and the contin...
Definition beat_repeat_kernel.hpp:424
BeatRepeatPlanResult trigger(const timebase::CompiledTempoMap &tempo, timebase::BeatDivision division, timebase::SamplePosition requested_sample) noexcept
Definition beat_repeat_kernel.hpp:360
bool last_capture_rejected() const noexcept
Definition beat_repeat_kernel.hpp:295
BeatRepeatKernelT(BeatRepeatKernelT &&other) noexcept
Definition beat_repeat_kernel.hpp:230
Status status() const noexcept
Definition beat_repeat_kernel.hpp:292
BeatRepeatKernelT & operator=(BeatRepeatKernelT &&other) noexcept
Definition beat_repeat_kernel.hpp:233
void stop() noexcept
Definition beat_repeat_kernel.hpp:383
bool seek(int captured_frame) noexcept
Definition beat_repeat_kernel.hpp:396
bool set_reverse(ReverseMode mode) noexcept
Definition beat_repeat_kernel.hpp:342
ReverseMode
Definition beat_repeat_kernel.hpp:179
@ On
Definition beat_repeat_kernel.hpp:181
@ Alternate
Definition beat_repeat_kernel.hpp:182
@ Off
Definition beat_repeat_kernel.hpp:180
int latency_samples() const noexcept
Definition beat_repeat_kernel.hpp:310
int history_capacity_frames() const noexcept
Definition beat_repeat_kernel.hpp:286
int channels() const noexcept
Definition beat_repeat_kernel.hpp:283
Snapshot snapshot() const
Definition beat_repeat_kernel.hpp:470
int max_tail_samples() const noexcept
Definition beat_repeat_kernel.hpp:313
int completed_repeats() const noexcept
Definition beat_repeat_kernel.hpp:307
void process(const SampleType *const *input, SampleType *const *output, int frames, timebase::SamplePosition block_start) noexcept
Definition beat_repeat_kernel.hpp:428
void reset() noexcept
Definition beat_repeat_kernel.hpp:406
int loop_position() const noexcept
Definition beat_repeat_kernel.hpp:304
std::size_t retained_bytes() const noexcept
Definition beat_repeat_kernel.hpp:316
bool restore(const Snapshot &value)
Definition beat_repeat_kernel.hpp:499
bool prepare(const Config &config)
Definition beat_repeat_kernel.hpp:241
TransitionKind
Definition beat_repeat_kernel.hpp:193
@ None
Definition beat_repeat_kernel.hpp:194
@ DryToWet
Definition beat_repeat_kernel.hpp:195
@ HeldToDry
Definition beat_repeat_kernel.hpp:197
@ HeldToWet
Definition beat_repeat_kernel.hpp:196
bool can_snapshot() const noexcept
Definition beat_repeat_kernel.hpp:298
BeatRepeatKernelT(const BeatRepeatKernelT &)=delete
bool set_transition_frames(int frames) noexcept
Definition beat_repeat_kernel.hpp:351
Status
Definition beat_repeat_kernel.hpp:185
@ Active
Definition beat_repeat_kernel.hpp:189
@ Releasing
Definition beat_repeat_kernel.hpp:190
@ Armed
Definition beat_repeat_kernel.hpp:187
@ CaptureRejectedInsufficientHistory
Definition beat_repeat_kernel.hpp:188
@ Idle
Definition beat_repeat_kernel.hpp:186
BeatRepeatKernelT & operator=(const BeatRepeatKernelT &)=delete
bool set_repeat_count(int count) noexcept
Definition beat_repeat_kernel.hpp:322
bool prepared() const noexcept
Definition beat_repeat_kernel.hpp:280
int available_history_frames() const noexcept
Definition beat_repeat_kernel.hpp:289
Definition freeze_loop_sampler.hpp:33
@ Success
Definition freeze_loop_sampler.hpp:38
void prepare(int channels, int capacity, int crossfade)
Definition freeze_loop_sampler.hpp:52
int capacity() const noexcept
Definition freeze_loop_sampler.hpp:98
int channels() const noexcept
Definition freeze_loop_sampler.hpp:95
Definition compiled_tempo_map.hpp:91
Capture-and-loop "freeze": snapshot a slice of recent audio and play it back as a seamless loop.
@ index
Definition gpu_pass.hpp:140
bool beat_repeat_checked_add(std::int64_t lhs, std::int64_t rhs, std::int64_t &result) noexcept
Definition beat_repeat_kernel.hpp:66
bool beat_repeat_checked_subtract(std::int64_t lhs, std::int64_t rhs, std::int64_t &result) noexcept
Definition beat_repeat_kernel.hpp:76
bool beat_repeat_ceil_tick(std::int64_t value, std::int64_t quantum, std::int64_t &result) noexcept
Definition beat_repeat_kernel.hpp:86
Definition additive_bank.hpp:218
BeatRepeatPlanError
Definition beat_repeat_kernel.hpp:35
@ InvalidDivision
Definition beat_repeat_kernel.hpp:37
@ StaleRequest
Definition beat_repeat_kernel.hpp:45
@ ReleaseInProgress
Definition beat_repeat_kernel.hpp:44
@ None
Definition beat_repeat_kernel.hpp:36
@ SampleRateMismatch
Definition beat_repeat_kernel.hpp:43
@ HistoryCapacityExceeded
Definition beat_repeat_kernel.hpp:41
@ SampleRangeExceeded
Definition beat_repeat_kernel.hpp:39
@ Unprepared
Definition beat_repeat_kernel.hpp:42
@ CaptureRangeExceeded
Definition beat_repeat_kernel.hpp:40
@ TickRangeExceeded
Definition beat_repeat_kernel.hpp:38
BeatRepeatKernelT< double > BeatRepeatKernel64
Definition beat_repeat_kernel.hpp:830
BeatRepeatPlanResult lower_beat_repeat_capture(const timebase::CompiledTempoMap &tempo, timebase::BeatDivision division, timebase::SamplePosition requested_sample) noexcept
Lower the first grid edge strictly after requested_sample and the exact preceding grid interval into ...
Definition beat_repeat_kernel.hpp:105
@ EqualGain
(1−u, u) — amplitude sum == 1.
Definition crossfade.hpp:42
@ maximum
Definition spectrum_trace.hpp:34
constexpr T crossfade_smoothstep(T t) noexcept
Smoothstep progress shaping: cubic 3u²−2u³ with zero slope at both ends, clamped to [0,...
Definition crossfade.hpp:50
BeatRepeatKernelT< float > BeatRepeatKernel
Definition beat_repeat_kernel.hpp:829
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
BeatDivision
Definition beat_division.hpp:15
runtime::Result< TickDuration, BeatDivisionError > division_ticks(BeatDivision division) noexcept
Definition beat_division.hpp:88
@ value
the widget's primary value (Knob, Fader, RangeSlider, Toggle, ProgressBar)
Definition binding_diagnostics.hpp:19
@ t
Definition input_events.hpp:41
Definition beat_repeat_kernel.hpp:48
constexpr auto operator<=>(const BeatRepeatCapturePlan &) const =default
timebase::SamplePosition requested_sample
Definition beat_repeat_kernel.hpp:49
timebase::SamplePosition edge_sample
Definition beat_repeat_kernel.hpp:50
int capture_frames
Definition beat_repeat_kernel.hpp:52
timebase::TickPosition edge_tick
Definition beat_repeat_kernel.hpp:51
Definition beat_repeat_kernel.hpp:200
int transition_frames
Definition beat_repeat_kernel.hpp:203
int channels
Definition beat_repeat_kernel.hpp:201
timebase::RationalRate sample_rate
Definition beat_repeat_kernel.hpp:204
int history_capacity_frames
Definition beat_repeat_kernel.hpp:202
Definition beat_repeat_kernel.hpp:207
std::vector< SampleType > sampler
Definition beat_repeat_kernel.hpp:222
int loop_position
Definition beat_repeat_kernel.hpp:213
std::vector< SampleType > transition_old
Definition beat_repeat_kernel.hpp:224
int transition_position
Definition beat_repeat_kernel.hpp:220
int completed_repeats
Definition beat_repeat_kernel.hpp:214
int transition_frames
Definition beat_repeat_kernel.hpp:219
int repeat_count
Definition beat_repeat_kernel.hpp:210
bool resumable
Definition beat_repeat_kernel.hpp:208
bool last_capture_rejected
Definition beat_repeat_kernel.hpp:217
std::vector< SampleType > last_output
Definition beat_repeat_kernel.hpp:223
bool alternate_repeat
Definition beat_repeat_kernel.hpp:215
SampleType gate
Definition beat_repeat_kernel.hpp:211
timebase::RationalRate sample_rate
Definition beat_repeat_kernel.hpp:209
TransitionKind transition
Definition beat_repeat_kernel.hpp:218
bool active
Definition beat_repeat_kernel.hpp:216
ReverseMode reverse
Definition beat_repeat_kernel.hpp:212
int transition_length
Definition beat_repeat_kernel.hpp:221
Definition beat_repeat_kernel.hpp:56
BeatRepeatCapturePlan plan
Definition beat_repeat_kernel.hpp:57
BeatRepeatPlanError error
Definition beat_repeat_kernel.hpp:58
Definition rational_time.hpp:14
Definition rational_time.hpp:9
Definition tick.hpp:61
Shared click-free crossfade primitive.