Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
waveset_transformer.hpp
Go to the documentation of this file.
1#pragma once
3#include "pulp/signal/rng.hpp"
4#include <algorithm>
5#include <array>
6#include <atomic>
7#include <cmath>
8#include <cstddef>
9#include <cstdint>
10#include <limits>
11#include <memory>
12#include <new>
13#include <type_traits>
14#include <utility>
15#include <vector>
16namespace pulp::signal {
17namespace detail {
19 std::size_t n{}, m{}, wcap{}, e{}, q{}, a{}, g{}, l{}, f{}, bytes{};
20 static bool add(std::size_t a, std::size_t b, std::size_t& result) noexcept {
21 if (b > std::numeric_limits<std::size_t>::max() - a)
22 return false;
23 result = a + b;
24 return true;
25 }
26 static bool multiply(std::size_t a, std::size_t b, std::size_t& result) noexcept {
27 if (a != 0 && b > std::numeric_limits<std::size_t>::max() / a)
28 return false;
29 result = a * b;
30 return true;
31 }
32 static bool make(double sample_rate, std::size_t n, std::size_t m, double normalize_ratio,
33 std::size_t repeat_limit, std::size_t rotate_limit, std::size_t slot_count,
34 std::size_t sample_bytes, std::size_t held_bytes, std::size_t token_bytes,
35 std::size_t program_bytes, std::size_t step_bytes,
36 CapacityLayout& result) noexcept {
37 CapacityLayout candidate{};
38 candidate.n = n;
39 candidate.m = m;
40 candidate.wcap = std::min(n, rotate_limit);
41 std::size_t repeat_bound{}, normalize_bound{};
42 if (!multiply(m, repeat_limit, repeat_bound))
43 return false;
44 const long double normalized = static_cast<long double>(m) * normalize_ratio;
45 if (!std::isfinite(normalized) ||
46 normalized > static_cast<long double>(std::numeric_limits<std::size_t>::max()))
47 return false;
48 normalize_bound = static_cast<std::size_t>(std::ceil(normalized));
49 candidate.e = std::max(repeat_bound, normalize_bound);
50 const long double fade_product = static_cast<long double>(sample_rate) * 0.020L;
51 if (!std::isfinite(fade_product) ||
52 fade_product > static_cast<long double>(std::numeric_limits<std::size_t>::max()))
53 return false;
54 candidate.f = static_cast<std::size_t>(std::ceil(fade_product));
55 if ((candidate.f & 1u) != 0u && !add(candidate.f, 1u, candidate.f))
56 return false;
57 std::size_t n1{};
58 if (!add(n, 1u, n1) || !multiply(n1, candidate.e, candidate.q) ||
59 !add(candidate.q, candidate.f, candidate.q) ||
60 !multiply(candidate.wcap, m, candidate.a) || !multiply(n, m, candidate.g) ||
61 !add(candidate.a, candidate.g, candidate.l))
62 return false;
63 std::size_t sample_count{}, term{}, count{};
64 if (!add(m, candidate.e, sample_count) || !add(sample_count, candidate.q, sample_count) ||
65 !add(sample_count, candidate.g, sample_count) ||
66 !multiply(sample_count, sample_bytes, candidate.bytes) ||
67 !multiply(n, held_bytes, term) || !add(candidate.bytes, term, candidate.bytes) ||
68 !multiply(n, token_bytes, term) || !add(candidate.bytes, term, candidate.bytes) ||
69 !multiply(slot_count, program_bytes, term) ||
70 !add(candidate.bytes, term, candidate.bytes) || !multiply(slot_count, n, count) ||
71 !multiply(count, step_bytes, term) || !add(candidate.bytes, term, candidate.bytes) ||
72 !multiply(slot_count, candidate.wcap, count) ||
73 !multiply(count, sizeof(std::uint16_t), term) ||
74 !add(candidate.bytes, term, candidate.bytes))
75 return false;
76 result = candidate;
77 return true;
78 }
79};
81 enum class Owner : std::uint8_t { Free, Capture, Rotate };
83};
84template <typename Program, typename Step, std::size_t Slots> class ProgramBank {
85 public:
86 bool prepare(std::size_t n, std::size_t wcap) noexcept {
87 auto programs = std::unique_ptr<Program[]>(new (std::nothrow) Program[Slots]);
88 auto steps = std::unique_ptr<Step[]>(new (std::nothrow) Step[Slots * n]);
89 auto permutations =
90 std::unique_ptr<std::uint16_t[]>(new (std::nothrow) std::uint16_t[Slots * wcap]);
91 if (!programs || !steps || !permutations)
92 return false;
93 programs_ = std::move(programs);
94 steps_ = std::move(steps);
95 permutations_ = std::move(permutations);
96 n_ = n;
97 wcap_ = wcap;
98 return true;
99 }
100 Program& operator[](std::size_t index) noexcept {
101 return programs_[index];
102 }
103 const Program& operator[](std::size_t index) const noexcept {
104 return programs_[index];
105 }
106 Step& step(std::size_t slot, std::size_t index) noexcept {
107 return steps_[slot * n_ + index];
108 }
109 const Step& step(std::size_t slot, std::size_t index) const noexcept {
110 return steps_[slot * n_ + index];
111 }
112 std::uint16_t& permutation(std::size_t slot, std::size_t index) noexcept {
113 return permutations_[slot * wcap_ + index];
114 }
115 template <typename SourceProgram, typename Validator>
116 bool publish(std::size_t slot, const SourceProgram& source, Validator&& validate) noexcept {
117 if (slot >= Slots || !validate(source))
118 return false;
119 for (std::size_t i = 0; i < source.steps.size(); ++i)
120 step(slot, i) = source.steps[i];
121 for (std::size_t i = 0; i < source.permutation.size(); ++i)
122 permutation(slot, i) = source.permutation[i];
123 auto& destination = programs_[slot];
124 destination.version = source.version;
125 destination.step_count = source.steps.size();
126 destination.rotate_window = source.rotate_window;
127 destination.permutation_count = source.permutation.size();
128 destination.configured = true;
129 return true;
130 }
131 const std::uint16_t& permutation(std::size_t slot, std::size_t index) const noexcept {
132 return permutations_[slot * wcap_ + index];
133 }
134 void clear() noexcept {
135 programs_.reset();
136 steps_.reset();
137 permutations_.reset();
138 n_ = wcap_ = 0;
139 }
140
141 private:
142 std::unique_ptr<Program[]> programs_;
143 std::unique_ptr<Step[]> steps_;
144 std::unique_ptr<std::uint16_t[]> permutations_;
145 std::size_t n_ = 0, wcap_ = 0;
146};
147template <typename Sample> class Segmenter {
148 public:
149 enum class Side : std::uint8_t { Negative, Inside, Positive };
150 Side classify(Sample sample, Sample epsilon) const noexcept {
151 if (sample < -epsilon)
152 return Side::Negative;
153 if (sample > epsilon)
154 return Side::Positive;
155 return Side::Inside;
156 }
157 bool transition(Sample sample, Sample epsilon, std::uint8_t polarity) const noexcept {
158 const auto next = classify(sample, epsilon);
159 if (next == Side::Inside || last_ == Side::Inside || next == last_)
160 return false;
161 const bool rising = last_ == Side::Negative && next == Side::Positive;
162 return polarity == 2u || (polarity == 0u && rising) || (polarity == 1u && !rising);
163 }
164 void observe(Sample sample, Sample epsilon) noexcept {
165 const auto side = classify(sample, epsilon);
166 if (side != Side::Inside)
167 last_ = side;
168 }
169 void reset() noexcept {
170 last_ = Side::Inside;
171 }
172
173 private:
174 Side last_ = Side::Inside;
175};
177 public:
178 bool prepare(std::size_t count) noexcept {
179 auto tokens =
180 std::unique_ptr<ReservationToken[]>(new (std::nothrow) ReservationToken[count]);
181 if (!tokens)
182 return false;
183 tokens_ = std::move(tokens);
184 capacity_ = count;
185 reset();
186 return true;
187 }
188 void reset() noexcept {
189 for (std::size_t i = 0; i < capacity_; ++i)
190 tokens_[i].owner = ReservationToken::Owner::Free;
191 count_ = 0;
192 }
193 std::size_t acquire_capture() noexcept {
194 for (std::size_t i = 0; i < capacity_; ++i) {
195 if (tokens_[i].owner == ReservationToken::Owner::Free) {
196 tokens_[i].owner = ReservationToken::Owner::Capture;
197 ++count_;
198 return i;
199 }
200 }
201 return capacity_;
202 }
203 bool transfer_to_rotate(std::size_t index) noexcept {
204 if (index >= capacity_ || tokens_[index].owner != ReservationToken::Owner::Capture)
205 return false;
206 tokens_[index].owner = ReservationToken::Owner::Rotate;
207 return true;
208 }
209 bool release(std::size_t index, ReservationToken::Owner owner) noexcept {
210 if (index >= capacity_ || tokens_[index].owner != owner)
211 return false;
212 tokens_[index].owner = ReservationToken::Owner::Free;
213 --count_;
214 return true;
215 }
216 bool owns(std::size_t index, ReservationToken::Owner owner) const noexcept {
217 return index < capacity_ && tokens_[index].owner == owner;
218 }
219 std::size_t count() const noexcept {
220 return count_;
221 }
222 std::size_t capacity() const noexcept {
223 return capacity_;
224 }
225 void clear() noexcept {
226 tokens_.reset();
227 capacity_ = count_ = 0;
228 }
229
230 private:
231 std::unique_ptr<ReservationToken[]> tokens_;
232 std::size_t capacity_ = 0, count_ = 0;
233};
234template <typename Sample> class OutputSplicer {
235 public:
236 bool prepare(std::size_t capacity) noexcept {
237 auto fifo = std::unique_ptr<Sample[]>(new (std::nothrow) Sample[capacity]);
238 if (!fifo)
239 return false;
240 fifo_ = std::move(fifo);
241 capacity_ = capacity;
242 reset();
243 return true;
244 }
245 void reset() noexcept {
246 head_ = size_ = 0;
247 invalidate();
248 }
249 void clear() noexcept {
250 fifo_.reset();
251 capacity_ = 0;
252 reset();
253 }
254 void publish(std::size_t full_length) noexcept {
255 valid_ = true;
256 full_length_ = full_length;
257 age_ = 0;
258 }
259 bool age(std::size_t limit) noexcept {
260 if (!valid_)
261 return false;
262 if (++age_ == limit) {
263 invalidate();
264 return true;
265 }
266 return false;
267 }
268 void invalidate() noexcept {
269 valid_ = false;
270 full_length_ = age_ = 0;
271 }
272 bool valid() const noexcept {
273 return valid_;
274 }
275 std::size_t full_length() const noexcept {
276 return full_length_;
277 }
278 std::size_t size() const noexcept {
279 return size_;
280 }
281 std::size_t capacity() const noexcept {
282 return capacity_;
283 }
284 std::size_t pull(Sample* output, std::size_t count, std::size_t holdback) noexcept {
285 const auto available = size_ > holdback ? size_ - holdback : 0u;
286 const auto amount = std::min(available, count);
287 for (std::size_t i = 0; i < amount; ++i) {
288 output[i] = fifo_[head_];
289 head_ = (head_ + 1u) % capacity_;
290 }
291 size_ -= amount;
292 if (size_ == 0)
293 invalidate();
294 return amount;
295 }
296 void append(const Sample* samples, std::size_t length, std::size_t fade,
297 CrossfadeGainLaw law) noexcept {
298 for (std::size_t i = 0; i < fade; ++i) {
299 const auto position = (head_ + size_ - fade + i) % capacity_;
300 const auto t =
301 fade == 1 ? Sample{1} : static_cast<Sample>(i) / static_cast<Sample>(fade - 1u);
302 Sample old_gain{}, new_gain{};
303 crossfade_gains(crossfade_smoothstep(t), law, old_gain, new_gain);
304 fifo_[position] = fifo_[position] * old_gain + samples[i] * new_gain;
305 }
306 for (std::size_t i = fade; i < length; ++i) {
307 fifo_[(head_ + size_) % capacity_] = samples[i];
308 ++size_;
309 }
310 publish(length);
311 }
312
313 private:
314 std::unique_ptr<Sample[]> fifo_;
315 std::size_t capacity_ = 0, head_ = 0, size_ = 0;
316 std::size_t full_length_ = 0, age_ = 0;
317 bool valid_ = false;
318};
319} // namespace detail
335template <typename SampleType = float>
336 requires(std::is_same_v<SampleType, float> || std::is_same_v<SampleType, double>)
338 public:
339 static constexpr std::size_t kMaxProgramSlots = 8;
340 static constexpr std::size_t kMaxCoordinateChoices = 8;
341 static constexpr int kMaxRepeatCount = 16;
342 static constexpr int kMaxRotateWindow = 256;
343 struct Capacity {
347 };
348 enum class ZeroCrossingPolarity : std::uint8_t { Rising, Falling, Both };
349 enum class Operation : std::uint8_t {
350 Pass,
351 Repeat,
352 Omit,
353 Reverse,
354 Rotate,
355 Normalize,
356 CoordinateSelect
357 };
358 struct ProgramStep {
360 std::uint8_t repeat_count = 1;
361 std::array<Operation, kMaxCoordinateChoices> coordinate_choices{};
362 std::uint8_t coordinate_choice_count = 0;
363 };
365 std::uint32_t version = 1;
366 std::vector<ProgramStep> steps;
368 std::vector<std::uint16_t> permutation;
369 };
370 bool prepare(double sample_rate, Capacity capacity) {
371 if (prepared())
372 return false;
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)
377 return false;
378 detail::CapacityLayout layout{};
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),
383 static_cast<std::size_t>(kMaxRotateWindow), kMaxProgramSlots, sizeof(SampleType),
384 sizeof(HeldSegment), sizeof(detail::ReservationToken), sizeof(StoredProgram),
385 sizeof(ProgramStep), layout) ||
386 layout.l > static_cast<std::size_t>(std::numeric_limits<int>::max()))
387 return false;
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;
396 } storage;
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)
404 return false;
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);
417 tail_ = layout.q;
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);
426 reset();
427 return true;
428 }
429 bool prepared() const noexcept {
430 return control_state(control_.load(std::memory_order_acquire)) != State::Unprepared;
431 }
432 bool set_program(std::uint8_t slot, const OperationProgram& program) {
433 if (slot >= kMaxProgramSlots)
434 return false;
435 auto word = control_.load(std::memory_order_acquire);
436 for (;;) {
437 if (control_state(word) != State::Quiescent)
438 return false;
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))
444 hook();
445#endif
446 const bool published =
447 programs_.publish(slot, program, [this](const auto& candidate) noexcept {
448 return validate_program(candidate);
449 });
450 auto published_control = control_.load(std::memory_order_acquire);
451 for (;;) {
452 auto configured = control_configured(published_control);
453 if (published)
454 configured |= std::uint32_t{1} << slot;
455 const auto next_state = control_finish_requested(published_control)
456 ? State::Finished
457 : State::Quiescent;
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))
463 break;
464 }
465 return published;
466 }
467 }
468 }
469 bool request_program_slot(std::uint8_t slot) noexcept {
470 if (slot >= kMaxProgramSlots)
471 return false;
472 auto word = control_.load(std::memory_order_acquire);
473 for (;;) {
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)
478 return false;
479 const auto desired =
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))
483 return true;
484 }
485 }
487 if (polarity < ZeroCrossingPolarity::Rising || polarity > ZeroCrossingPolarity::Both)
488 return false;
489 return update_control([&] { polarity_.store(static_cast<std::uint8_t>(polarity)); });
490 }
491 bool set_zero_crossing_epsilon(SampleType epsilon) noexcept {
492 if (!std::isfinite(epsilon) || epsilon < SampleType{})
493 return false;
494 return update_control([&] { epsilon_.store(epsilon); });
495 }
498 return false;
499 return update_control([&] { crossfade_law_.store(static_cast<std::uint8_t>(law)); });
500 }
501 bool set_crossfade_duration_ms(SampleType duration_ms) noexcept {
502 if (!std::isfinite(duration_ms) || duration_ms < SampleType{} ||
503 duration_ms > SampleType{20})
504 return false;
505 return update_control([&] { crossfade_ms_.store(duration_ms); });
506 }
507 bool set_normalize_ratio(SampleType ratio) noexcept {
508 if (!std::isfinite(ratio) || ratio <= SampleType{} ||
509 static_cast<double>(ratio) > max_normalize_ratio_)
510 return false;
511 return update_control([&] { normalize_ratio_.store(ratio); });
512 }
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);
517 }
518 using PublicationHook = void (*)();
519 static void set_publication_hook(PublicationHook hook) noexcept {
520 publication_hook_.store(hook);
521 }
522 using FinishHook = void (*)();
523 static void set_finish_hook(FinishHook hook) noexcept {
524 finish_hook_.store(hook);
525 }
526 using ResetHook = void (*)();
527 static void set_reset_hook(ResetHook hook) noexcept {
528 reset_hook_.store(hook);
529 }
530 using ResetCompletionHook = void (*)();
531 static void set_reset_completion_hook(ResetCompletionHook hook) noexcept {
532 reset_completion_hook_.store(hook);
533 }
534 using ControlCompletionHook = void (*)();
535 static void set_control_completion_hook(ControlCompletionHook hook) noexcept {
536 control_completion_hook_.store(hook);
537 }
538 using DrainedSnapshotHook = void (*)();
539 static void set_drained_snapshot_hook(DrainedSnapshotHook hook) noexcept {
540 drained_snapshot_hook_.store(hook);
541 }
542#endif
543 bool set_coordinate_seed(std::uint64_t seed) noexcept {
544 return update_control([&] { coordinate_seed_.store(seed); });
545 }
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)
550 return 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]))
555 return 0;
556 for (;;) {
557 state = control_state(word);
558 if (state != State::Quiescent)
559 return 0;
560 startup_slot = control_slot(word);
561 const auto desired =
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))
565 break;
566 }
567 if (!ensure_reservation()) {
568 word = control_.load(std::memory_order_acquire);
569 for (;;) {
570 const auto target =
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))
576 return 0;
577 }
578 }
579 } else {
580 for (;;) {
581 state = control_state(word);
582 if (state != State::Live)
583 return 0;
584 if (control_.compare_exchange_weak(word, with_state(word, State::Pushing),
585 std::memory_order_acq_rel,
586 std::memory_order_acquire))
587 break;
588 }
589 }
590 if (starting) {
591#if defined(PULP_WAVESET_TEST_SEAMS)
592 if (const auto hook = startup_hook_.load(std::memory_order_acquire))
593 hook();
594#endif
595 apply_program(startup_slot);
596 }
597 int accepted = 0;
598 while (accepted < count) {
599 const auto sample = input[accepted];
600 if (!std::isfinite(sample))
601 break;
602 if (capture_size_ != 0 && is_boundary(sample)) {
603 finalize_capture(false);
604 if (current_state() == State::Faulted)
605 break;
606 if (!ensure_reservation())
607 break;
608 } else if (capture_size_ == 0 && !ensure_reservation()) {
609 break;
610 }
611 if (capture_size_ == 0)
612 capture_source_begin_ = next_source_index_;
613 capture_[capture_size_++] = sample;
614 update_side(sample);
615 ++accepted;
616 ++next_source_index_;
617 ++accepted_total_;
618 if (capture_size_ == static_cast<std::size_t>(capacity_.max_waveset_samples))
619 finalize_capture(true);
620 if (current_state() == State::Faulted)
621 break;
622 }
623 word = control_.load(std::memory_order_acquire);
624 for (;;) {
625 if (control_state(word) == State::Faulted)
626 return accepted;
627 if (control_finish_requested(word)) {
628 finish_owned_processing();
629 return accepted;
630 }
631 if (control_.compare_exchange_weak(word, with_state(word, State::Live),
632 std::memory_order_acq_rel,
633 std::memory_order_acquire))
634 return accepted;
635 }
636 }
637 int pull(SampleType* output, int count) noexcept {
638 if (output == nullptr || count <= 0)
639 return 0;
640 auto word = control_.load(std::memory_order_acquire);
641 State source{};
642 for (;;) {
643 source = control_state(word);
644 if (source == State::Quiescent)
645 return 0;
646 if (source != State::Live && source != State::Finished)
647 return 0;
648 const auto owned =
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))
652 break;
653 }
654 const auto holdback = source == State::Finished || !splicer_.valid()
655 ? std::size_t{}
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);
659 for (;;) {
660 if (source == State::Live && control_finish_requested(word)) {
661 finish_owned_processing();
662 break;
663 }
664 if (control_.compare_exchange_weak(word, with_state(word, source),
665 std::memory_order_acq_rel,
666 std::memory_order_acquire))
667 break;
668 }
669 return static_cast<int>(amount);
670 }
671 void finish_input() noexcept {
672 auto word = control_.load(std::memory_order_acquire);
673 for (;;) {
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)
678 return;
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))
684 return;
685 continue;
686 }
687 const auto desired =
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))
691 continue;
692 if (state == State::Quiescent)
693 return;
694 break;
695 }
696#if defined(PULP_WAVESET_TEST_SEAMS)
697 if (const auto hook = finish_hook_.load(std::memory_order_acquire))
698 hook();
699#endif
700 finish_owned_processing();
701 }
702 bool drained() const noexcept {
703 auto word = control_.load(std::memory_order_acquire);
704 for (;;) {
705 if (control_state(word) != State::Finished)
706 return false;
707 if (control_.compare_exchange_weak(word, with_state(word, State::Observing),
708 std::memory_order_acq_rel,
709 std::memory_order_acquire))
710 break;
711 }
712#if defined(PULP_WAVESET_TEST_SEAMS)
713 if (const auto hook = drained_snapshot_hook_.load(std::memory_order_acquire))
714 hook();
715#endif
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)) {
721 }
722 return result;
723 }
724 void reset() noexcept {
725 auto word = control_.load(std::memory_order_acquire);
726 for (;;) {
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)
732 return;
733 if (control_.compare_exchange_weak(word, with_state(word, State::Resetting),
734 std::memory_order_acq_rel,
735 std::memory_order_acquire))
736 break;
737 }
738#if defined(PULP_WAVESET_TEST_SEAMS)
739 if (const auto hook = reset_hook_.load(std::memory_order_acquire))
740 hook();
741#endif
742 capture_size_ = held_count_ = 0;
743 ledger_.reset();
744 accepted_total_ = next_source_index_ = completed_index_ = 0;
745 program_cursor_ = 0;
746 active_slot_ = programs_[0].configured ? 0 : first_configured_slot();
747 const auto configured = control_configured(control_.load(std::memory_order_relaxed));
748 segmenter_.reset();
749 splicer_.reset();
750#if defined(PULP_WAVESET_TEST_SEAMS)
751 if (const auto hook = reset_completion_hook_.load(std::memory_order_acquire))
752 hook();
753#endif
754 control_.store(pack_control(State::Quiescent, active_slot_, configured),
755 std::memory_order_release);
756 }
757 int latency_samples() const noexcept {
758 return 0;
759 }
760 int max_lookahead_samples() const noexcept {
761 return prepared() ? max_lookahead_ : 0;
762 }
763 std::size_t tail_samples() const noexcept {
764 return prepared() ? tail_ : 0;
765 }
766
767 private:
768 enum class State : std::uint8_t {
770 Quiescent,
771 Publishing,
772 Live,
773 Pushing,
774 Pulling,
775 Draining,
776 Observing,
777 Controlling,
778 Finishing,
779 Resetting,
780 Finished,
781 Faulted,
782 };
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,
789 std::uint32_t configured = 0) noexcept {
790 return (configured << kConfiguredShift) |
791 (static_cast<std::uint32_t>(state) << kStateShift) | (slot & kSlotMask);
792 }
793 static constexpr State control_state(std::uint32_t word) noexcept {
794 return static_cast<State>((word >> kStateShift) & 0x1fu);
795 }
796 static constexpr std::uint8_t control_slot(std::uint32_t word) noexcept {
797 return static_cast<std::uint8_t>(word & kSlotMask);
798 }
799 static constexpr std::uint32_t control_configured(std::uint32_t word) noexcept {
800 return (word >> kConfiguredShift) & kConfiguredMask;
801 }
802 static constexpr bool control_finish_requested(std::uint32_t word) noexcept {
803 return (word & kFinishRequestedMask) != 0;
804 }
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);
808 }
809 State current_state() const noexcept {
810 return control_state(control_.load(std::memory_order_acquire));
811 }
812 void transition_terminal(State terminal) noexcept {
813 auto word = control_.load(std::memory_order_acquire);
814 for (;;) {
815 const auto state = control_state(word);
816 if (state == State::Faulted || state == State::Finished || state == State::Unprepared)
817 return;
818 if (state == State::Publishing)
819 return;
820 const auto desired =
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))
824 return;
825 }
826 }
827 struct StoredProgram {
828 std::uint32_t version = 0;
829 int rotate_window = 1;
830 std::size_t step_count = 0, permutation_count = 0;
831 bool configured = false;
832 };
833 struct HeldSegment {
834 std::size_t length = 0, storage_offset = 0, token_index = 0;
835 std::uint64_t source_begin = 0, source_end = 0;
836 };
837 struct ChunkInfo {
838 std::uint64_t source_begin = 0, source_end = 0;
839 std::size_t length = 0;
840 bool forward = true, resampled = false;
841 };
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)
844 return false;
845 result = a * b;
846 return true;
847 }
848 void clear_configuration() noexcept {
849 control_.store(pack_control(State::Unprepared, 0), std::memory_order_release);
850 capture_.reset();
851 scratch_.reset();
852 splicer_.clear();
853 rotate_storage_.reset();
854 held_.reset();
855 ledger_.clear();
856 programs_.clear();
857 }
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))
861 return false;
862 bool has_rotate = false;
863 for (const auto& step : program.steps) {
864 if (!valid_operation(step.operation))
865 return false;
866 if (step.operation == Operation::Repeat &&
867 (step.repeat_count < 1 || step.repeat_count > kMaxRepeatCount))
868 return false;
869 if (step.operation == Operation::CoordinateSelect) {
870 if (step.coordinate_choice_count < 1 ||
871 step.coordinate_choice_count > kMaxCoordinateChoices)
872 return false;
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))
876 return false;
877 }
878 if (step.repeat_count < 1 || step.repeat_count > kMaxRepeatCount)
879 return false;
880 }
881 has_rotate = has_rotate || step.operation == Operation::Rotate;
882 }
883 if (!has_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))
888 return false;
889 std::array<bool, 256> seen{};
890 for (auto value : program.permutation) {
891 if (value >= static_cast<std::uint16_t>(w) || seen[value])
892 return false;
893 seen[value] = true;
894 }
895 for (std::size_t i = 0; i < program.steps.size();) {
896 if (program.steps[i].operation != Operation::Rotate) {
897 ++i;
898 continue;
899 }
900 if (i + static_cast<std::size_t>(w) > program.steps.size())
901 return false;
902 for (int j = 0; j < w; ++j)
903 if (program.steps[i + static_cast<std::size_t>(j)].operation != Operation::Rotate)
904 return false;
905 i += static_cast<std::size_t>(w);
906 }
907 return true;
908 }
909 static constexpr bool valid_operation(Operation operation) noexcept {
910 return operation >= Operation::Pass && operation <= Operation::CoordinateSelect;
911 }
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;
916 }
917 bool ensure_reservation() noexcept {
918 if (ledger_.owns(active_token_, detail::ReservationToken::Owner::Capture))
919 return true;
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)
924 return false;
925 const auto token = ledger_.acquire_capture();
926 if (token == ledger_.capacity())
927 return false;
928 active_token_ = token;
929 return true;
930 }
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));
934 }
935 void update_side(SampleType sample) noexcept {
936 segmenter_.observe(sample, epsilon_.load(std::memory_order_relaxed));
937 }
938 void finalize_capture(bool) noexcept {
939 if (capture_size_ == 0)
940 return;
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_)) {
945 fault();
946 return;
947 }
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_;
956 ++held_count_;
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,
962 fault();
963 return;
964 }
965 }
966 for (std::size_t i = 0; i < held_count_; ++i) {
967 const auto& source = held_[programs_.permutation(active_slot_, i)];
968 append_chunk(
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,
973 return;
974 }
975 held_count_ = 0;
976 program_cursor_ =
977 (program_cursor_ + static_cast<std::size_t>(program.rotate_window)) %
978 program.step_count;
979 apply_pending_program();
980 }
981 } else {
982 if (!ledger_.owns(active_token_, detail::ReservationToken::Owner::Capture)) {
983 fault();
984 return;
985 }
986 render_step(step, capture_.get(), capture_size_, capture_source_begin_);
987 if (!release_reservation(active_token_, detail::ReservationToken::Owner::Capture))
988 return;
989 program_cursor_ = (program_cursor_ + 1u) % program.step_count;
990 apply_pending_program();
991 }
992 ++completed_index_;
993 capture_size_ = 0;
994 }
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) {
999 const auto draw = unit_from<double>(
1000 rng_key(coordinate_seed_.load(std::memory_order_relaxed), completed_index_));
1001 const auto choice =
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];
1005 }
1006 if (operation == Operation::Omit) {
1007 age_splice_suffix();
1008 return;
1009 }
1010 std::size_t result_length = length;
1011 bool forward = true;
1012 bool resampled = false;
1013 switch (operation) {
1014 case Operation::Pass:
1015 std::copy_n(samples, length, scratch_.get());
1016 break;
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});
1021 return;
1022 case Operation::Reverse:
1023 std::reverse_copy(samples, samples + length, scratch_.get());
1024 forward = false;
1025 break;
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)),
1030 1u,
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]);
1035 } else {
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;
1043 }
1044 }
1045 resampled = result_length != length;
1046 break;
1047 }
1048 default:
1049 return;
1050 }
1051 append_chunk(scratch_.get(), result_length,
1052 {source_begin, source_begin + length, result_length, forward, resampled});
1053 }
1054 void append_chunk(const SampleType* samples, std::size_t length, ChunkInfo info) noexcept {
1055 if (length == 0)
1056 return;
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()});
1067 }
1068 const auto law =
1069 static_cast<CrossfadeGainLaw>(crossfade_law_.load(std::memory_order_relaxed));
1070 splicer_.append(samples, length, fade, law);
1071 previous_chunk_ = info;
1072 }
1073 void age_splice_suffix() noexcept {
1074 splicer_.age(static_cast<std::size_t>(capacity_.max_wavesets));
1075 }
1076 bool release_reservation(std::size_t token, detail::ReservationToken::Owner owner) noexcept {
1077 if (ledger_.release(token, owner))
1078 return true;
1079 fault();
1080 return false;
1081 }
1082 void fault() noexcept {
1083 transition_terminal(State::Faulted);
1084 }
1085 void finish_owned_processing() noexcept {
1086 if (capture_size_ != 0)
1087 finalize_capture(true);
1088 if (current_state() == State::Faulted)
1089 return;
1090 flush_partial_rotate();
1091 if (current_state() != State::Faulted)
1092 transition_terminal(State::Finished);
1093 }
1094 void apply_pending_program() noexcept {
1095 apply_program(control_slot(control_.load(std::memory_order_acquire)));
1096 }
1097 void apply_program(std::uint8_t slot) noexcept {
1098 if (slot == active_slot_)
1099 return;
1100 active_slot_ = slot;
1101 program_cursor_ = 0;
1102 }
1103 void flush_partial_rotate() noexcept {
1104 for (std::size_t i = 0; i < held_count_; ++i) {
1105 if (!ledger_.owns(held_[i].token_index, detail::ReservationToken::Owner::Rotate)) {
1106 fault();
1107 return;
1108 }
1109 }
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});
1114 if (!release_reservation(segment.token_index, detail::ReservationToken::Owner::Rotate))
1115 return;
1116 }
1117 held_count_ = 0;
1118 }
1119 std::uint8_t first_configured_slot() const noexcept {
1120 for (std::uint8_t i = 0; i < kMaxProgramSlots; ++i)
1121 if (programs_[i].configured)
1122 return i;
1123 return 0;
1124 }
1125 template <typename Mutation> bool update_control(Mutation&& mutation) noexcept {
1126 auto word = control_.load(std::memory_order_acquire);
1127 State source{};
1128 for (;;) {
1129 source = control_state(word);
1130 if (control_finish_requested(word) ||
1131 (source != State::Quiescent && source != State::Live))
1132 return false;
1133 if (control_.compare_exchange_weak(word, with_state(word, State::Controlling),
1134 std::memory_order_acq_rel,
1135 std::memory_order_acquire))
1136 break;
1137 }
1138 mutation();
1139#if defined(PULP_WAVESET_TEST_SEAMS)
1140 if (const auto hook = control_completion_hook_.load(std::memory_order_acquire))
1141 hook();
1142#endif
1143 word = control_.load(std::memory_order_acquire);
1144 for (;;) {
1145 if (control_finish_requested(word)) {
1146 finish_owned_processing();
1147 return true;
1148 }
1149 if (control_.compare_exchange_weak(word, with_state(word, source),
1150 std::memory_order_acq_rel,
1151 std::memory_order_acquire))
1152 return true;
1153 }
1154 }
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_{
1176 static_cast<std::uint8_t>(CrossfadeGainLaw::EqualGain)};
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};
1189#endif
1190};
1193} // namespace pulp::signal
A bounded, allocation-free-at-runtime waveset transformation engine.
Definition waveset_transformer.hpp:337
bool prepared() const noexcept
Definition waveset_transformer.hpp:429
bool set_normalize_ratio(SampleType ratio) noexcept
Definition waveset_transformer.hpp:507
int pull(SampleType *output, int count) noexcept
Definition waveset_transformer.hpp:637
int push(const SampleType *input, int count) noexcept
Definition waveset_transformer.hpp:546
bool drained() const noexcept
Definition waveset_transformer.hpp:702
bool set_crossfade_law(CrossfadeGainLaw law) noexcept
Definition waveset_transformer.hpp:496
bool prepare(double sample_rate, Capacity capacity)
Definition waveset_transformer.hpp:370
bool set_program(std::uint8_t slot, const OperationProgram &program)
Definition waveset_transformer.hpp:432
Operation
Definition waveset_transformer.hpp:349
@ Pass
Definition waveset_transformer.hpp:350
bool set_zero_crossing_epsilon(SampleType epsilon) noexcept
Definition waveset_transformer.hpp:491
static constexpr std::size_t kMaxCoordinateChoices
Definition waveset_transformer.hpp:340
bool set_zero_crossing_polarity(ZeroCrossingPolarity polarity) noexcept
Definition waveset_transformer.hpp:486
static constexpr int kMaxRepeatCount
Definition waveset_transformer.hpp:341
static constexpr int kMaxRotateWindow
Definition waveset_transformer.hpp:342
static constexpr std::size_t kMaxProgramSlots
Definition waveset_transformer.hpp:339
void reset() noexcept
Definition waveset_transformer.hpp:724
ZeroCrossingPolarity
Definition waveset_transformer.hpp:348
std::size_t tail_samples() const noexcept
Definition waveset_transformer.hpp:763
int latency_samples() const noexcept
Definition waveset_transformer.hpp:757
bool set_coordinate_seed(std::uint64_t seed) noexcept
Definition waveset_transformer.hpp:543
bool request_program_slot(std::uint8_t slot) noexcept
Definition waveset_transformer.hpp:469
int max_lookahead_samples() const noexcept
Definition waveset_transformer.hpp:760
bool set_crossfade_duration_ms(SampleType duration_ms) noexcept
Definition waveset_transformer.hpp:501
void finish_input() noexcept
Definition waveset_transformer.hpp:671
Definition waveset_transformer.hpp:234
bool age(std::size_t limit) noexcept
Definition waveset_transformer.hpp:259
std::size_t pull(Sample *output, std::size_t count, std::size_t holdback) noexcept
Definition waveset_transformer.hpp:284
void invalidate() noexcept
Definition waveset_transformer.hpp:268
void clear() noexcept
Definition waveset_transformer.hpp:249
void publish(std::size_t full_length) noexcept
Definition waveset_transformer.hpp:254
bool valid() const noexcept
Definition waveset_transformer.hpp:272
bool prepare(std::size_t capacity) noexcept
Definition waveset_transformer.hpp:236
std::size_t capacity() const noexcept
Definition waveset_transformer.hpp:281
std::size_t size() const noexcept
Definition waveset_transformer.hpp:278
void append(const Sample *samples, std::size_t length, std::size_t fade, CrossfadeGainLaw law) noexcept
Definition waveset_transformer.hpp:296
std::size_t full_length() const noexcept
Definition waveset_transformer.hpp:275
void reset() noexcept
Definition waveset_transformer.hpp:245
Definition waveset_transformer.hpp:84
bool publish(std::size_t slot, const SourceProgram &source, Validator &&validate) noexcept
Definition waveset_transformer.hpp:116
Step & step(std::size_t slot, std::size_t index) noexcept
Definition waveset_transformer.hpp:106
const Step & step(std::size_t slot, std::size_t index) const noexcept
Definition waveset_transformer.hpp:109
Program & operator[](std::size_t index) noexcept
Definition waveset_transformer.hpp:100
bool prepare(std::size_t n, std::size_t wcap) noexcept
Definition waveset_transformer.hpp:86
std::uint16_t & permutation(std::size_t slot, std::size_t index) noexcept
Definition waveset_transformer.hpp:112
const std::uint16_t & permutation(std::size_t slot, std::size_t index) const noexcept
Definition waveset_transformer.hpp:131
const Program & operator[](std::size_t index) const noexcept
Definition waveset_transformer.hpp:103
void clear() noexcept
Definition waveset_transformer.hpp:134
Definition waveset_transformer.hpp:176
void clear() noexcept
Definition waveset_transformer.hpp:225
bool transfer_to_rotate(std::size_t index) noexcept
Definition waveset_transformer.hpp:203
bool prepare(std::size_t count) noexcept
Definition waveset_transformer.hpp:178
void reset() noexcept
Definition waveset_transformer.hpp:188
std::size_t acquire_capture() noexcept
Definition waveset_transformer.hpp:193
std::size_t count() const noexcept
Definition waveset_transformer.hpp:219
bool release(std::size_t index, ReservationToken::Owner owner) noexcept
Definition waveset_transformer.hpp:209
std::size_t capacity() const noexcept
Definition waveset_transformer.hpp:222
bool owns(std::size_t index, ReservationToken::Owner owner) const noexcept
Definition waveset_transformer.hpp:216
Definition waveset_transformer.hpp:147
Side classify(Sample sample, Sample epsilon) const noexcept
Definition waveset_transformer.hpp:150
void observe(Sample sample, Sample epsilon) noexcept
Definition waveset_transformer.hpp:164
void reset() noexcept
Definition waveset_transformer.hpp:169
Side
Definition waveset_transformer.hpp:149
@ Positive
Definition waveset_transformer.hpp:149
@ Inside
Definition waveset_transformer.hpp:149
@ Negative
Definition waveset_transformer.hpp:149
bool transition(Sample sample, Sample epsilon, std::uint8_t polarity) const noexcept
Definition waveset_transformer.hpp:157
The one shared old→new crossfade gain law for the SIGNAL side.
@ forward
not the editor's: the host acts on it (Space → transport)
Definition editor_host.hpp:378
@ b
Definition pitch.hpp:31
@ State
Definition project_package.hpp:17
@ info
Definition gpu_diagnostics.hpp:56
Round-2 effect-wave vocabulary.
Definition analysis_frontends.hpp:50
Definition additive_bank.hpp:218
@ forward
(s + 1) mod N.
Definition stage_sequencer.hpp:18
@ Unprepared
Definition beat_repeat_kernel.hpp:42
@ rising
Definition rise_fall_generator.hpp:19
WavesetTransformerT< float > WavesetTransformer
Definition waveset_transformer.hpp:1191
constexpr std::uint64_t rng_key(std::uint64_t purpose, std::uint64_t index)
Combine a purpose tag and an index into a key for unit_from().
Definition rng.hpp:187
@ ratio
base_frequency_hz * ratio.
Definition fm_operator_engine.hpp:20
CrossfadeGainLaw
The old→new gain split applied to a shaped progress u ∈ [0,1].
Definition crossfade.hpp:41
@ EqualPower
(cos(u·π/2), sin(u·π/2)) — power sum == 1.
Definition crossfade.hpp:43
@ EqualGain
(1−u, u) — amplitude sum == 1.
Definition crossfade.hpp:42
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
@ a
Definition formant_filter_bank.hpp:20
@ u
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
WavesetTransformerT< double > WavesetTransformer64
Definition waveset_transformer.hpp:1192
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
@ prepared
Definition finite_stretch_builder.hpp:19
@ accepted
Definition realtime_pitch_time_geometry.hpp:29
float unit_from(std::uint64_t key)
Stateless purpose-keyed draw in [0, 1).
Definition rng.hpp:164
@ configured
Definition formant_filter_bank.hpp:23
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48
@ value
the widget's primary value (Knob, Fader, RangeSlider, Toggle, ProgressBar)
Definition binding_diagnostics.hpp:19
@ j
Definition input_events.hpp:40
@ w
Definition input_events.hpp:41
Deterministic randomness for modulation: a counter-free PRNG, a Gaussian draw, a stateless purpose-ke...
Definition waveset_transformer.hpp:343
int max_waveset_samples
Definition waveset_transformer.hpp:345
int max_wavesets
Definition waveset_transformer.hpp:344
double max_normalize_ratio
Definition waveset_transformer.hpp:346
Definition waveset_transformer.hpp:364
int rotate_window
Definition waveset_transformer.hpp:367
std::vector< std::uint16_t > permutation
Definition waveset_transformer.hpp:368
std::uint32_t version
Definition waveset_transformer.hpp:365
std::vector< ProgramStep > steps
Definition waveset_transformer.hpp:366
Definition waveset_transformer.hpp:358
std::uint8_t coordinate_choice_count
Definition waveset_transformer.hpp:362
std::array< Operation, kMaxCoordinateChoices > coordinate_choices
Definition waveset_transformer.hpp:361
Operation operation
Definition waveset_transformer.hpp:359
std::uint8_t repeat_count
Definition waveset_transformer.hpp:360
Definition waveset_transformer.hpp:18
static bool add(std::size_t a, std::size_t b, std::size_t &result) noexcept
Definition waveset_transformer.hpp:20
std::size_t l
Definition waveset_transformer.hpp:19
std::size_t e
Definition waveset_transformer.hpp:19
std::size_t g
Definition waveset_transformer.hpp:19
std::size_t bytes
Definition waveset_transformer.hpp:19
std::size_t f
Definition waveset_transformer.hpp:19
static bool make(double sample_rate, std::size_t n, std::size_t m, double normalize_ratio, std::size_t repeat_limit, std::size_t rotate_limit, std::size_t slot_count, std::size_t sample_bytes, std::size_t held_bytes, std::size_t token_bytes, std::size_t program_bytes, std::size_t step_bytes, CapacityLayout &result) noexcept
Definition waveset_transformer.hpp:32
std::size_t m
Definition waveset_transformer.hpp:19
std::size_t a
Definition waveset_transformer.hpp:19
std::size_t wcap
Definition waveset_transformer.hpp:19
static bool multiply(std::size_t a, std::size_t b, std::size_t &result) noexcept
Definition waveset_transformer.hpp:26
std::size_t n
Definition waveset_transformer.hpp:19
std::size_t q
Definition waveset_transformer.hpp:19
Definition waveset_transformer.hpp:80
Owner owner
Definition waveset_transformer.hpp:82
Owner
Definition waveset_transformer.hpp:81
@ Capture
Definition waveset_transformer.hpp:81
@ Rotate
Definition waveset_transformer.hpp:81
@ Free
Definition waveset_transformer.hpp:81