370 bool prepare(
double sample_rate, Capacity capacity) {
373 clear_configuration();
374 if (!std::isfinite(sample_rate) || sample_rate <= 0.0 || capacity.max_wavesets <= 0 ||
375 capacity.max_waveset_samples <= 0 || !std::isfinite(capacity.max_normalize_ratio) ||
376 capacity.max_normalize_ratio < 1.0)
380 sample_rate,
static_cast<std::size_t
>(capacity.max_wavesets),
381 static_cast<std::size_t
>(capacity.max_waveset_samples),
382 capacity.max_normalize_ratio,
static_cast<std::size_t
>(
kMaxRepeatCount),
385 sizeof(ProgramStep), layout) ||
386 layout.
l >
static_cast<std::size_t
>(std::numeric_limits<int>::max()))
388 const auto n = layout.
n;
389 const auto m = layout.
m;
390 struct PreparedStorage {
391 std::unique_ptr<SampleType[]> capture, scratch, rotate;
392 std::unique_ptr<HeldSegment[]> held;
397 storage.capture.reset(
new (std::nothrow) SampleType[m]);
398 storage.scratch.reset(
new (std::nothrow) SampleType[layout.
e]);
399 storage.rotate.reset(
new (std::nothrow) SampleType[layout.
g]);
400 storage.held.reset(
new (std::nothrow) HeldSegment[n]);
401 if (!storage.programs.prepare(n, layout.
wcap) || !storage.ledger.prepare(n) ||
402 !storage.splicer.prepare(layout.
q) || !storage.capture || !storage.scratch ||
403 !storage.rotate || !storage.held)
405 capture_ = std::move(storage.capture);
406 scratch_ = std::move(storage.scratch);
407 rotate_storage_ = std::move(storage.rotate);
408 held_ = std::move(storage.held);
409 programs_ = std::move(storage.programs);
410 ledger_ = std::move(storage.ledger);
411 splicer_ = std::move(storage.splicer);
412 sample_rate_ = sample_rate;
413 capacity_ = capacity;
414 max_normalize_ratio_ = capacity.max_normalize_ratio;
415 max_segment_output_ = layout.
e;
416 max_lookahead_ =
static_cast<int>(layout.
l);
418 max_fade_ = layout.
f;
419 programs_[0].version = 1;
420 programs_[0].step_count = 1;
421 programs_.step(0, 0) = ProgramStep{};
422 programs_[0].rotate_window = 1;
423 programs_[0].permutation_count = 0;
424 programs_[0].configured =
true;
425 control_.store(pack_control(State::Quiescent, 0, 1u), std::memory_order_release);
430 return control_state(control_.load(std::memory_order_acquire)) != State::Unprepared;
432 bool set_program(std::uint8_t slot,
const OperationProgram& program) {
435 auto word = control_.load(std::memory_order_acquire);
437 if (control_state(word) != State::Quiescent)
439 const auto publishing = with_state(word, State::Publishing);
440 if (control_.compare_exchange_weak(word, publishing, std::memory_order_acq_rel,
441 std::memory_order_acquire)) {
442#if defined(PULP_WAVESET_TEST_SEAMS)
443 if (
const auto hook = publication_hook_.load(std::memory_order_acquire))
446 const bool published =
447 programs_.publish(slot, program, [
this](
const auto& candidate)
noexcept {
448 return validate_program(candidate);
450 auto published_control = control_.load(std::memory_order_acquire);
452 auto configured = control_configured(published_control);
455 const auto next_state = control_finish_requested(published_control)
458 const auto completed =
459 pack_control(next_state, control_slot(published_control),
configured);
460 if (control_.compare_exchange_weak(published_control, completed,
461 std::memory_order_acq_rel,
462 std::memory_order_acquire))
472 auto word = control_.load(std::memory_order_acquire);
474 const auto state = control_state(word);
475 if (control_finish_requested(word) ||
476 (
state != State::Quiescent &&
state != State::Live &&
state != State::Pushing) ||
477 (control_configured(word) & (std::uint32_t{1} << slot)) == 0)
480 pack_control(
state, slot, control_configured(word)) | (word & kFinishRequestedMask);
481 if (control_.compare_exchange_weak(word, desired, std::memory_order_acq_rel,
482 std::memory_order_acquire))
487 if (polarity < ZeroCrossingPolarity::Rising || polarity > ZeroCrossingPolarity::Both)
489 return update_control([&] { polarity_.store(
static_cast<std::uint8_t
>(polarity)); });
492 if (!std::isfinite(epsilon) || epsilon < SampleType{})
494 return update_control([&] { epsilon_.store(epsilon); });
499 return update_control([&] { crossfade_law_.store(
static_cast<std::uint8_t
>(law)); });
502 if (!std::isfinite(duration_ms) || duration_ms < SampleType{} ||
503 duration_ms > SampleType{20})
505 return update_control([&] { crossfade_ms_.store(duration_ms); });
508 if (!std::isfinite(
ratio) ||
ratio <= SampleType{} ||
509 static_cast<double>(
ratio) > max_normalize_ratio_)
511 return update_control([&] { normalize_ratio_.store(
ratio); });
513#if defined(PULP_WAVESET_TEST_SEAMS)
514 using StartupHook = void (*)();
515 static void set_startup_hook(StartupHook hook)
noexcept {
516 startup_hook_.store(hook);
518 using PublicationHook = void (*)();
519 static void set_publication_hook(PublicationHook hook)
noexcept {
520 publication_hook_.store(hook);
522 using FinishHook = void (*)();
523 static void set_finish_hook(FinishHook hook)
noexcept {
524 finish_hook_.store(hook);
526 using ResetHook = void (*)();
527 static void set_reset_hook(ResetHook hook)
noexcept {
528 reset_hook_.store(hook);
530 using ResetCompletionHook = void (*)();
531 static void set_reset_completion_hook(ResetCompletionHook hook)
noexcept {
532 reset_completion_hook_.store(hook);
534 using ControlCompletionHook = void (*)();
535 static void set_control_completion_hook(ControlCompletionHook hook)
noexcept {
536 control_completion_hook_.store(hook);
538 using DrainedSnapshotHook = void (*)();
539 static void set_drained_snapshot_hook(DrainedSnapshotHook hook)
noexcept {
540 drained_snapshot_hook_.store(hook);
544 return update_control([&] { coordinate_seed_.store(seed); });
546 int push(
const SampleType* input,
int count)
noexcept {
547 auto word = control_.load(std::memory_order_acquire);
548 auto state = control_state(word);
549 if ((
state != State::Quiescent &&
state != State::Live) || input ==
nullptr || count <= 0)
551 const bool starting =
state == State::Quiescent;
552 std::uint8_t startup_slot{};
553 if (
state == State::Quiescent) {
554 if (!std::isfinite(input[0]))
557 state = control_state(word);
558 if (
state != State::Quiescent)
560 startup_slot = control_slot(word);
562 pack_control(State::Pushing, startup_slot, control_configured(word));
563 if (control_.compare_exchange_weak(word, desired, std::memory_order_acq_rel,
564 std::memory_order_acquire))
567 if (!ensure_reservation()) {
568 word = control_.load(std::memory_order_acquire);
571 control_finish_requested(word) ? State::Finished : State::Quiescent;
572 const auto completed =
573 pack_control(target, control_slot(word), control_configured(word));
574 if (control_.compare_exchange_weak(word, completed, std::memory_order_acq_rel,
575 std::memory_order_acquire))
581 state = control_state(word);
582 if (
state != State::Live)
584 if (control_.compare_exchange_weak(word, with_state(word, State::Pushing),
585 std::memory_order_acq_rel,
586 std::memory_order_acquire))
591#if defined(PULP_WAVESET_TEST_SEAMS)
592 if (
const auto hook = startup_hook_.load(std::memory_order_acquire))
595 apply_program(startup_slot);
599 const auto sample = input[
accepted];
600 if (!std::isfinite(sample))
602 if (capture_size_ != 0 && is_boundary(sample)) {
603 finalize_capture(
false);
604 if (current_state() == State::Faulted)
606 if (!ensure_reservation())
608 }
else if (capture_size_ == 0 && !ensure_reservation()) {
611 if (capture_size_ == 0)
612 capture_source_begin_ = next_source_index_;
613 capture_[capture_size_++] = sample;
616 ++next_source_index_;
618 if (capture_size_ ==
static_cast<std::size_t
>(capacity_.max_waveset_samples))
619 finalize_capture(
true);
620 if (current_state() == State::Faulted)
623 word = control_.load(std::memory_order_acquire);
625 if (control_state(word) == State::Faulted)
627 if (control_finish_requested(word)) {
628 finish_owned_processing();
631 if (control_.compare_exchange_weak(word, with_state(word, State::Live),
632 std::memory_order_acq_rel,
633 std::memory_order_acquire))
637 int pull(SampleType* output,
int count)
noexcept {
638 if (output ==
nullptr || count <= 0)
640 auto word = control_.load(std::memory_order_acquire);
643 source = control_state(word);
644 if (source == State::Quiescent)
646 if (source != State::Live && source != State::Finished)
649 with_state(word, source == State::Finished ? State::Draining : State::Pulling);
650 if (control_.compare_exchange_weak(word, owned, std::memory_order_acq_rel,
651 std::memory_order_acquire))
654 const auto holdback = source == State::Finished || !splicer_.valid()
656 : std::min(max_fade_, splicer_.full_length() / 2u);
657 const auto amount = splicer_.pull(output,
static_cast<std::size_t
>(count), holdback);
658 word = control_.load(std::memory_order_acquire);
660 if (source == State::Live && control_finish_requested(word)) {
661 finish_owned_processing();
664 if (control_.compare_exchange_weak(word, with_state(word, source),
665 std::memory_order_acq_rel,
666 std::memory_order_acquire))
669 return static_cast<int>(amount);
672 auto word = control_.load(std::memory_order_acquire);
674 const auto state = control_state(word);
675 if (
state == State::Unprepared ||
state == State::Finished ||
676 state == State::Draining ||
state == State::Observing ||
state == State::Faulted ||
677 state == State::Finishing ||
state == State::Resetting)
679 if (
state == State::Publishing ||
state == State::Pushing ||
state == State::Pulling ||
680 state == State::Controlling) {
681 const auto desired = word | kFinishRequestedMask;
682 if (control_.compare_exchange_weak(word, desired, std::memory_order_acq_rel,
683 std::memory_order_acquire))
688 with_state(word,
state == State::Quiescent ? State::Finished : State::Finishing);
689 if (!control_.compare_exchange_weak(word, desired, std::memory_order_acq_rel,
690 std::memory_order_acquire))
692 if (
state == State::Quiescent)
696#if defined(PULP_WAVESET_TEST_SEAMS)
697 if (
const auto hook = finish_hook_.load(std::memory_order_acquire))
700 finish_owned_processing();
703 auto word = control_.load(std::memory_order_acquire);
705 if (control_state(word) != State::Finished)
707 if (control_.compare_exchange_weak(word, with_state(word, State::Observing),
708 std::memory_order_acq_rel,
709 std::memory_order_acquire))
712#if defined(PULP_WAVESET_TEST_SEAMS)
713 if (
const auto hook = drained_snapshot_hook_.load(std::memory_order_acquire))
716 const bool result = splicer_.size() == 0 && capture_size_ == 0 && held_count_ == 0;
717 word = control_.load(std::memory_order_acquire);
718 while (!control_.compare_exchange_weak(word, with_state(word, State::Finished),
719 std::memory_order_acq_rel,
720 std::memory_order_acquire)) {
725 auto word = control_.load(std::memory_order_acquire);
727 const auto state = control_state(word);
728 if (
state == State::Unprepared ||
state == State::Publishing ||
729 state == State::Pushing ||
state == State::Pulling ||
state == State::Draining ||
730 state == State::Observing ||
state == State::Controlling ||
731 state == State::Finishing ||
state == State::Resetting)
733 if (control_.compare_exchange_weak(word, with_state(word, State::Resetting),
734 std::memory_order_acq_rel,
735 std::memory_order_acquire))
738#if defined(PULP_WAVESET_TEST_SEAMS)
739 if (
const auto hook = reset_hook_.load(std::memory_order_acquire))
742 capture_size_ = held_count_ = 0;
744 accepted_total_ = next_source_index_ = completed_index_ = 0;
746 active_slot_ = programs_[0].configured ? 0 : first_configured_slot();
747 const auto configured = control_configured(control_.load(std::memory_order_relaxed));
750#if defined(PULP_WAVESET_TEST_SEAMS)
751 if (
const auto hook = reset_completion_hook_.load(std::memory_order_acquire))
754 control_.store(pack_control(State::Quiescent, active_slot_,
configured),
755 std::memory_order_release);
761 return prepared() ? max_lookahead_ : 0;
768 enum class State : std::uint8_t {
783 static constexpr std::uint32_t kStateShift = 3;
784 static constexpr std::uint32_t kSlotMask = 0x7u;
785 static constexpr std::uint32_t kConfiguredShift = 8;
786 static constexpr std::uint32_t kConfiguredMask = 0xffu;
787 static constexpr std::uint32_t kFinishRequestedMask = std::uint32_t{1} << 31;
788 static constexpr std::uint32_t pack_control(State state, std::uint8_t slot,
791 (
static_cast<std::uint32_t
>(state) << kStateShift) | (slot & kSlotMask);
793 static constexpr State control_state(std::uint32_t word)
noexcept {
794 return static_cast<State>((word >> kStateShift) & 0x1fu);
796 static constexpr std::uint8_t control_slot(std::uint32_t word)
noexcept {
797 return static_cast<std::uint8_t
>(word & kSlotMask);
799 static constexpr std::uint32_t control_configured(std::uint32_t word)
noexcept {
800 return (word >> kConfiguredShift) & kConfiguredMask;
802 static constexpr bool control_finish_requested(std::uint32_t word)
noexcept {
803 return (word & kFinishRequestedMask) != 0;
805 static constexpr std::uint32_t with_state(std::uint32_t word, State state)
noexcept {
806 return pack_control(state, control_slot(word), control_configured(word)) |
807 (word & kFinishRequestedMask);
809 State current_state() const noexcept {
810 return control_state(control_.load(std::memory_order_acquire));
812 void transition_terminal(State terminal)
noexcept {
813 auto word = control_.load(std::memory_order_acquire);
815 const auto state = control_state(word);
816 if (state == State::Faulted || state == State::Finished || state == State::Unprepared)
818 if (state == State::Publishing)
821 pack_control(terminal, control_slot(word), control_configured(word));
822 if (control_.compare_exchange_weak(word, desired, std::memory_order_acq_rel,
823 std::memory_order_acquire))
827 struct StoredProgram {
828 std::uint32_t version = 0;
829 int rotate_window = 1;
830 std::size_t step_count = 0, permutation_count = 0;
834 std::size_t length = 0, storage_offset = 0, token_index = 0;
835 std::uint64_t source_begin = 0, source_end = 0;
838 std::uint64_t source_begin = 0, source_end = 0;
839 std::size_t length = 0;
840 bool forward =
true, resampled =
false;
842 static bool checked_mul(std::size_t
a, std::size_t b, std::size_t& result)
noexcept {
843 if (
a != 0 && b > std::numeric_limits<std::size_t>::max() /
a)
848 void clear_configuration() noexcept {
849 control_.store(pack_control(State::Unprepared, 0), std::memory_order_release);
853 rotate_storage_.reset();
858 bool validate_program(
const OperationProgram& program)
const noexcept {
859 if (program.version == 0 || program.steps.empty() ||
860 program.steps.size() >
static_cast<std::size_t
>(capacity_.max_wavesets))
862 bool has_rotate =
false;
863 for (
const auto& step : program.steps) {
864 if (!valid_operation(step.operation))
866 if (step.operation == Operation::Repeat &&
867 (step.repeat_count < 1 || step.repeat_count > kMaxRepeatCount))
869 if (step.operation == Operation::CoordinateSelect) {
870 if (step.coordinate_choice_count < 1 ||
871 step.coordinate_choice_count > kMaxCoordinateChoices)
873 for (std::size_t
i = 0;
i < step.coordinate_choice_count; ++
i) {
874 const auto op = step.coordinate_choices[
i];
875 if (!valid_coordinate_operation(op))
878 if (step.repeat_count < 1 || step.repeat_count > kMaxRepeatCount)
881 has_rotate = has_rotate || step.operation == Operation::Rotate;
884 return program.permutation.empty();
885 const auto w = program.rotate_window;
886 if (w <= 0 || w > std::min(capacity_.max_wavesets, kMaxRotateWindow) ||
887 program.permutation.size() !=
static_cast<std::size_t
>(w))
889 std::array<bool, 256> seen{};
890 for (
auto value : program.permutation) {
891 if (value >=
static_cast<std::uint16_t
>(w) || seen[value])
895 for (std::size_t
i = 0;
i < program.steps.size();) {
896 if (program.steps[
i].operation != Operation::Rotate) {
900 if (
i +
static_cast<std::size_t
>(w) > program.steps.size())
902 for (
int j = 0;
j <
w; ++
j)
903 if (program.steps[
i +
static_cast<std::size_t
>(j)].operation != Operation::Rotate)
905 i +=
static_cast<std::size_t
>(
w);
909 static constexpr bool valid_operation(Operation operation)
noexcept {
910 return operation >= Operation::Pass && operation <= Operation::CoordinateSelect;
912 static constexpr bool valid_coordinate_operation(Operation operation)
noexcept {
913 return operation == Operation::Pass || operation == Operation::Repeat ||
914 operation == Operation::Omit || operation == Operation::Reverse ||
915 operation == Operation::Normalize;
917 bool ensure_reservation() noexcept {
920 std::size_t credit{};
921 if (!checked_mul(ledger_.count(), max_segment_output_, credit) ||
922 credit > splicer_.capacity() - splicer_.size() ||
923 max_segment_output_ > splicer_.capacity() - splicer_.size() - credit)
925 const auto token = ledger_.acquire_capture();
926 if (token == ledger_.capacity())
928 active_token_ = token;
931 bool is_boundary(SampleType sample)
const noexcept {
932 return segmenter_.transition(sample, epsilon_.load(std::memory_order_relaxed),
933 polarity_.load(std::memory_order_relaxed));
935 void update_side(SampleType sample)
noexcept {
936 segmenter_.observe(sample, epsilon_.load(std::memory_order_relaxed));
938 void finalize_capture(
bool)
noexcept {
939 if (capture_size_ == 0)
941 const auto& program = programs_[active_slot_];
942 const auto& step = programs_.step(active_slot_, program_cursor_);
943 if (step.operation == Operation::Rotate) {
944 if (!ledger_.transfer_to_rotate(active_token_)) {
948 auto& held = held_[held_count_];
949 held.storage_offset =
950 held_count_ *
static_cast<std::size_t
>(capacity_.max_waveset_samples);
951 std::copy_n(capture_.get(), capture_size_, rotate_storage_.get() + held.storage_offset);
952 held.length = capture_size_;
953 held.source_begin = capture_source_begin_;
954 held.source_end = capture_source_begin_ + capture_size_;
955 held.token_index = active_token_;
957 if (held_count_ ==
static_cast<std::size_t
>(program.rotate_window)) {
958 for (std::size_t
i = 0;
i < held_count_; ++
i) {
959 const auto& source = held_[programs_.permutation(active_slot_,
i)];
960 if (!ledger_.owns(source.token_index,
966 for (std::size_t
i = 0;
i < held_count_; ++
i) {
967 const auto& source = held_[programs_.permutation(active_slot_,
i)];
969 rotate_storage_.get() + source.storage_offset, source.length,
970 {source.source_begin, source.source_end, source.length, true, false});
971 if (!release_reservation(source.token_index,
977 (program_cursor_ +
static_cast<std::size_t
>(program.rotate_window)) %
979 apply_pending_program();
986 render_step(step, capture_.get(), capture_size_, capture_source_begin_);
989 program_cursor_ = (program_cursor_ + 1u) % program.step_count;
990 apply_pending_program();
995 void render_step(
const ProgramStep& step,
const SampleType* samples, std::size_t length,
996 std::uint64_t source_begin)
noexcept {
997 auto operation = step.operation;
998 if (operation == Operation::CoordinateSelect) {
1000 rng_key(coordinate_seed_.load(std::memory_order_relaxed), completed_index_));
1002 std::min<std::size_t>(
static_cast<std::size_t
>(draw * step.coordinate_choice_count),
1003 step.coordinate_choice_count - 1u);
1004 operation = step.coordinate_choices[choice];
1006 if (operation == Operation::Omit) {
1007 age_splice_suffix();
1010 std::size_t result_length = length;
1012 bool resampled =
false;
1013 switch (operation) {
1014 case Operation::Pass:
1015 std::copy_n(samples, length, scratch_.get());
1017 case Operation::Repeat:
1018 for (std::size_t copy = 0; copy < step.repeat_count; ++copy)
1019 append_chunk(samples, length,
1020 {source_begin, source_begin + length, length,
true,
false});
1022 case Operation::Reverse:
1023 std::reverse_copy(samples, samples + length, scratch_.get());
1026 case Operation::Normalize: {
1027 const auto ratio = normalize_ratio_.load(std::memory_order_relaxed);
1028 result_length = std::clamp<std::size_t>(
1029 static_cast<std::size_t
>(std::llround(
static_cast<long double>(length) *
ratio)),
1031 static_cast<std::size_t
>(
1032 std::ceil(capacity_.max_waveset_samples * capacity_.max_normalize_ratio)));
1033 if (result_length == 1 || length == 1) {
1034 std::fill_n(scratch_.get(), result_length, samples[0]);
1036 for (std::size_t
i = 0;
i < result_length; ++
i) {
1037 const long double position =
1038 static_cast<long double>(
i) * (length - 1u) / (result_length - 1u);
1039 const auto lower =
static_cast<std::size_t
>(position);
1040 const auto upper = std::min(lower + 1u, length - 1u);
1041 const auto fraction =
static_cast<SampleType
>(position - lower);
1042 scratch_[
i] = samples[lower] + (samples[upper] - samples[lower]) * fraction;
1045 resampled = result_length != length;
1051 append_chunk(scratch_.get(), result_length,
1052 {source_begin, source_begin + length, result_length, forward, resampled});
1054 void append_chunk(
const SampleType* samples, std::size_t length, ChunkInfo info)
noexcept {
1057 const bool contiguous_identity =
1058 splicer_.valid() && previous_chunk_.forward && !previous_chunk_.resampled &&
1059 info.forward && !
info.resampled && previous_chunk_.source_end ==
info.source_begin;
1060 std::size_t fade = 0;
1061 if (splicer_.valid() && !contiguous_identity) {
1062 const auto requested = std::min(
1063 max_fade_,
static_cast<std::size_t
>(std::llround(
1064 crossfade_ms_.load(std::memory_order_relaxed) * SampleType{0.001} *
1065 static_cast<SampleType
>(sample_rate_))));
1066 fade = std::min({requested, splicer_.full_length() / 2u, length / 2u, splicer_.size()});
1069 static_cast<CrossfadeGainLaw>(crossfade_law_.load(std::memory_order_relaxed));
1070 splicer_.append(samples, length, fade, law);
1071 previous_chunk_ =
info;
1073 void age_splice_suffix() noexcept {
1074 splicer_.age(
static_cast<std::size_t
>(capacity_.max_wavesets));
1077 if (ledger_.release(token, owner))
1082 void fault() noexcept {
1083 transition_terminal(State::Faulted);
1085 void finish_owned_processing() noexcept {
1086 if (capture_size_ != 0)
1087 finalize_capture(
true);
1088 if (current_state() == State::Faulted)
1090 flush_partial_rotate();
1091 if (current_state() != State::Faulted)
1092 transition_terminal(State::Finished);
1094 void apply_pending_program() noexcept {
1095 apply_program(control_slot(control_.load(std::memory_order_acquire)));
1097 void apply_program(std::uint8_t slot)
noexcept {
1098 if (slot == active_slot_)
1100 active_slot_ = slot;
1101 program_cursor_ = 0;
1103 void flush_partial_rotate() noexcept {
1104 for (std::size_t
i = 0;
i < held_count_; ++
i) {
1110 for (std::size_t
i = 0;
i < held_count_; ++
i) {
1111 const auto& segment = held_[
i];
1112 append_chunk(rotate_storage_.get() + segment.storage_offset, segment.length,
1113 {segment.source_begin, segment.source_end, segment.length, true, false});
1119 std::uint8_t first_configured_slot() const noexcept {
1120 for (std::uint8_t
i = 0;
i < kMaxProgramSlots; ++
i)
1125 template <
typename Mutation>
bool update_control(Mutation&& mutation)
noexcept {
1126 auto word = control_.load(std::memory_order_acquire);
1129 source = control_state(word);
1130 if (control_finish_requested(word) ||
1131 (source != State::Quiescent && source != State::Live))
1133 if (control_.compare_exchange_weak(word, with_state(word, State::Controlling),
1134 std::memory_order_acq_rel,
1135 std::memory_order_acquire))
1139#if defined(PULP_WAVESET_TEST_SEAMS)
1140 if (
const auto hook = control_completion_hook_.load(std::memory_order_acquire))
1143 word = control_.load(std::memory_order_acquire);
1145 if (control_finish_requested(word)) {
1146 finish_owned_processing();
1149 if (control_.compare_exchange_weak(word, with_state(word, source),
1150 std::memory_order_acq_rel,
1151 std::memory_order_acquire))
1156 std::unique_ptr<SampleType[]> capture_, scratch_, rotate_storage_;
1157 std::unique_ptr<HeldSegment[]> held_;
1161 Capacity capacity_{};
1162 double sample_rate_ = 0.0, max_normalize_ratio_ = 1.0;
1163 std::size_t max_segment_output_ = 0, max_fade_ = 0;
1164 int max_lookahead_ = 0;
1165 std::size_t tail_ = 0, capture_size_ = 0, held_count_ = 0;
1166 std::size_t active_token_ = 0, program_cursor_ = 0;
1167 std::uint64_t capture_source_begin_ = 0, next_source_index_ = 0;
1168 std::uint64_t accepted_total_ = 0, completed_index_ = 0;
1169 ChunkInfo previous_chunk_{};
1170 std::uint8_t active_slot_ = 0;
1171 static_assert(std::atomic<std::uint32_t>::is_always_lock_free);
1172 mutable std::atomic<std::uint32_t> control_{pack_control(State::Unprepared, 0)};
1173 std::atomic<std::uint8_t> polarity_{
static_cast<std::uint8_t
>(ZeroCrossingPolarity::Rising)};
1174 std::atomic<SampleType> epsilon_{SampleType{}};
1175 std::atomic<std::uint8_t> crossfade_law_{
1177 std::atomic<SampleType> crossfade_ms_{SampleType{}}, normalize_ratio_{SampleType{1}};
1178 std::atomic<std::uint64_t> coordinate_seed_{0};
1179 static_assert(std::atomic<SampleType>::is_always_lock_free);
1180 static_assert(std::atomic<std::uint64_t>::is_always_lock_free);
1181#if defined(PULP_WAVESET_TEST_SEAMS)
1182 inline static std::atomic<StartupHook> startup_hook_{
nullptr};
1183 inline static std::atomic<PublicationHook> publication_hook_{
nullptr};
1184 inline static std::atomic<FinishHook> finish_hook_{
nullptr};
1185 inline static std::atomic<ResetHook> reset_hook_{
nullptr};
1186 inline static std::atomic<ResetCompletionHook> reset_completion_hook_{
nullptr};
1187 inline static std::atomic<ControlCompletionHook> control_completion_hook_{
nullptr};
1188 inline static std::atomic<DrainedSnapshotHook> drained_snapshot_hook_{
nullptr};