Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
freeze_hold.hpp
Go to the documentation of this file.
1#pragma once
2
95
99
100#include <algorithm>
101#include <array>
102#include <atomic>
103#include <cassert>
104#include <cmath>
105#include <complex>
106#include <cstddef>
107#include <cstdint>
108#include <cstring>
109#include <span>
110#include <utility>
111#include <vector>
112
113namespace pulp::signal {
114
116enum class FreezeHoldSynthesis : std::uint8_t {
121};
122
124enum class FreezeHoldFrequency : std::uint8_t {
129};
130
132enum class FreezeHoldAverage : std::uint8_t {
137};
138
141enum class FreezeHoldPhaseLock : std::uint8_t {
148};
149
160
177template <typename SampleType = float>
179 static constexpr std::uint32_t kFormatVersion = 1;
180 static constexpr int kMinFftSize = 256;
181 static constexpr int kMaxFftSize = 16384;
182 static constexpr int kMaxChannels = 16;
183 static constexpr std::size_t kHeaderBytes = 56;
184 static constexpr std::uint32_t kFlagEngaged = 1u;
185 static constexpr std::uint32_t kFlagReleasing = 2u;
186
187 int fft_size = 0;
188 int channels = 0;
194 int fade_step = 0;
195 bool engaged = false;
196 bool releasing = false;
199 double sample_rate = 0.0;
200 std::uint64_t rng_state = 0;
201 std::vector<SampleType> magnitudes; // channels * bins
202 std::vector<double> phases; // channels * bins, wrapped
203 std::vector<double> inst_freq; // bins, radians/sample
204
205 static bool valid_geometry(int fft, int ch, int hop) noexcept {
206 return fft >= kMinFftSize && fft <= kMaxFftSize && (fft & (fft - 1)) == 0
207 && ch >= 1 && ch <= kMaxChannels && hop > 0 && hop <= fft / 2;
208 }
209
212 static std::size_t serialized_size(int fft, int ch) noexcept {
213 if (!valid_geometry(fft, ch, 1)) return 0;
214 const auto bins = static_cast<std::size_t>(fft / 2 + 1);
215 const auto channel_bins = static_cast<std::size_t>(ch) * bins;
216 return kHeaderBytes + channel_bins * sizeof(float)
217 + channel_bins * sizeof(double) + bins * sizeof(double);
218 }
219
221 bool prepare(int fft, int ch, int hop) {
222 if (!valid_geometry(fft, ch, hop)) return false;
223 const auto bins = static_cast<std::size_t>(fft / 2 + 1);
224 fft_size = fft;
225 channels = ch;
226 analysis_hop = hop;
227 capture_frames = 0;
229 fade_step = 0;
230 engaged = false;
231 releasing = false;
232 sample_rate = 0.0;
233 rng_state = 0;
234 magnitudes.assign(static_cast<std::size_t>(ch) * bins, SampleType{0});
235 phases.assign(static_cast<std::size_t>(ch) * bins, 0.0);
236 inst_freq.assign(bins, 0.0);
237 return true;
238 }
239
244 bool valid() const noexcept {
247 || (engaged && releasing) || !std::isfinite(sample_rate)
248 || sample_rate < 0.0 || rng_state == 0)
249 return false;
250 const auto bins = static_cast<std::size_t>(fft_size / 2 + 1);
251 const auto channel_bins = static_cast<std::size_t>(channels) * bins;
252 if (magnitudes.size() != channel_bins || phases.size() != channel_bins
253 || inst_freq.size() != bins)
254 return false;
255 constexpr double kPhaseBound = 3.14159265358979323846 + 1e-6;
256 for (const auto m : magnitudes)
257 if (!std::isfinite(m) || m < SampleType{0}) return false;
258 for (const auto p : phases)
259 if (!std::isfinite(p) || std::abs(p) > kPhaseBound) return false;
260 for (const auto f : inst_freq)
261 if (!std::isfinite(f) || std::abs(f) > 2.0 * kPhaseBound) return false;
262 return true;
263 }
264
267 bool write_bytes(std::vector<std::uint8_t>& out) const {
268 out.clear();
269 if (!valid()) return false;
270 out.reserve(serialized_size(fft_size, channels));
271 const std::uint8_t magic[4] = {'P', 'F', 'H', 'S'};
272 out.insert(out.end(), magic, magic + 4);
273 put_u32_(out, kFormatVersion);
274 put_u32_(out, static_cast<std::uint32_t>(fft_size));
275 put_u32_(out, static_cast<std::uint32_t>(channels));
276 put_u32_(out, static_cast<std::uint32_t>(analysis_hop));
277 put_u32_(out, static_cast<std::uint32_t>(capture_frames));
278 put_u32_(out, static_cast<std::uint32_t>(crossfade_frames));
279 put_u32_(out, static_cast<std::uint32_t>(fade_step));
280 put_u32_(out, (engaged ? kFlagEngaged : 0u) | (releasing ? kFlagReleasing : 0u));
281 put_u32_(out, 0u);
282 put_f64_(out, sample_rate);
283 put_u64_(out, rng_state);
284 for (const auto m : magnitudes) {
285 const auto f = static_cast<float>(m);
286 std::uint32_t bits = 0;
287 std::memcpy(&bits, &f, sizeof(bits));
288 put_u32_(out, bits);
289 }
290 for (const auto p : phases) put_f64_(out, p);
291 for (const auto f : inst_freq) put_f64_(out, f);
292 return true;
293 }
294
299 static bool read_bytes(const std::uint8_t* data, std::size_t size,
300 FreezeHoldSnapshotT& out) {
301 if (data == nullptr || size < kHeaderBytes
302 || std::memcmp(data, "PFHS", 4) != 0)
303 return false;
304 std::size_t at = 4;
305 if (get_u32_(data, at) != kFormatVersion) return false;
306 const auto fft = get_u32_(data, at);
307 const auto ch = get_u32_(data, at);
308 const auto hop = get_u32_(data, at);
309 const auto depth = get_u32_(data, at);
310 const auto crossfade = get_u32_(data, at);
311 const auto step = get_u32_(data, at);
312 const auto flags = get_u32_(data, at);
313 const auto reserved = get_u32_(data, at);
314 constexpr std::uint32_t kIntBound = 0x7fffffffu;
315 if (fft > static_cast<std::uint32_t>(kMaxFftSize)
316 || ch > static_cast<std::uint32_t>(kMaxChannels)
317 || hop > static_cast<std::uint32_t>(kMaxFftSize) || depth > kIntBound
318 || crossfade > kIntBound || step > kIntBound
319 || (flags & ~(kFlagEngaged | kFlagReleasing)) != 0 || reserved != 0)
320 return false;
322 if (!image.prepare(static_cast<int>(fft), static_cast<int>(ch),
323 static_cast<int>(hop))
324 || size != serialized_size(image.fft_size, image.channels))
325 return false;
326 image.capture_frames = static_cast<int>(depth);
327 image.crossfade_frames = static_cast<int>(crossfade);
328 image.fade_step = static_cast<int>(step);
329 image.engaged = (flags & kFlagEngaged) != 0;
330 image.releasing = (flags & kFlagReleasing) != 0;
331 image.sample_rate = get_f64_(data, at);
332 image.rng_state = get_u64_(data, at);
333 for (auto& m : image.magnitudes) {
334 const std::uint32_t bits = get_u32_(data, at);
335 float f = 0.0f;
336 std::memcpy(&f, &bits, sizeof(f));
337 m = static_cast<SampleType>(f);
338 }
339 for (auto& p : image.phases) p = get_f64_(data, at);
340 for (auto& f : image.inst_freq) f = get_f64_(data, at);
341 if (at != size || !image.valid()) return false;
342 out = std::move(image);
343 return true;
344 }
345
346private:
347 static void put_u32_(std::vector<std::uint8_t>& out, std::uint32_t v) {
348 for (int i = 0; i < 4; ++i)
349 out.push_back(static_cast<std::uint8_t>((v >> (8 * i)) & 0xffu));
350 }
351 static void put_u64_(std::vector<std::uint8_t>& out, std::uint64_t v) {
352 for (int i = 0; i < 8; ++i)
353 out.push_back(static_cast<std::uint8_t>((v >> (8 * i)) & 0xffu));
354 }
355 static void put_f64_(std::vector<std::uint8_t>& out, double v) {
356 std::uint64_t bits = 0;
357 std::memcpy(&bits, &v, sizeof(bits));
358 put_u64_(out, bits);
359 }
360 static std::uint32_t get_u32_(const std::uint8_t* data, std::size_t& at) {
361 std::uint32_t v = 0;
362 for (int i = 0; i < 4; ++i)
363 v |= static_cast<std::uint32_t>(data[at + static_cast<std::size_t>(i)]) << (8 * i);
364 at += 4;
365 return v;
366 }
367 static std::uint64_t get_u64_(const std::uint8_t* data, std::size_t& at) {
368 std::uint64_t v = 0;
369 for (int i = 0; i < 8; ++i)
370 v |= static_cast<std::uint64_t>(data[at + static_cast<std::size_t>(i)]) << (8 * i);
371 at += 8;
372 return v;
373 }
374 static double get_f64_(const std::uint8_t* data, std::size_t& at) {
375 const std::uint64_t bits = get_u64_(data, at);
376 double v = 0.0;
377 std::memcpy(&v, &bits, sizeof(v));
378 return v;
379 }
380};
381
384
390 static constexpr double kSampleRate = 48000.0;
391 static constexpr int kAnalysisHop = 512;
393 static constexpr double kCaptureSeconds = 8.0 * kAnalysisHop / kSampleRate;
395 static constexpr double kCrossfadeSeconds = 6.0 * kAnalysisHop / kSampleRate;
398 static constexpr double kPhaseJitterPerSqrtSecond = 0.14523687548277813;
399};
400
401template <typename SampleType = float>
403public:
458
460
463 static Config resolve(const Config& config) noexcept {
464 Config out = config;
465 if (config.sample_rate > 0.0 && config.analysis_hop > 0) {
466 out.capture_frames = frames_for_seconds(config.capture_seconds, config);
467 out.crossfade_frames = frames_for_seconds(config.crossfade_seconds, config);
468 out.phase_jitter = static_cast<float>(
469 std::max(config.phase_jitter_per_sqrt_second, 0.0)
470 * std::sqrt(static_cast<double>(config.analysis_hop) / config.sample_rate));
471 out.max_capture_frames = frames_for_seconds(config.max_capture_seconds, config);
472 }
473 out.capture_frames = std::max(out.capture_frames, 2);
474 out.crossfade_frames = std::max(out.crossfade_frames, 1);
475 out.max_capture_frames = std::max(out.max_capture_frames, out.capture_frames);
476 return out;
477 }
478
481 static int frames_for_seconds(double seconds, const Config& config) noexcept {
482 if (!(config.sample_rate > 0.0) || config.analysis_hop <= 0 || !(seconds > 0.0))
483 return 0;
484 const double frames =
485 seconds * config.sample_rate / static_cast<double>(config.analysis_hop);
486 return static_cast<int>(std::lround(std::min(frames, 1.0e6)));
487 }
488
489 static bool checked_retained_bytes(const Config& requested,
490 std::uint64_t target_max_bytes,
491 std::uint64_t& bytes) noexcept {
492 const Config config = resolve(requested);
493 const auto bins = static_cast<std::uint64_t>(config.fft_size / 2 + 1);
494 const auto channels = static_cast<std::uint64_t>(config.channels);
495 const auto depth = static_cast<std::uint64_t>(config.max_capture_frames);
496 std::uint64_t channel_bins = 0;
497 std::uint64_t channel_capture_bins = 0;
498 if (!checked_capacity_product(channels, bins, UINT64_MAX, channel_bins)
499 || !checked_capacity_product(depth, channel_bins, UINT64_MAX,
500 channel_capture_bins))
501 return false;
502 CheckedRetainedByteCharge charge(target_max_bytes);
503 // Capture magnitude ring, newest phase increment, previous captured
504 // frame, the held state, and the equally sized staged-restore slot.
505 if (!charge.add<SampleType>(channel_capture_bins)
506 || !charge.add<double>(bins)
507 || !charge.add<std::complex<SampleType>>(channel_bins)
508 || !charge.add<SampleType>(channel_bins)
509 || !charge.add<double>(channel_bins) || !charge.add<double>(bins)
510 || !charge.add<SampleType>(channel_bins)
511 || !charge.add<double>(channel_bins) || !charge.add<double>(bins))
512 return false;
513 // Opt-in cost modes: the per-frame increment ring and its window sum,
514 // running magnitude sums, and the phasor / rotor / jitter state.
516 std::uint64_t capture_bins = 0;
517 if (!checked_capacity_product(depth, bins, UINT64_MAX, capture_bins)
518 || !charge.add<std::complex<SampleType>>(capture_bins)
519 || !charge.add<std::complex<double>>(bins))
520 return false;
521 }
523 && !charge.add<double>(channel_bins))
524 return false;
525 if (config.synthesis == FreezeHoldSynthesis::rotor
526 && (!charge.add<std::complex<double>>(channel_bins)
527 || !charge.add<std::complex<double>>(bins)))
528 return false;
529 if (config.phase_lock == FreezeHoldPhaseLock::peak_lobe
530 && (!charge.add<int>(bins) || !charge.add<double>(bins)
531 || !charge.add<double>(bins)))
532 return false;
533 bytes = charge.total();
534 return true;
535 }
536
541 void prepare(const Config& requested) {
542 const Config config = resolve(requested);
543 assert(config.fft_size >= 256 && (config.fft_size & (config.fft_size - 1)) == 0);
544 assert(config.channels >= 1);
545 config_ = config;
546 num_bins_ = config.fft_size / 2 + 1;
547
548 const auto bins = static_cast<size_t>(num_bins_);
549 const auto channels = static_cast<size_t>(config.channels);
550 const auto depth = static_cast<size_t>(config.max_capture_frames);
551 capture_mag_.assign(channels * depth * bins, SampleType{0});
552 latest_increment_.assign(bins, 0.0);
553 latest_increment_raw_.assign(bins, std::complex<double>{});
554 prev_frame_.assign(channels * bins, std::complex<SampleType>{});
555 held_mag_.assign(channels * bins, SampleType{0});
556 held_phase_.assign(channels * bins, 0.0);
557 inst_freq_.assign(bins, 0.0);
558 pending_mag_.assign(channels * bins, SampleType{0});
559 pending_phase_.assign(channels * bins, 0.0);
560 pending_inst_freq_.assign(bins, 0.0);
561 const bool weighted = config.frequency == FreezeHoldFrequency::energy_weighted;
562 increment_ring_.assign(weighted ? depth * bins : 0, std::complex<SampleType>{});
563 increment_sum_.assign(weighted ? bins : 0, std::complex<double>{});
564 running_mag_sum_.assign(
565 config.average == FreezeHoldAverage::running ? channels * bins : 0, 0.0);
566 const bool rotor = config.synthesis == FreezeHoldSynthesis::rotor;
567 phasor_.assign(rotor ? channels * bins : 0, std::complex<double>(1.0, 0.0));
568 rotor_.assign(rotor ? bins : 0, std::complex<double>(1.0, 0.0));
569 // Jitter entry j is the walk step for u = (j + 0.5) / 128 - 1, the
570 // centre of the j-th of 256 equal slices of [-1, 1].
571 for (std::size_t j = 0; j < jitter_table_.size(); ++j) {
572 const double u = (static_cast<double>(j) + 0.5) / 128.0 - 1.0;
573 jitter_table_[j] = std::polar(1.0, static_cast<double>(config.phase_jitter) * u);
574 random_phase_table_[j] = std::polar(1.0, 3.14159265358979323846 * u);
575 }
576 const bool lock = config.phase_lock == FreezeHoldPhaseLock::peak_lobe;
577 peak_of_.assign(lock ? bins : 0, -1);
578 lobe_power_.assign(lock ? bins : 0, 0.0);
579 lobe_draw_.assign(lock ? bins : 0, 0.0);
580 pending_.value.store(kRestoreIdle, std::memory_order_release);
581 active_capture_frames_ = config.capture_frames;
582 latched_capture_frames_ = 0;
583 reset();
584 }
585
587 const Config& config() const { return config_; }
588
594 void set_capture_frames(int frames) {
595 if (num_bins_ == 0) return; // not prepared
596 active_capture_frames_ = std::clamp(frames, 2, config_.max_capture_frames);
597 }
598
600 void set_capture_seconds(double seconds) {
601 if (config_.sample_rate > 0.0)
602 set_capture_frames(frames_for_seconds(seconds, config_));
603 }
604
605 int capture_frames() const { return active_capture_frames_; }
606
613 void reset() {
615 engaged_ = false;
616 latched_ = false;
617 releasing_ = false;
618 has_hold_ = false;
619 fade_ = SampleType{0};
620 fade_step_ = 0;
621 rng_ = kRngSeed;
622 engage_staging_.cancel();
623 renormalise_countdown_ = kRenormaliseHops;
624 }
625
631 // Only the count matters: a latch reads the newest `captured_`
632 // slots, all written since this call, so nothing needs zeroing.
633 captured_ = 0;
634 capture_pos_ = 0;
635 prev_valid_ = false;
636 window_ = 0;
637 }
638
644 void set_frozen(bool frozen) {
645 if (engaged_ && !frozen && latched_) {
646 captured_ = 0;
647 window_ = 0;
648 releasing_ = true;
649 }
650 engaged_ = frozen;
651 }
652
653 bool is_engaged() const { return engaged_; }
656 bool is_latched() const { return latched_; }
658 bool is_releasing() const { return releasing_; }
661 bool has_hold() const { return has_hold_; }
662
669 SampleType engage_progress() const { return latched_ ? fade_ : SampleType{0}; }
670
671 // ── held-state pipeline ────────────────────────────────────────────────
672
674 std::span<const SampleType> held_magnitudes(int channel) const {
675 return {held_mag_.data() + static_cast<size_t>(channel) * static_cast<size_t>(num_bins_),
676 static_cast<size_t>(num_bins_)};
677 }
678
683 std::span<const double> held_phases(int channel) const {
684 if (rotor_mode()) sync_phases_from_phasors();
685 return {held_phase_.data() + static_cast<size_t>(channel) * static_cast<size_t>(num_bins_),
686 static_cast<size_t>(num_bins_)};
687 }
688
690 std::span<const double> instantaneous_frequency() const {
691 return {inst_freq_.data(), inst_freq_.size()};
692 }
693
697 bool write_hold(std::complex<SampleType>* const* frames, int channels,
698 int num_bins) const {
699 if (!has_hold_ || channels != config_.channels || num_bins != num_bins_)
700 return false;
701 if (rotor_mode()) {
702 for (int ch = 0; ch < channels; ++ch) {
703 const auto mags = held_magnitudes(ch);
704 const auto* z = phasor_.data() + static_cast<size_t>(ch) * static_cast<size_t>(num_bins_);
705 for (int k = 0; k < num_bins; ++k)
706 frames[ch][k] = scaled_phasor(z[k], mags[static_cast<size_t>(k)]);
707 }
708 return true;
709 }
710 for (int ch = 0; ch < channels; ++ch) {
711 const auto mags = held_magnitudes(ch);
712 const auto phases = held_phases(ch);
713 for (int k = 0; k < num_bins; ++k)
714 frames[ch][k] = rt::polar(mags[static_cast<size_t>(k)],
715 static_cast<SampleType>(phases[static_cast<size_t>(k)]));
716 }
717 return true;
718 }
719
722 bool engage_pending() const { return engage_staging_.active(); }
723
729 void advance_hold(int frames = 1) {
730 for (int i = 0; i < frames; ++i) advance_hold_phases();
731 }
732
741 void rewind_hold_phases(int frames) {
742 if (rotor_mode()) {
743 if (engage_staging_.active())
744 engage_staging_.finish_now([this](int step) { build_rotor_chunk(step); });
745 // conj(rotor)^frames by repeated squaring: no transcendental.
746 for (int k = 0; k < num_bins_; ++k) {
747 std::complex<double> base = std::conj(rotor_[static_cast<size_t>(k)]);
748 std::complex<double> back(1.0, 0.0);
749 for (int n = std::max(frames, 0); n > 0; n >>= 1) {
750 if (n & 1) back *= base;
751 base *= base;
752 }
753 for (int ch = 0; ch < config_.channels; ++ch)
754 phasor_[static_cast<size_t>(ch) * num_bins_ + static_cast<size_t>(k)] *= back;
755 }
756 return;
757 }
758 const double hops = static_cast<double>(frames)
759 * static_cast<double>(config_.analysis_hop);
760 for (int k = 0; k < num_bins_; ++k) {
761 const double back = inst_freq_[static_cast<size_t>(k)] * hops;
762 for (int ch = 0; ch < config_.channels; ++ch) {
763 double& phase = held_phase_[static_cast<size_t>(ch) * num_bins_
764 + static_cast<size_t>(k)];
765 phase = princarg(phase - back);
766 }
767 }
768 }
769
770 // ── state recall ───────────────────────────────────────────────────────
771
776 bool snapshot(Snapshot& out) const {
777 const auto bins = static_cast<std::size_t>(num_bins_);
778 const auto channel_bins = static_cast<std::size_t>(config_.channels) * bins;
779 if (engage_staging_.active()) return false;
780 if (rotor_mode()) sync_phases_from_phasors();
781 if (!has_hold_ || num_bins_ == 0 || out.fft_size != config_.fft_size
782 || out.channels != config_.channels
783 || out.analysis_hop != config_.analysis_hop
784 || out.magnitudes.size() != channel_bins
785 || out.phases.size() != channel_bins || out.inst_freq.size() != bins)
786 return false;
787 std::copy(held_mag_.begin(), held_mag_.end(), out.magnitudes.begin());
788 std::copy(held_phase_.begin(), held_phase_.end(), out.phases.begin());
789 std::copy(inst_freq_.begin(), inst_freq_.end(), out.inst_freq.begin());
790 out.capture_frames = latched_capture_frames_;
791 out.crossfade_frames = config_.crossfade_frames;
792 out.fade_step = latched_ ? fade_step_ : 0;
793 out.engaged = latched_ && engaged_ && !releasing_;
794 out.releasing = latched_ && releasing_;
795 out.sample_rate = config_.sample_rate;
796 out.rng_state = rng_;
797 return true;
798 }
799
810 bool stage_restore(const Snapshot& in,
812 if (num_bins_ == 0 || in.fft_size != config_.fft_size
813 || in.channels != config_.channels
814 || in.analysis_hop != config_.analysis_hop || !in.valid()
815 || (in.sample_rate > 0.0 && config_.sample_rate > 0.0
816 && in.sample_rate != config_.sample_rate))
817 return false;
818 if (pending_.value.load(std::memory_order_acquire) != kRestoreIdle)
819 return false;
820 std::copy(in.magnitudes.begin(), in.magnitudes.end(), pending_mag_.begin());
821 std::copy(in.phases.begin(), in.phases.end(), pending_phase_.begin());
822 std::copy(in.inst_freq.begin(), in.inst_freq.end(), pending_inst_freq_.begin());
823 pending_rng_ = in.rng_state;
824 const int steps = config_.crossfade_frames;
825 switch (engage) {
827 pending_engaged_ = true;
828 pending_releasing_ = false;
829 pending_fade_step_ = steps;
830 break;
832 pending_engaged_ = in.engaged;
833 pending_releasing_ = in.releasing;
834 // Same fraction of this hold's crossfade.
835 pending_fade_step_ = static_cast<int>(std::lround(
836 static_cast<double>(in.fade_step) * steps / in.crossfade_frames));
837 break;
839 default:
840 pending_engaged_ = true;
841 pending_releasing_ = false;
842 pending_fade_step_ = 0;
843 break;
844 }
845 pending_.value.store(kRestoreReady, std::memory_order_release);
846 return true;
847 }
848
851 bool restore_pending() const {
852 return pending_.value.load(std::memory_order_acquire) == kRestoreReady;
853 }
854
855 // ── streaming ──────────────────────────────────────────────────────────
856
860 void process_group(std::complex<SampleType>* const* frames, int channels, int num_bins) {
861 assert(num_bins == num_bins_);
862 assert(channels == config_.channels);
863
864 if (pending_.value.load(std::memory_order_acquire) == kRestoreReady)
865 adopt_restore();
866
867 // Capture BEFORE anything below rewrites `frames`: the window must
868 // hold live input, never a mix that already contains the hold.
869 // Capture runs whenever no hold is being sustained — including the
870 // release fade, so a re-freeze latches on post-release input as soon
871 // as the fresh window is full.
872 if (!latched_ || releasing_)
873 capture(frames, channels);
874
875 if (releasing_ && fade_step_ <= 0) { // release completed last frame
876 latched_ = false;
877 releasing_ = false;
878 }
879
880 if (engaged_ && !latched_ && captured_ >= active_capture_frames_)
881 latch();
882
883 if (engage_staging_.active())
884 engage_staging_.run_next(1, [this](int step) { build_rotor_chunk(step); });
885
886 if (!latched_)
887 return; // pass-through (possibly still filling — never mute)
888
889 // Crossfade target: 1 while holding, 0 while releasing.
890 //
891 // The fade LAW depends on direction. At ENGAGE the hold starts
892 // phase-aligned with the live signal (initialized from the latched
893 // frame and advanced only AFTER each mix, so the latch frame plays
894 // its captured phases verbatim): the sum is correlated, and linear
895 // gains keep it amplitude-flat — equal-power would bump it up to
896 // 3 dB. At RELEASE the hold has drifted (instantaneous-frequency
897 // estimate error + the deliberate phase random-walk), so hold and
898 // live are decorrelated: there a linear fade dips ~3–5 dB mid-fade
899 // (measured -5.1 dB on a steady tone — an audible "ding"), and the
900 // power-flat equal-power law is correct.
901 //
902 // The position is an integer step count: an accumulated 1/N float
903 // step does not reach exactly 1 for every N (N = 12 stops at
904 // 0.99999988), which would leave a residual live leak forever.
905 const int steps = config_.crossfade_frames;
906 fade_step_ = std::clamp(fade_step_ + (releasing_ ? -1 : 1), 0, steps);
907 fade_ = static_cast<SampleType>(fade_step_) / static_cast<SampleType>(steps);
908 SampleType hold_gain, live_gain;
909 if (!releasing_) {
910 hold_gain = fade_;
911 live_gain = SampleType{1} - fade_;
912 } else {
913 const SampleType t = fade_ * static_cast<SampleType>(1.57079632679489662); // fade_ * pi/2
914 hold_gain = std::sin(t);
915 live_gain = std::cos(t);
916 }
917
918 const bool rotor = rotor_mode();
919 for (int ch = 0; ch < channels; ++ch) {
920 const SampleType* mags = held_mag_.data()
921 + static_cast<size_t>(ch) * num_bins_;
922 const double* phases = held_phase_.data()
923 + static_cast<size_t>(ch) * num_bins_;
924 const std::complex<double>* phasors =
925 rotor ? phasor_.data() + static_cast<size_t>(ch) * num_bins_ : nullptr;
926 // Per-channel frame energies for the transition normalization
927 // below: the gain laws above are only flat on average — a
928 // narrowband bin whose hold drifted to anti-phase with the live
929 // signal partially cancels under ANY fixed-gain crossfade
930 // (measured: a residual -2.7 dB release notch on a pure tone).
931 // Renormalizing the mixed frame to the power-interpolated
932 // target makes the transition level-flat by construction, for
933 // any content and any phase relationship.
934 double e_live = 0.0, e_hold = 0.0, e_mix = 0.0;
935 for (int k = 0; k < num_bins_; ++k) {
936 const auto live = frames[ch][k];
937 const auto held = rotor
938 ? scaled_phasor(phasors[k], mags[k] * hold_gain)
939 : rt::polar(mags[k] * hold_gain, static_cast<SampleType>(phases[k]));
940 e_live += static_cast<double>(std::norm(live));
941 e_hold += static_cast<double>(mags[k]) * mags[k];
942 const auto mixed = live * live_gain + held;
943 e_mix += static_cast<double>(std::norm(mixed));
944 frames[ch][k] = mixed;
945 }
946 if (live_gain > SampleType{0}) { // mid-fade only; steady hold is exact
947 // Target: endpoint energies interpolated by fade POSITION,
948 // not by the gains — gain-derived targets are wrong for one
949 // correlation case or the other (a g²-weighted target undid
950 // the correlated engage fade by up to -3 dB mid-fade).
951 const double target =
952 (1.0 - static_cast<double>(fade_)) * e_live
953 + static_cast<double>(fade_) * e_hold;
954 const double scale_sq = target / std::max(e_mix, 1e-12);
955 const SampleType scale = static_cast<SampleType>(std::sqrt(
956 std::clamp(scale_sq, 0.0625, 16.0)));
957 for (int k = 0; k < num_bins_; ++k) frames[ch][k] *= scale;
958 }
959 }
960 // Advance AFTER mixing: the first held frame must reproduce the
961 // latched phases exactly, or the engage fade sums partially
962 // cancelling signals (one-hop phase skew ≈ arbitrary per bin —
963 // measured as a -4.6 dB dip at engage before this ordering).
964 advance_hold_phases();
965 }
966
967private:
968 void capture(std::complex<SampleType>* const* frames, int channels) {
969 const auto bins = static_cast<size_t>(num_bins_);
970 const auto depth = static_cast<size_t>(config_.max_capture_frames);
971 const bool running = config_.average == FreezeHoldAverage::running;
972 const bool weighted = config_.frequency == FreezeHoldFrequency::energy_weighted;
973 // A window that spans the whole ring includes the slot about to be
974 // overwritten: take it out of the running sums first.
975 if (running && window_ >= config_.max_capture_frames) {
976 remove_from_window(capture_pos_);
977 --window_;
978 }
979 for (int ch = 0; ch < channels; ++ch) {
980 SampleType* slot = capture_mag_.data()
981 + (static_cast<size_t>(ch) * depth
982 + static_cast<size_t>(capture_pos_)) * bins;
983 for (int k = 0; k < num_bins_; ++k)
984 slot[k] = std::abs(frames[ch][k]);
985 }
986 // Per-bin phase increment since the previous captured frame, from
987 // all channels at once: arg(sum_ch X_t * conj(X_{t-1})). The
988 // magnitude weighting follows the dominant channel, and unlike the
989 // phase of the channel SUM it is not destroyed where channels
990 // cancel (anti-phase bins). The first frame after a history clear
991 // has no predecessor and records the bin-centre increment.
992 if (weighted) {
993 // Keep the raw increment vector per frame; its argument is taken
994 // once, over the window, at the latch. A frame with no
995 // predecessor contributes nothing.
996 std::complex<SampleType>* ring = increment_ring_.data()
997 + static_cast<size_t>(capture_pos_) * bins;
998 for (int k = 0; k < num_bins_; ++k) {
999 std::complex<double> acc(0.0, 0.0);
1000 for (int ch = 0; ch < channels; ++ch) {
1001 const auto now = frames[ch][k];
1002 auto& prev = prev_frame_[static_cast<size_t>(ch) * bins + static_cast<size_t>(k)];
1003 acc += std::complex<double>(now) * std::conj(std::complex<double>(prev));
1004 prev = now;
1005 }
1006 ring[k] = prev_valid_ ? std::complex<SampleType>(acc) : std::complex<SampleType>{};
1007 }
1008 } else {
1009 // Only the increment vector is kept here; its argument is taken
1010 // at the latch, the one place it is read. An atan2 per bin per
1011 // hop (4097 for 8192 points) was the largest single cost of a
1012 // capturing frame group, paid on every hop for a value only a
1013 // latch consumes -- and the argument of the same vector later is
1014 // the same number, so the hold is bit-identical.
1015 std::complex<double>* raw = latest_increment_raw_.data();
1016 for (int k = 0; k < num_bins_; ++k) {
1017 std::complex<double> acc(0.0, 0.0);
1018 for (int ch = 0; ch < channels; ++ch) {
1019 const auto now = frames[ch][k];
1020 auto& prev = prev_frame_[static_cast<size_t>(ch) * bins + static_cast<size_t>(k)];
1021 acc += std::complex<double>(now) * std::conj(std::complex<double>(prev));
1022 prev = now;
1023 }
1024 raw[k] = acc;
1025 }
1026 latest_increment_valid_ = prev_valid_;
1027 }
1028 prev_valid_ = true;
1029 last_frames_ = frames; // valid only within process_group call
1030 const int written = capture_pos_;
1031 capture_pos_ = (capture_pos_ + 1) % config_.max_capture_frames;
1032 if (captured_ < config_.max_capture_frames) ++captured_;
1033 if (running) {
1034 add_to_window(written);
1035 ++window_;
1036 slide_window(kWindowAdjustPerHop);
1037 }
1038 }
1039
1040 // ── running window sums (average = running) ──────────────────────────
1041
1042 int ring_slot_back(int back) const {
1043 const int ring = config_.max_capture_frames;
1044 return ((capture_pos_ - back) % ring + ring) % ring;
1045 }
1046
1047 void add_to_window(int slot) {
1048 accumulate_slot(slot, window_ == 0 ? 0 : 1);
1049 }
1050
1051 void remove_from_window(int slot) { accumulate_slot(slot, -1); }
1052
1053 // sign 0 assigns (the window was empty, so no stale sum survives a
1054 // history clear), +1 adds, -1 subtracts.
1055 void accumulate_slot(int slot, int sign) {
1056 const auto bins = static_cast<size_t>(num_bins_);
1057 const auto ring = static_cast<size_t>(config_.max_capture_frames);
1058 for (int ch = 0; ch < config_.channels; ++ch) {
1059 const SampleType* mags = capture_mag_.data()
1060 + (static_cast<size_t>(ch) * ring + static_cast<size_t>(slot)) * bins;
1061 double* sum = running_mag_sum_.data() + static_cast<size_t>(ch) * bins;
1062 for (size_t k = 0; k < bins; ++k) {
1063 const double m = static_cast<double>(mags[k]);
1064 sum[k] = sign == 0 ? m : sum[k] + sign * m;
1065 }
1066 }
1067 std::uint64_t work = static_cast<std::uint64_t>(config_.channels) * bins;
1068 if (config_.frequency == FreezeHoldFrequency::energy_weighted) {
1069 const std::complex<SampleType>* inc = increment_ring_.data()
1070 + static_cast<size_t>(slot) * bins;
1071 for (size_t k = 0; k < bins; ++k) {
1072 const std::complex<double> v(inc[k]);
1073 increment_sum_[k] = sign == 0 ? v
1074 : sign > 0 ? increment_sum_[k] + v
1075 : increment_sum_[k] - v;
1076 }
1077 work += bins;
1078 }
1079 rt::count_bins(work);
1080 }
1081
1082 // Move the window toward the active capture depth: the ordinary slide
1083 // (one frame out per frame in) plus at most `extra` frames of catch-up
1084 // after set_capture_frames(), so a hold-length change never bursts the
1085 // whole difference into one frame group. A latch finishes the catch-up.
1086 void slide_window(int extra) {
1087 const int target = std::min(active_capture_frames_, captured_);
1088 if (window_ > target) {
1089 remove_from_window(ring_slot_back(window_));
1090 --window_;
1091 }
1092 for (int i = 0; i < extra && window_ != target; ++i) {
1093 if (window_ > target) {
1094 remove_from_window(ring_slot_back(window_));
1095 --window_;
1096 } else {
1097 add_to_window(ring_slot_back(window_ + 1));
1098 ++window_;
1099 }
1100 }
1101 }
1102
1103 void latch() {
1104 const auto bins = static_cast<size_t>(num_bins_);
1105 const int ring = config_.max_capture_frames;
1106 const int count = active_capture_frames_;
1107 const bool running = config_.average == FreezeHoldAverage::running;
1108 const bool weighted = config_.frequency == FreezeHoldFrequency::energy_weighted;
1109
1110 if (running) {
1111 // The running sums already cover the newest `count` frames unless
1112 // a recent hold-length change is still being caught up.
1113 while (window_ != count) slide_window(1);
1114 const double inv = 1.0 / static_cast<double>(count);
1115 for (size_t i = 0; i < held_mag_.size(); ++i)
1116 held_mag_[i] = static_cast<SampleType>(std::max(running_mag_sum_[i], 0.0) * inv);
1117 rt::count_bins(held_mag_.size());
1118 } else {
1119 // Hold magnitudes: average over the newest `count` captured frames —
1120 // exactly the last `count` consecutive analysis frames.
1121 rt::count_bins(static_cast<std::uint64_t>(count) * config_.channels * bins);
1122 for (int ch = 0; ch < config_.channels; ++ch) {
1123 SampleType* held = held_mag_.data() + static_cast<size_t>(ch) * bins;
1124 std::fill(held, held + bins, SampleType{0});
1125 for (int back = 1; back <= count; ++back) {
1126 const auto slot_index = static_cast<size_t>((capture_pos_ - back + ring) % ring);
1127 const SampleType* slot = capture_mag_.data()
1128 + (static_cast<size_t>(ch) * static_cast<size_t>(ring) + slot_index) * bins;
1129 for (int k = 0; k < num_bins_; ++k) held[k] += slot[k];
1130 }
1131 const SampleType inv = SampleType{1} / static_cast<SampleType>(count);
1132 for (int k = 0; k < num_bins_; ++k) held[k] *= inv;
1133 }
1134 }
1135 latched_capture_frames_ = count;
1136
1137 if (weighted && !running) {
1138 // Window sum of the per-frame increment vectors.
1139 std::fill(increment_sum_.begin(), increment_sum_.end(), std::complex<double>{});
1140 for (int back = 1; back <= count; ++back) {
1141 const std::complex<SampleType>* inc = increment_ring_.data()
1142 + static_cast<size_t>((capture_pos_ - back + ring) % ring) * bins;
1143 for (size_t k = 0; k < bins; ++k)
1144 increment_sum_[k] += std::complex<double>(inc[k]);
1145 }
1146 rt::count_bins(static_cast<std::uint64_t>(count) * bins);
1147 }
1148
1149 const bool staged = rotor_mode() && config_.engage_bins_per_hop > 0;
1150 const bool lock = config_.phase_lock == FreezeHoldPhaseLock::peak_lobe;
1151 if (lock) assign_lobes();
1152 if (weighted) {
1153 // With staging, each bin's frequency is derived with its rotor;
1154 // peaks are derived now because their lobes read them.
1155 if (!staged) {
1156 for (int k = 0; k < num_bins_; ++k) derive_weighted_frequency(k);
1157 } else if (lock) {
1158 for (int k = 0; k < num_bins_; ++k)
1159 if (peak_of_[static_cast<size_t>(k)] == k) derive_weighted_frequency(k);
1160 }
1161 } else {
1162 // Instantaneous frequency from the newest capture's increment
1163 // (heterodyned phase increment over one analysis hop). The increment
1164 // is the argument of the vector the newest capture kept.
1165 double* increment = latest_increment_.data();
1166 const double ha = static_cast<double>(config_.analysis_hop);
1167 const std::complex<double>* raw = latest_increment_raw_.data();
1168 for (int k = 0; k < num_bins_; ++k)
1169 increment[k] = latest_increment_valid_ ? rt::arg(raw[k])
1170 : two_pi_ * k / config_.fft_size * ha;
1171 for (int k = 0; k < num_bins_; ++k) {
1172 const double omega = two_pi_ * k / config_.fft_size;
1173 const double delta = princarg(increment[k] - omega * ha);
1174 inst_freq_[static_cast<size_t>(k)] = omega + delta / ha;
1175 }
1176 }
1177
1178 // Initial hold phases from the latched (newest) live frame so the
1179 // per-channel phase relationships — the image — carry into the hold.
1180 if (last_frames_ != nullptr) {
1181 if (rotor_mode()) {
1182 // Unit phasors straight from the frame: no atan2.
1183 for (int ch = 0; ch < config_.channels; ++ch) {
1184 std::complex<double>* z = phasor_.data() + static_cast<size_t>(ch) * bins;
1185 for (int k = 0; k < num_bins_; ++k) {
1186 const std::complex<double> x(last_frames_[ch][k]);
1187 const double r = std::abs(x);
1188 z[k] = r > 0.0 ? x / r : std::complex<double>(1.0, 0.0);
1189 }
1190 }
1191 } else {
1192 for (int ch = 0; ch < config_.channels; ++ch) {
1193 double* phases = held_phase_.data() + static_cast<size_t>(ch) * bins;
1194 for (int k = 0; k < num_bins_; ++k)
1195 phases[k] = static_cast<double>(rt::arg(last_frames_[ch][k]));
1196 }
1197 }
1198 }
1199 if (lock) {
1200 if (!(weighted && staged)) lock_lobe_frequencies();
1201 randomise_unlocked_phases();
1202 }
1203 if (rotor_mode())
1204 begin_rotor_build(weighted && staged, staged);
1205 fade_ = SampleType{0};
1206 fade_step_ = 0;
1207 latched_ = true;
1208 has_hold_ = true;
1209 }
1210
1211 void adopt_restore() {
1212 std::swap(held_mag_, pending_mag_);
1213 std::swap(held_phase_, pending_phase_);
1214 std::swap(inst_freq_, pending_inst_freq_);
1215 rng_ = pending_rng_;
1216 engaged_ = pending_engaged_;
1217 releasing_ = pending_releasing_;
1218 // A hold recorded after its release completed is kept (has_hold())
1219 // but not played.
1220 latched_ = engaged_ || releasing_;
1221 has_hold_ = true;
1222 fade_step_ = pending_fade_step_;
1223 fade_ = static_cast<SampleType>(fade_step_)
1224 / static_cast<SampleType>(config_.crossfade_frames);
1225 if (releasing_) {
1226 captured_ = 0; // the next hold starts from fresh input
1227 window_ = 0;
1228 }
1229 // A restored hold carries no lobe structure: each bin walks alone.
1230 std::fill(peak_of_.begin(), peak_of_.end(), -1);
1231 if (rotor_mode()) {
1232 // Built in full: a staged bin would hold its phase for a few
1233 // hops and the restored hold would no longer track its source.
1234 for (size_t i = 0; i < phasor_.size(); ++i)
1235 phasor_[i] = rt::polar(1.0, held_phase_[i]);
1236 begin_rotor_build(false, false);
1237 }
1238 pending_.value.store(kRestoreIdle, std::memory_order_release);
1239 }
1240
1241 // ── rotor synthesis ────────────────────────────────────────────────────
1242
1243 bool rotor_mode() const { return config_.synthesis == FreezeHoldSynthesis::rotor; }
1244
1245 static std::complex<SampleType> scaled_phasor(const std::complex<double>& z,
1246 SampleType magnitude) {
1247 const double m = static_cast<double>(magnitude);
1248 return {static_cast<SampleType>(z.real() * m), static_cast<SampleType>(z.imag() * m)};
1249 }
1250
1251 // Build every rotor now, or stage them over the following frame groups
1252 // (`engage_bins_per_hop` per group); a bin whose rotor is not built yet
1253 // holds its phase (rotor = 1). `derive_frequency` also derives each
1254 // bin's energy-weighted frequency in its step.
1255 void begin_rotor_build(bool derive_frequency, bool staged) {
1256 derive_frequency_in_steps_ = derive_frequency;
1257 const int chunk = staged ? config_.engage_bins_per_hop : num_bins_;
1258 engage_chunk_ = chunk;
1259 if (staged)
1260 std::fill(rotor_.begin(), rotor_.end(), std::complex<double>(1.0, 0.0));
1261 engage_staging_.begin((num_bins_ + chunk - 1) / chunk);
1262 if (!staged)
1263 engage_staging_.finish_now([this](int step) { build_rotor_chunk(step); });
1264 }
1265
1266 void build_rotor_chunk(int step) {
1267 const double ha = static_cast<double>(config_.analysis_hop);
1268 const int first = step * engage_chunk_;
1269 const int last = std::min(first + engage_chunk_, num_bins_);
1270 const bool lock = !peak_of_.empty();
1271 for (int k = first; k < last; ++k) {
1272 if (derive_frequency_in_steps_) {
1273 const int peak = lock ? peak_of_[static_cast<size_t>(k)] : -1;
1274 if (peak < 0)
1275 derive_weighted_frequency(k);
1276 else if (peak != k) // the peak itself was derived at the latch
1277 inst_freq_[static_cast<size_t>(k)] = inst_freq_[static_cast<size_t>(peak)];
1278 }
1279 rotor_[static_cast<size_t>(k)] =
1280 rt::polar(1.0, inst_freq_[static_cast<size_t>(k)] * ha);
1281 }
1282 }
1283
1284 // Instantaneous frequency of bin k from the window's summed increment
1285 // vector. A silent bin (zero sum) keeps its bin-centre frequency.
1286 void derive_weighted_frequency(int k) {
1287 const double ha = static_cast<double>(config_.analysis_hop);
1288 const double omega = two_pi_ * k / config_.fft_size;
1289 const auto sum = increment_sum_[static_cast<size_t>(k)];
1290 const double increment = (sum.real() == 0.0 && sum.imag() == 0.0)
1291 ? omega * ha
1292 : rt::arg(sum);
1293 const double delta = princarg(increment - omega * ha);
1294 inst_freq_[static_cast<size_t>(k)] = omega + delta / ha;
1295 }
1296
1297 // ── peak-lobe phase lock ───────────────────────────────────────────────
1298
1299 // Mark every bin within lobe_half_width of a peak with that peak's index
1300 // (-1 elsewhere). Peaks are local maxima of the channel-summed held power
1301 // over +-lobe_half_width, so two peaks are more than a lobe apart and a
1302 // peak is never inside another peak's lobe; a bin between two lobes goes
1303 // to the stronger peak.
1304 void assign_lobes() {
1305 const int bins = num_bins_;
1306 const int w = std::max(config_.lobe_half_width, 0);
1307 double top = 0.0;
1308 for (int k = 0; k < bins; ++k) {
1309 double p = 0.0;
1310 for (int ch = 0; ch < config_.channels; ++ch) {
1311 const double m = held_mag_[static_cast<size_t>(ch) * bins + static_cast<size_t>(k)];
1312 p += m * m;
1313 }
1314 lobe_power_[static_cast<size_t>(k)] = p;
1315 top = std::max(top, p);
1316 }
1317 std::fill(peak_of_.begin(), peak_of_.end(), -1);
1318 const double floor = top * 1e-6; // -60 dB
1319 for (int k = 0; k < bins; ++k) {
1320 const double p = lobe_power_[static_cast<size_t>(k)];
1321 if (!(p > floor)) continue;
1322 bool peak = true;
1323 for (int j = std::max(k - w, 0); peak && j <= std::min(k + w, bins - 1); ++j)
1324 if (j != k && (j < k ? lobe_power_[static_cast<size_t>(j)] >= p
1325 : lobe_power_[static_cast<size_t>(j)] > p))
1326 peak = false;
1327 if (!peak) continue;
1328 for (int j = std::max(k - w, 0); j <= std::min(k + w, bins - 1); ++j) {
1329 int& owner = peak_of_[static_cast<size_t>(j)];
1330 if (owner < 0 || lobe_power_[static_cast<size_t>(owner)] < p) owner = k;
1331 }
1332 }
1333 rt::count_bins(static_cast<std::uint64_t>(bins)
1334 * static_cast<std::uint64_t>(config_.channels + 2 * w + 1));
1335 }
1336
1337 void lock_lobe_frequencies() {
1338 for (int k = 0; k < num_bins_; ++k) {
1339 const int peak = peak_of_[static_cast<size_t>(k)];
1340 if (peak >= 0 && peak != k)
1341 inst_freq_[static_cast<size_t>(k)] = inst_freq_[static_cast<size_t>(peak)];
1342 }
1343 }
1344
1345 // One random rotation per unlocked bin, shared by every channel so the
1346 // inter-channel phase offsets survive.
1347 void randomise_unlocked_phases() {
1348 const auto bins = static_cast<size_t>(num_bins_);
1349 for (size_t k = 0; k < bins; ++k) {
1350 if (peak_of_[k] >= 0) continue;
1351 if (rotor_mode()) {
1352 const auto turn = random_phase_table_[next_jitter_index()];
1353 for (int ch = 0; ch < config_.channels; ++ch)
1354 phasor_[static_cast<size_t>(ch) * bins + k] *= turn;
1355 } else {
1356 const double turn = 3.14159265358979323846 * next_uniform();
1357 for (int ch = 0; ch < config_.channels; ++ch) {
1358 double& phase = held_phase_[static_cast<size_t>(ch) * bins + k];
1359 phase = princarg(phase + turn);
1360 }
1361 }
1362 }
1363 }
1364
1365 void sync_phases_from_phasors() const {
1366 for (size_t i = 0; i < phasor_.size(); ++i)
1367 held_phase_[i] = rt::arg(phasor_[i]);
1368 }
1369
1370 void advance_hold_phases() {
1371 // Under a peak-lobe lock one partial is one random walk: every lobe
1372 // bin takes its peak's draw, so the walk never decoheres a lobe. The
1373 // stream still advances once per bin, as without the lock.
1374 const bool lock = !peak_of_.empty();
1375 if (lock)
1376 for (size_t k = 0; k < lobe_draw_.size(); ++k)
1377 lobe_draw_[k] = rotor_mode() ? static_cast<double>(next_jitter_index())
1378 : next_uniform();
1379 const auto draw_for = [&](size_t k) {
1380 const int peak = peak_of_[k];
1381 return lobe_draw_[peak >= 0 ? static_cast<size_t>(peak) : k];
1382 };
1383 if (rotor_mode()) {
1384 const auto bins = static_cast<size_t>(num_bins_);
1385 for (size_t k = 0; k < bins; ++k) {
1386 const std::size_t index = lock ? static_cast<std::size_t>(draw_for(k))
1387 : next_jitter_index();
1388 const auto step = rotor_[k] * jitter_table_[index];
1389 for (int ch = 0; ch < config_.channels; ++ch)
1390 phasor_[static_cast<size_t>(ch) * bins + k] *= step;
1391 }
1392 // One Newton step toward |z| = 1 (z *= (3 - |z|^2) / 2) keeps
1393 // rounding from ever reaching the level.
1394 if (--renormalise_countdown_ <= 0) {
1395 renormalise_countdown_ = kRenormaliseHops;
1396 for (auto& z : phasor_) z *= 0.5 * (3.0 - std::norm(z));
1397 }
1398 return;
1399 }
1400 const double ha = static_cast<double>(config_.analysis_hop);
1401 const double jitter = static_cast<double>(config_.phase_jitter);
1402 for (int k = 0; k < num_bins_; ++k) {
1403 const double advance = inst_freq_[static_cast<size_t>(k)] * ha
1404 + jitter * (lock ? draw_for(static_cast<size_t>(k))
1405 : next_uniform());
1406 // Keep phases wrapped: the mix converts them to SampleType, and an
1407 // unbounded float phase loses the per-channel offsets (the stereo
1408 // image) and inter-frame coherence within seconds of holding.
1409 for (int ch = 0; ch < config_.channels; ++ch) {
1410 double& phase = held_phase_[static_cast<size_t>(ch) * num_bins_
1411 + static_cast<size_t>(k)];
1412 phase = princarg(phase + advance);
1413 }
1414 }
1415 }
1416
1417 // xorshift64* — deterministic, allocation-free; uniform in [-1, 1].
1418 double next_uniform() {
1419 return static_cast<double>(next_random() >> 11) * (2.0 / 9007199254740992.0) - 1.0;
1420 }
1421
1422 // The same stream quantised to a jitter-table index (top 8 bits).
1423 std::size_t next_jitter_index() {
1424 return static_cast<std::size_t>(next_random() >> 56);
1425 }
1426
1427 std::uint64_t next_random() {
1428 rng_ ^= rng_ >> 12;
1429 rng_ ^= rng_ << 25;
1430 rng_ ^= rng_ >> 27;
1431 return rng_ * 0x2545f4914f6cdd1dull;
1432 }
1433
1434 static double princarg(double p) {
1435 return p - two_pi_ * std::round(p / two_pi_);
1436 }
1437
1438 static constexpr double two_pi_ = 6.28318530717958647692;
1439 static constexpr std::uint64_t kRngSeed = 0x9e3779b97f4a7c15ull;
1440 static constexpr std::uint32_t kRestoreIdle = 0;
1441 static constexpr std::uint32_t kRestoreReady = 1;
1442 static constexpr int kRenormaliseHops = 64;
1443 static constexpr int kWindowAdjustPerHop = 2;
1444
1445 // Copyable wrapper so the hold (and the processors that own one by value)
1446 // keep their implicit copy/move operations.
1447 struct RestoreFlag {
1448 std::atomic<std::uint32_t> value{kRestoreIdle};
1449 RestoreFlag() = default;
1450 RestoreFlag(const RestoreFlag& other) noexcept
1451 : value(other.value.load(std::memory_order_acquire)) {}
1452 RestoreFlag& operator=(const RestoreFlag& other) noexcept {
1453 value.store(other.value.load(std::memory_order_acquire),
1454 std::memory_order_release);
1455 return *this;
1456 }
1457 };
1458
1459 Config config_;
1460 int num_bins_ = 0;
1461
1462 std::vector<SampleType> capture_mag_; // channels * max depth * bins
1463 std::vector<double> latest_increment_; // bins
1464 // The newest capture's per-bin increment vector, sum_ch X_t conj(X_{t-1});
1465 // its argument is taken at the latch. Invalid for a frame with no
1466 // predecessor, which takes the bin-centre increment instead.
1467 std::vector<std::complex<double>> latest_increment_raw_; // bins
1468 bool latest_increment_valid_ = false;
1469 std::vector<std::complex<SampleType>> prev_frame_; // channels * bins
1470 std::vector<SampleType> held_mag_; // channels * bins
1471 // Mutable: with rotor synthesis the phasors are the state and these
1472 // phases are derived on demand by const accessors.
1473 mutable std::vector<double> held_phase_; // channels * bins, wrapped
1474 std::vector<double> inst_freq_; // bins
1475
1476 // Opt-in cost modes (empty unless configured).
1477 std::vector<std::complex<SampleType>> increment_ring_; // max depth * bins
1478 std::vector<std::complex<double>> increment_sum_; // bins
1479 std::vector<double> running_mag_sum_; // channels * bins
1480 std::vector<std::complex<double>> phasor_; // channels * bins, |z| = 1
1481 std::vector<std::complex<double>> rotor_; // bins
1482 std::array<std::complex<double>, 256> jitter_table_{};
1483 std::array<std::complex<double>, 256> random_phase_table_{};
1484 std::vector<int> peak_of_; // bins, peak index or -1
1485 std::vector<double> lobe_power_; // bins, scratch
1486 std::vector<double> lobe_draw_; // bins, per-hop walk draws
1487 StagedTransition engage_staging_;
1488 int engage_chunk_ = 0;
1489 bool derive_frequency_in_steps_ = false;
1490 int window_ = 0; // frames in the running sums
1491 int renormalise_countdown_ = kRenormaliseHops;
1492
1493 // Staged-restore slot (single non-audio producer, audio consumer).
1494 std::vector<SampleType> pending_mag_;
1495 std::vector<double> pending_phase_;
1496 std::vector<double> pending_inst_freq_;
1497 std::uint64_t pending_rng_ = 0;
1498 int pending_fade_step_ = 0;
1499 bool pending_engaged_ = false;
1500 bool pending_releasing_ = false;
1501 RestoreFlag pending_;
1502
1503 std::complex<SampleType>* const* last_frames_ = nullptr;
1504 int captured_ = 0;
1505 int capture_pos_ = 0;
1506 int active_capture_frames_ = 2;
1507 int latched_capture_frames_ = 0;
1508 bool prev_valid_ = false;
1509 bool engaged_ = false;
1510 bool latched_ = false;
1511 bool releasing_ = false;
1512 bool has_hold_ = false;
1513 SampleType fade_ = SampleType{0};
1514 int fade_step_ = 0;
1515 std::uint64_t rng_ = kRngSeed;
1516};
1517
1520
1521} // namespace pulp::signal
Definition checked_allocation.hpp:57
bool add(std::uint64_t elements) noexcept
Definition checked_allocation.hpp:63
std::uint64_t total() const noexcept
Definition checked_allocation.hpp:83
Definition freeze_hold.hpp:402
bool is_engaged() const
The last engage/release request.
Definition freeze_hold.hpp:653
std::span< const double > held_phases(int channel) const
Current hold phases of one channel, wrapped to [-pi, pi]: the phases the next hold frame is rendered ...
Definition freeze_hold.hpp:683
const Config & config() const
Definition freeze_hold.hpp:587
bool restore_pending() const
True while a staged restore is waiting for the next frame group.
Definition freeze_hold.hpp:851
void reset()
Full reset: drops the hold and the capture history and re-seeds the PRNG (the prepared state).
Definition freeze_hold.hpp:613
bool is_latched() const
True once held content is actually playing (capture was full).
Definition freeze_hold.hpp:656
std::span< const double > instantaneous_frequency() const
Per-bin instantaneous frequency of the hold, radians per sample.
Definition freeze_hold.hpp:690
static bool checked_retained_bytes(const Config &requested, std::uint64_t target_max_bytes, std::uint64_t &bytes) noexcept
Definition freeze_hold.hpp:489
void advance_hold(int frames=1)
Advance the hold by frames analysis hops exactly as process_group() does after each held frame (insta...
Definition freeze_hold.hpp:729
void process_group(std::complex< SampleType > *const *frames, int channels, int num_bins)
Run at the head of the vocoder chain for every analysis frame group.
Definition freeze_hold.hpp:860
static Config resolve(const Config &config) noexcept
Resolve the time-based fields of config into frame counts: the exact configuration prepare() runs wit...
Definition freeze_hold.hpp:463
std::span< const SampleType > held_magnitudes(int channel) const
Averaged magnitudes of one channel's hold (num_bins values).
Definition freeze_hold.hpp:674
void prepare(const Config &requested)
RT contract: prepare() allocates capture/hold storage and is not audio-thread safe.
Definition freeze_hold.hpp:541
bool is_releasing() const
True while a released hold is fading out.
Definition freeze_hold.hpp:658
bool write_hold(std::complex< SampleType > *const *frames, int channels, int num_bins) const
Render the current hold frame (magnitude and phase only — no live mix, no crossfade,...
Definition freeze_hold.hpp:697
bool engage_pending() const
True while latch- or restore-side rotor construction is still being staged across frame groups (engag...
Definition freeze_hold.hpp:722
static int frames_for_seconds(double seconds, const Config &config) noexcept
round(seconds * sample_rate / analysis_hop) for a seconds-based config (0 when it is not seconds-base...
Definition freeze_hold.hpp:481
SampleType engage_progress() const
Hold-weight position of the crossfade used for the most recent frame group: 0 = live only,...
Definition freeze_hold.hpp:669
int capture_frames() const
The capture depth the next latch uses.
Definition freeze_hold.hpp:605
void set_frozen(bool frozen)
RT-safe engage/release request.
Definition freeze_hold.hpp:644
void set_capture_seconds(double seconds)
Seconds-based form of set_capture_frames(); ignored unless the hold was prepared with a sample rate.
Definition freeze_hold.hpp:600
bool snapshot(Snapshot &out) const
Copy the current hold into out, which must have been prepared for this geometry.
Definition freeze_hold.hpp:776
bool stage_restore(const Snapshot &in, FreezeRestoreEngage engage=FreezeRestoreEngage::crossfade)
Stage in to become the hold at the start of the next process_group().
Definition freeze_hold.hpp:810
FreezeHoldSnapshotT< SampleType > Snapshot
Definition freeze_hold.hpp:459
void clear_history()
Forget captured input only.
Definition freeze_hold.hpp:630
bool has_hold() const
True once a hold has been latched or restored since the last reset(); the held-state accessors and wr...
Definition freeze_hold.hpp:661
void rewind_hold_phases(int frames)
Move the hold phases back by frames hops at the instantaneous frequency, without the random walk and ...
Definition freeze_hold.hpp:741
void set_capture_frames(int frames)
Hold length: the number of most recent frames the NEXT latch averages, clamped to [2,...
Definition freeze_hold.hpp:594
Definition staged_transition.hpp:33
std::uint64_t next_random(std::uint64_t &state) noexcept
Definition sample_heritage_engine.hpp:25
@ top
Definition canvas.hpp:222
@ count
sentinel: number of real capabilities (keep LAST)
Definition canvas_capability.hpp:38
@ Config
Set via InspectorConfig / setEditorUrlTemplate.
Definition editor_url.hpp:50
MidiEvent at(MidiEvent event, std::int32_t sample_offset) noexcept
Definition utility_contract.hpp:140
@ index
Definition gpu_pass.hpp:140
@ back
Definition gpu_pass.hpp:283
@ other
Definition udev_monitor.hpp:26
@ ring
Definition string.hpp:18
std::complex< T > polar(T magnitude, T phase) noexcept
Counted std::polar. Bit-identical result.
Definition rt_work_counter.hpp:133
void count_bins(std::uint64_t n) noexcept
Record n units of per-bin accumulation work.
Definition rt_work_counter.hpp:100
T arg(const std::complex< T > &z) noexcept
Counted std::arg (an atan2). Bit-identical result.
Definition rt_work_counter.hpp:161
Definition additive_bank.hpp:218
@ first
Definition dither.hpp:23
FreezeHoldFrequency
Where a held bin's instantaneous frequency comes from.
Definition freeze_hold.hpp:124
@ newest
The newest captured frame's phase increment (the original estimate).
Definition freeze_hold.hpp:126
@ energy_weighted
Energy-weighted circular mean of the increments over the window.
Definition freeze_hold.hpp:128
@ peak
Sample peak. Literal, fastest-responding.
Definition feedforward_compressor.hpp:119
FreezeHoldPhaseLock
Phase structure of a hold as it starts (see the class notes on stationarity).
Definition freeze_hold.hpp:141
@ none
Every bin starts at its latched phase and rotates at its own frequency (the original behaviour).
Definition freeze_hold.hpp:144
@ peak_lobe
Bins within lobe_half_width of a spectral peak rotate at the peak's frequency; every other bin starts...
Definition freeze_hold.hpp:147
FreezeHoldT< double > FreezeHold64
Definition freeze_hold.hpp:1519
FreezeHoldSnapshotT< float > FreezeHoldSnapshot
Definition freeze_hold.hpp:382
bool checked_capacity_product(std::uint64_t lhs, std::uint64_t rhs, std::uint64_t maximum, std::uint64_t &product) noexcept
Definition checked_allocation.hpp:12
FreezeHoldT< float > FreezeHold
Definition freeze_hold.hpp:1518
@ u
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
@ e
Definition formant_filter_bank.hpp:20
@ none
Definition dither.hpp:17
FreezeHoldSynthesis
How held frames are synthesised (see the class notes on per-callback cost).
Definition freeze_hold.hpp:116
@ rotor
Unit phasor per bin advanced by a rotor and a jitter table.
Definition freeze_hold.hpp:120
@ polar
One std::polar per channel per bin per hop (the original path).
Definition freeze_hold.hpp:118
FreezeRestoreEngage
How a restored hold enters the output.
Definition freeze_hold.hpp:151
@ crossfade
Fade in over the configured crossfade, like a normal engage.
Definition freeze_hold.hpp:153
@ immediate
Replace the output with the hold from the adopting frame onward.
Definition freeze_hold.hpp:155
@ as_captured
Resume the recorded request and fade position (a hold captured mid-release keeps releasing).
Definition freeze_hold.hpp:158
FreezeHoldSnapshotT< double > FreezeHoldSnapshot64
Definition freeze_hold.hpp:383
FreezeHoldAverage
When the capture window is averaged.
Definition freeze_hold.hpp:132
@ running
Maintain running sums as frames arrive; the latch only divides.
Definition freeze_hold.hpp:136
@ at_latch
Sum the window inside the latching frame group (the original path).
Definition freeze_hold.hpp:134
float sum(const float *data, std::size_t count) noexcept
@ value
the widget's primary value (Knob, Fader, RangeSlider, Toggle, ProgressBar)
Definition binding_diagnostics.hpp:19
@ j
Definition input_events.hpp:40
@ r
Definition input_events.hpp:41
@ m
Definition input_events.hpp:40
@ p
Definition input_events.hpp:40
@ k
Definition input_events.hpp:40
@ x
Definition input_events.hpp:41
@ v
Definition input_events.hpp:41
@ w
Definition input_events.hpp:41
@ z
Definition input_events.hpp:42
Deterministic operation counters for audio-callback cost gates.
Amortise a bounded piece of transition work over several audio callbacks.
Time-based FreezeHold timing that reproduces the frame-count defaults (8 capture frames,...
Definition freeze_hold.hpp:389
static constexpr int kAnalysisHop
Definition freeze_hold.hpp:391
static constexpr double kSampleRate
Definition freeze_hold.hpp:390
static constexpr double kCrossfadeSeconds
6 hops: 6 * 512 / 48000 s.
Definition freeze_hold.hpp:395
static constexpr double kPhaseJitterPerSqrtSecond
0.015 rad per 512-sample frame, as a random-walk rate: 0.015 * sqrt(48000 / 512) rad per sqrt(second)...
Definition freeze_hold.hpp:398
static constexpr double kCaptureSeconds
8 hops: 8 * 512 / 48000 s.
Definition freeze_hold.hpp:393
Value image of a FreezeHold's held state: everything needed to reproduce the held output bit-for-bit ...
Definition freeze_hold.hpp:178
std::vector< float > magnitudes
Definition freeze_hold.hpp:201
int crossfade_frames
Definition freeze_hold.hpp:193
static constexpr int kMaxChannels
Definition freeze_hold.hpp:182
static constexpr std::uint32_t kFlagReleasing
Definition freeze_hold.hpp:185
static constexpr int kMinFftSize
Definition freeze_hold.hpp:180
static constexpr int kMaxFftSize
Definition freeze_hold.hpp:181
bool write_bytes(std::vector< std::uint8_t > &out) const
Serialize to the versioned byte image.
Definition freeze_hold.hpp:267
static std::size_t serialized_size(int fft, int ch) noexcept
Byte size of the serialized image for a geometry, or 0 when the geometry is outside the supported bou...
Definition freeze_hold.hpp:212
int fft_size
Definition freeze_hold.hpp:187
bool valid() const noexcept
Shape and value check: geometry in bounds, storage sized for it, a consistent fade record,...
Definition freeze_hold.hpp:244
static constexpr std::uint32_t kFlagEngaged
Definition freeze_hold.hpp:184
int analysis_hop
Definition freeze_hold.hpp:189
int capture_frames
Definition freeze_hold.hpp:191
bool releasing
Definition freeze_hold.hpp:196
std::vector< double > inst_freq
Definition freeze_hold.hpp:203
std::uint64_t rng_state
Definition freeze_hold.hpp:200
bool engaged
Definition freeze_hold.hpp:195
static bool valid_geometry(int fft, int ch, int hop) noexcept
Definition freeze_hold.hpp:205
int channels
Definition freeze_hold.hpp:188
static bool read_bytes(const std::uint8_t *data, std::size_t size, FreezeHoldSnapshotT &out)
Parse a byte image.
Definition freeze_hold.hpp:299
static constexpr std::uint32_t kFormatVersion
Definition freeze_hold.hpp:179
int fade_step
Definition freeze_hold.hpp:194
std::vector< double > phases
Definition freeze_hold.hpp:202
bool prepare(int fft, int ch, int hop)
Allocate storage for a geometry. Not audio-thread safe.
Definition freeze_hold.hpp:221
static constexpr std::size_t kHeaderBytes
Definition freeze_hold.hpp:183
double sample_rate
Definition freeze_hold.hpp:199
Two sizing modes.
Definition freeze_hold.hpp:410
int lobe_half_width
With peak_lobe: bins either side of a peak locked to it (2 spans a Hann main lobe).
Definition freeze_hold.hpp:456
int max_capture_frames
Largest capture depth set_capture_frames() may select later; storage is preallocated for it.
Definition freeze_hold.hpp:422
FreezeHoldSynthesis synthesis
Held-frame synthesis. polar reproduces the original output.
Definition freeze_hold.hpp:440
int capture_frames
Frames averaged into the hold (rolling capture depth, >= 2).
Definition freeze_hold.hpp:415
FreezeHoldPhaseLock phase_lock
Phase structure at the latch. none reproduces the original.
Definition freeze_hold.hpp:451
int engage_bins_per_hop
With rotor synthesis: bins whose rotor (and, with energy_weighted, frequency) is built per frame grou...
Definition freeze_hold.hpp:449
double max_capture_seconds
Longest capture set_capture_seconds() may select later.
Definition freeze_hold.hpp:437
int crossfade_frames
Engage/release crossfade length in frames (>= 1).
Definition freeze_hold.hpp:417
double sample_rate
Definition freeze_hold.hpp:425
FreezeHoldAverage average
Capture-window averaging. at_latch reproduces the original.
Definition freeze_hold.hpp:444
double crossfade_seconds
Crossfade length: frames = round(s * sr / hop).
Definition freeze_hold.hpp:429
int fft_size
Definition freeze_hold.hpp:411
int analysis_hop
Definition freeze_hold.hpp:413
int channels
Definition freeze_hold.hpp:412
float phase_jitter
Random-walk step bound (radians/frame) for de-looping.
Definition freeze_hold.hpp:419
double phase_jitter_per_sqrt_second
Random-walk rate in radians per sqrt(second); the per-frame bound is rate * sqrt(hop / sr),...
Definition freeze_hold.hpp:433
FreezeHoldFrequency frequency
Per-bin frequency estimate. newest reproduces the original.
Definition freeze_hold.hpp:442
double capture_seconds
Capture window in seconds of hop grid: frames = round(s * sr / hop).
Definition freeze_hold.hpp:427