228 const double floored = std::floor(x);
229 const double fraction = x - floored;
230 const auto below =
static_cast<long long>(floored);
231 if (fraction > 0.5)
return below + 1;
232 if (fraction < 0.5)
return below;
233 return (below & 1LL) == 0 ? below : below + 1;
264template <
typename SampleType =
float>
347 const auto ring_needed =
static_cast<std::size_t
>(
349 ring_mask_ = power_of_two_mask(ring_needed);
350 ring_.assign(ring_mask_ + 1u, SampleType{0});
352 const auto longest_cycle =
static_cast<long long>(
354 max_grain_ = std::max<long long>(
361 accum_mask_ = power_of_two_mask(
static_cast<std::size_t
>(2 * max_grain_) + 2u);
362 accum_.assign(accum_mask_ + 1u, 0.0);
363 accum_epoch_.assign(accum_mask_ + 1u, 0
u);
371 std::fill(ring_.begin(), ring_.end(), SampleType{0});
372 std::fill(accum_.begin(), accum_.end(), 0.0);
373 std::fill(accum_epoch_.begin(), accum_epoch_.end(), 0
u);
374 accum_generation_ = 1u;
375 reset_runtime_state();
379 void audio_fault_reset() noexcept {
381 if (accum_generation_ == 0
u) accum_generation_ = 1u;
382 reset_runtime_state();
385 void reset_runtime_state() noexcept {
393 previous_fade_out_ = crossfade_length_;
394 end_previous_grain_ = 0;
395 end_two_grains_ago_ = 0;
407 if (!std::isfinite(hz))
return;
418 if (!std::isfinite(pct))
return;
424 if (!std::isfinite(shape))
return;
425 crossfade_shape_ = std::clamp(shape, 0.0, 1.0);
429 if (!std::isfinite(
ratio))
return;
434 if (!std::isfinite(ms))
return;
440 if (!std::isfinite(percent))
return;
441 mix_ = std::clamp(percent, 0.0, 100.0) * 0.01;
445 if (!std::isfinite(db))
return;
460 int cycle_samples() const noexcept {
return static_cast<int>(cycle_length_); }
462 int grain_samples() const noexcept {
return static_cast<int>(grain_length_); }
466 int hop_samples() const noexcept {
return static_cast<int>(hop_length_); }
469 return hop_length_ > 0 ? sample_rate_ /
static_cast<double>(hop_length_) : 0.0;
489 return (1.0 - crossfade_shape_) + crossfade_shape_ *
kWorstCaseGain;
498 const double ideal =
static_cast<double>(out_pos) / stretch_ratio_;
499 return cycle_length_ *
round_half_even(ideal /
static_cast<double>(cycle_length_));
532 void process(
const SampleType* in, SampleType* out,
int n) {
534 if (ring_.empty() || accum_.empty() || in ==
nullptr) {
535 std::fill_n(out, n, SampleType{0});
538 for (
int i = 0;
i < n; ++
i) {
539 if (!std::isfinite(
static_cast<double>(in[
i]))) {
541 out[
i] = SampleType{0};
544 ring_[write_index_] = in[
i];
545 write_index_ = (write_index_ + 1u) & ring_mask_;
550 if (total_written_ <= grain_length_) {
551 out[
i] = SampleType{0};
559 while (grain_out_pos_ <= out_read_) emit_grain();
561 const auto slot =
static_cast<std::size_t
>(out_read_) & accum_mask_;
562 const double wet = accum_epoch_[slot] == accum_generation_
566 accum_epoch_[slot] = accum_generation_;
576 const auto dry_index =
static_cast<std::size_t
>(
577 (write_index_ + ring_mask_ + 1u -
578 static_cast<std::size_t
>(grain_length_ + 1)) & ring_mask_);
579 const double dry =
static_cast<double>(ring_[dry_index]);
581 const double result = output_gain_ * (mix_ * wet + (1.0 - mix_) * dry);
582 if (!std::isfinite(result)) {
584 out[
i] = SampleType{0};
586 out[
i] =
static_cast<SampleType
>(result);
592 static std::size_t power_of_two_mask(std::size_t at_least) {
593 std::size_t size = 2;
594 while (size < at_least) size <<= 1;
634 void advance_read_cursor(
long long out_pos)
noexcept {
637 prev_out_pos_ = out_pos;
639 const long long newest = total_written_ - grain_length_;
640 const long long oldest = std::max<long long>(0, total_written_ - capture_window_);
641 const long long span = std::max<long long>(cycle_length_,
newest -
oldest);
644 const long long laps = (read_pos_ -
newest + span - 1) / span;
645 read_pos_ -= laps * span;
646 }
else if (read_pos_ <
oldest) {
647 const long long laps = (
oldest - read_pos_ + span - 1) / span;
648 read_pos_ += laps * span;
653 read_pos_ = (read_pos_ / cycle_length_) * cycle_length_;
654 read_pos_ = std::clamp(read_pos_, std::max<long long>(0,
oldest),
655 std::max<long long>(0,
newest));
662 void splice_weights(
double theta,
double& fade_in,
double& fade_out)
const noexcept {
663 double linear_out = 0.0, linear_in = 0.0;
664 double power_out = 0.0, power_in = 0.0;
667 fade_in = linear_in + crossfade_shape_ * (power_in - linear_in);
668 fade_out = linear_out + crossfade_shape_ * (power_out - linear_out);
672 void emit_grain() noexcept {
686 const long long out_pos = std::max(grain_out_pos_, end_two_grains_ago_);
687 grain_out_pos_ = out_pos;
688 advance_read_cursor(out_pos);
689 const long long in_pos = read_pos_;
712 const long long previous_fade =
713 grain_ == 0 ? crossfade_length_ : previous_fade_out_;
714 const long long fade_in = std::clamp<long long>(
715 previous_fade, 1, std::max<long long>(1, grain_length_ - crossfade_length_));
716 const long long fade_out = crossfade_length_;
717 const long long interior_start = grain_length_ -
fade_out;
719 for (
long long k = 0;
k < grain_length_; ++
k) {
720 const auto source =
static_cast<std::size_t
>(in_pos +
k) & ring_mask_;
724 splice_weights(
static_cast<double>(k) /
static_cast<double>(fade_in),
727 }
else if (k >= interior_start) {
729 splice_weights(
static_cast<double>(k - interior_start) /
730 static_cast<double>(fade_out),
734 const auto slot =
static_cast<std::size_t
>(out_pos +
k) & accum_mask_;
735 if (accum_epoch_[slot] != accum_generation_) {
737 accum_epoch_[slot] = accum_generation_;
739 accum_[slot] +=
static_cast<double>(ring_[source]) * weight;
749 grain_out_pos_ += std::max(hop_length_, fade_in);
751 end_two_grains_ago_ = end_previous_grain_;
752 end_previous_grain_ = out_pos + grain_length_;
759 void update_lengths() noexcept {
760 const double max_cycle_hz = sample_rate_ /
static_cast<double>(
kMinCycleSamples);
761 const double hz = std::min(cycle_hz_, max_cycle_hz);
763 std::llround(sample_rate_ / hz));
765 grain_length_ = cycle_length_ * grain_periods_;
766 if (max_grain_ > 0) grain_length_ = std::min(grain_length_, max_grain_);
768 crossfade_length_ = std::llround(crossfade_pct_ * 0.01 *
769 static_cast<double>(grain_length_));
772 crossfade_length_ = std::clamp<long long>(crossfade_length_, 1, grain_length_ / 2);
775 grain_length_ - crossfade_length_);
777 capture_window_ = std::max<long long>(
779 static_cast<long long>(std::llround(
781 capture_window_ = std::min<long long>(
782 capture_window_,
static_cast<long long>(ring_mask_));
786 double sample_rate_ = 48000.0;
794 double output_gain_ = 1.0;
797 long long cycle_length_ = 240;
798 long long grain_length_ = 240;
799 long long crossfade_length_ = 60;
800 long long hop_length_ = 180;
801 long long capture_window_ = 96000;
802 long long max_grain_ = 0;
811 std::vector<SampleType> ring_{};
812 std::vector<double> accum_{};
813 std::vector<std::uint64_t> accum_epoch_{};
814 std::uint64_t accum_generation_ = 1u;
815 std::size_t ring_mask_ = 0;
816 std::size_t accum_mask_ = 0;
817 std::size_t write_index_ = 0;
818 long long total_written_ = 0;
819 long long out_read_ = 0;
820 long long grain_ = 0;
833 long long grain_out_pos_ = 0;
838 long long read_pos_ = 0;
839 long long prev_out_pos_ = 0;
844 long long previous_fade_out_ = 60;
849 long long end_previous_grain_ = 0;
850 long long end_two_grains_ago_ = 0;
The fixed-cycle splice stretcher.
Definition cyclic_stretch.hpp:265
double stretch_ratio() const noexcept
Definition cyclic_stretch.hpp:476
double flutter_hz() const noexcept
The metallic flutter fundamental, fs / S, in Hz.
Definition cyclic_stretch.hpp:468
static constexpr double kStretchRatioMax
Definition cyclic_stretch.hpp:300
static constexpr int kGrainPeriodsDefault
Definition cyclic_stretch.hpp:280
static constexpr double kStretchRatioDefault
Definition cyclic_stretch.hpp:301
void set_regime(const CyclicStretchRegime ®ime)
Apply one of the two documented regimes in a single call.
Definition cyclic_stretch.hpp:450
static constexpr double kCaptureMsMax
Definition cyclic_stretch.hpp:307
static constexpr double kOutputDbMin
Definition cyclic_stretch.hpp:317
static constexpr double kCycleHzDefault
Definition cyclic_stretch.hpp:275
static constexpr double kMixDefault
Definition cyclic_stretch.hpp:312
void reset()
Never allocates. A zero-initialised instance is already in this state.
Definition cyclic_stretch.hpp:370
void prepare(double sample_rate)
Sizes the capture ring for kCaptureMsMax and the accumulator for the largest grain the parameter rang...
Definition cyclic_stretch.hpp:341
void set_grain_periods(int periods)
Definition cyclic_stretch.hpp:412
static constexpr int kGrainPeriodsMax
Definition cyclic_stretch.hpp:279
void set_stretch_ratio(double ratio) noexcept
Definition cyclic_stretch.hpp:428
void set_capture_ms(double ms)
Definition cyclic_stretch.hpp:433
static constexpr double kCaptureMsDefault
Definition cyclic_stretch.hpp:308
void set_output_db(double db) noexcept
Definition cyclic_stretch.hpp:444
double worst_case_gain() const noexcept
The proven bound on |out| / max|in| — see the file doc block.
Definition cyclic_stretch.hpp:488
static constexpr int kMinCycleSamples
Definition cyclic_stretch.hpp:325
int crossfade_samples() const noexcept
X — splice overlap in samples.
Definition cyclic_stretch.hpp:464
long long grain_input_position(long long g) const noexcept
The schedule at grain index g, valid while the resolved lengths hold still — out_pos(g) = g·S only as...
Definition cyclic_stretch.hpp:506
void set_crossfade_pct(double pct)
Definition cyclic_stretch.hpp:417
int cycle_samples() const noexcept
L — cycle length in samples.
Definition cyclic_stretch.hpp:460
int hop_samples() const noexcept
S — synthesis hop in samples.
Definition cyclic_stretch.hpp:466
static constexpr double kCrossfadePctMin
Definition cyclic_stretch.hpp:286
static constexpr double kCycleHzMin
Definition cyclic_stretch.hpp:273
static constexpr double kWorstCaseGain
Definition cyclic_stretch.hpp:334
void set_crossfade_shape(double shape) noexcept
Definition cyclic_stretch.hpp:423
void set_cycle_hz(double hz)
Definition cyclic_stretch.hpp:406
static constexpr double kCrossfadePctDefault
Definition cyclic_stretch.hpp:288
int latency_samples() const noexcept
The initial fill, reported exactly (series law 5).
Definition cyclic_stretch.hpp:483
static constexpr double kOutputDbDefault
Definition cyclic_stretch.hpp:319
static constexpr double kCrossfadeShapeDefault
Definition cyclic_stretch.hpp:292
long long schedule_input_position(long long out_pos) const noexcept
The splice schedule at a given OUTPUT position — no clamping, no buffer state, a pure function of the...
Definition cyclic_stretch.hpp:497
long long grain_count() const noexcept
Number of grains emitted since reset().
Definition cyclic_stretch.hpp:511
double crossfade_shape() const noexcept
Definition cyclic_stretch.hpp:477
long long total_captured() const noexcept
Input samples captured since reset().
Definition cyclic_stretch.hpp:525
static constexpr double kOutputDbMax
Definition cyclic_stretch.hpp:318
long long next_grain_out_pos() const noexcept
The next grain's output position, in absolute output samples.
Definition cyclic_stretch.hpp:514
static constexpr int kGrainPeriodsMin
Definition cyclic_stretch.hpp:278
static constexpr double kStretchRatioMin
Definition cyclic_stretch.hpp:299
long long capture_window_samples() const noexcept
The active capture window in samples — what the falling-behind clamp folds against,...
Definition cyclic_stretch.hpp:473
void set_mix(double percent) noexcept
Definition cyclic_stretch.hpp:439
long long read_position() const noexcept
The live read cursor, in absolute INPUT samples — where the last grain actually read from after the b...
Definition cyclic_stretch.hpp:522
static constexpr int kMinHopSamples
Definition cyclic_stretch.hpp:330
int grain_samples() const noexcept
N — grain length in samples.
Definition cyclic_stretch.hpp:462
static constexpr double kCaptureMsMin
Definition cyclic_stretch.hpp:306
static constexpr double kCycleHzMax
Definition cyclic_stretch.hpp:274
static constexpr double kCrossfadePctMax
Definition cyclic_stretch.hpp:287
double sample_rate() const noexcept
Definition cyclic_stretch.hpp:475
void process(const SampleType *in, SampleType *out, int n)
One block.
Definition cyclic_stretch.hpp:532
The one shared old→new crossfade gain law for the SIGNAL side.
T ms_to_samples(T ms, T sample_rate)
Milliseconds → samples at sample_rate Hz.
Definition units.hpp:147
T db_to_linear(T db)
Amplitude dB → linear gain. 0 dB → 1, −6.0206 dB → 0.5.
Definition units.hpp:71
Definition additive_bank.hpp:218
@ newest
The newest captured frame's phase increment (the original estimate).
Definition freeze_hold.hpp:126
CyclicStretchT< float > CyclicStretch
Definition cyclic_stretch.hpp:853
@ falling
Definition rise_fall_generator.hpp:19
@ rising
Definition rise_fall_generator.hpp:19
long long round_half_even(double x) noexcept
Round half to even, in integer logic.
Definition cyclic_stretch.hpp:227
@ oldest
Recycle the grain furthest through its window — the quietest one when durations match,...
Definition granular.hpp:123
constexpr CyclicStretchRegime kCyclicStretchLongFrame
"Glitchy long frame" — tens of milliseconds per grain, a short linear splice, cycle_hz left wherever ...
Definition cyclic_stretch.hpp:261
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
@ ratio
base_frequency_hz * ratio.
Definition fm_operator_engine.hpp:20
@ 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
@ u
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
CyclicStretchT< double > CyclicStretch64
Definition cyclic_stretch.hpp:854
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
constexpr CyclicStretchRegime kCyclicStretchShortFrame
"Clean-ish short frame" — one cycle per grain, a generous equal-power splice, cycle_hz meant to be tu...
Definition cyclic_stretch.hpp:253
@ fade_out
Definition waveform_editor_primitives.hpp:78
@ fade_in
Definition waveform_editor_primitives.hpp:77
@ k
Definition input_events.hpp:40
One of the two documented sonic regimes.
Definition cyclic_stretch.hpp:240
double crossfade_shape
Definition cyclic_stretch.hpp:244
int grain_periods
Definition cyclic_stretch.hpp:242
double cycle_hz
Definition cyclic_stretch.hpp:241
double crossfade_pct
Definition cyclic_stretch.hpp:243
The real-unit vocabulary shared by every DSP block in the catalog.