149template <
typename SampleType =
float>
253 sample_rate_ = std::isfinite(sample_rate) && sample_rate > 0.0 ? sample_rate
255 ring_length_ =
static_cast<int>(std::ceil(
kRingSeconds * sample_rate_));
256 ring_.assign(
static_cast<std::size_t
>(ring_length_), SampleType{0});
269 std::fill(ring_.begin(), ring_.end(), SampleType{0});
270 reset_runtime_state();
279 void audio_fault_reset() {
280 reset_runtime_state();
283 void reset_runtime_state() {
291 playhead_ = position01_ *
static_cast<double>(std::max(buffer_length_, 1));
305 void set_buffer(
const SampleType* data,
int length,
int channels) {
307 buffer_length_ = std::max(length, 0);
308 buffer_channels_ = std::max(channels, 1);
309 playhead_ = position01_ *
static_cast<double>(std::max(buffer_length_, 1));
316 if (ring_length_ <= 0)
return;
319 if (!accepts_live_input())
return;
320 for (
int i = 0;
i < n; ++
i) {
321 if (!std::isfinite(
static_cast<double>(in[
i]))) {
334 if (!std::isfinite(rate))
return;
343 if (!std::isfinite(pos01))
return;
344 position01_ = std::clamp(pos01, 0.0, 1.0);
346 playhead_ = position01_ *
static_cast<double>(std::max(buffer_length_, 1));
352 if (!std::isfinite(ms))
return;
360 if (!std::isfinite(hz))
return;
366 if (!std::isfinite(ms))
return;
373 if (!std::isfinite(j01))
return;
374 jitter_ = std::clamp(j01, 0.0, 1.0);
384 max_grains_ = clamped;
397 if (!std::isfinite(t01))
return;
398 taper_ = std::clamp(t01, 0.0, 1.0);
404 trapezoid_ = trapezoid;
410 if (!std::isfinite(st))
return;
416 if (!std::isfinite(st))
return;
421 if (!std::isfinite(s01))
return;
422 pan_spray_ = std::clamp(s01, 0.0, 1.0);
432 if (!std::isfinite(db))
return;
441 if (!std::isfinite(mix01))
return;
442 mix_target_ = std::clamp(mix01, 0.0, 1.0);
443 mix_.set_target(mix_target_);
445 double mix()
const {
return mix_target_; }
449 std::uint32_t
seed()
const {
return seed_; }
459 double mean_overlap()
const {
return density_hz_ * grain_ms_ * 0.001; }
465 ? 1.0 / (window_rms_ * std::sqrt(overlap))
466 : 1.0 / (window_mean_ * overlap);
467 return std::min(1.0, raw);
478 const double x = std::clamp(phase, 0.0, 1.0) *
static_cast<double>(
kWindowTableSize);
479 int index =
static_cast<int>(x);
481 const double frac = x -
static_cast<double>(index);
482 const double a = window_[
static_cast<std::size_t
>(index)];
483 const double b = window_[
static_cast<std::size_t
>(index) + 1];
484 return a + frac * (b -
a);
489 for (
int i = 0;
i < max_grains_; ++
i) {
490 if (grains_[
static_cast<std::size_t
>(
i)].active) ++count;
498 const Grain& g = grains_[
static_cast<std::size_t
>(slot)];
499 view.active = g.active;
500 view.phase = g.phase;
501 view.read_position = g.read_position;
502 view.ratio = g.ratio;
504 view.pan_left = g.pan_left;
505 view.pan_right = g.pan_right;
524 if (index < 0 || index >= ring_length_)
return SampleType{0};
525 return ring_[
static_cast<std::size_t
>(index)];
536 return static_cast<int>(
544 void process(SampleType* out_left, SampleType* out_right,
int n) {
545 render(
nullptr, out_left, out_right, n);
558 void process(
const SampleType* in, SampleType* out_left, SampleType* out_right,
int n) {
559 render(in, out_left, out_right, n);
563 bool accepts_live_input()
const {
570 double phase_increment = 0.0;
571 double read_position = 0.0;
574 double pan_left = 0.0;
575 double pan_right = 0.0;
578 static constexpr double kPi = 3.14159265358979323846;
586 void rebuild_window() {
589 window_[
static_cast<std::size_t
>(
i)] =
598 const double w = window_[
static_cast<std::size_t
>(
i)];
603 window_rms_ = std::sqrt(sum_squares /
static_cast<double>(
kWindowTableSize));
606 double window_shape(
double p)
const {
607 if (taper_ <= 0.0)
return 1.0;
608 const double half = 0.5 * taper_;
610 if (edge >= half)
return 1.0;
611 const double t = edge /
half;
612 if (trapezoid_)
return t;
615 return 0.5 * (1.0 - std::cos(kPi * t));
630 double onset_interval_samples(std::uint64_t k)
const {
631 const double h = sample_rate_ / density_hz_;
632 if (jitter_ <= 0.0)
return h;
636 const double blended = (1.0 - jitter_) * h + jitter_ *
exponential;
640 int allocate_slot() {
641 for (
int i = 0;
i < max_grains_; ++
i) {
642 if (!grains_[
static_cast<std::size_t
>(
i)].active)
return i;
650 double best_phase = -1.0;
651 for (
int i = 0;
i < max_grains_; ++
i) {
652 const double phase = grains_[
static_cast<std::size_t
>(
i)].phase;
653 if (phase > best_phase) {
659 double best_level = 1e300;
660 for (
int i = 0;
i < max_grains_; ++
i) {
661 const Grain&
g = grains_[
static_cast<std::size_t
>(
i)];
663 if (level < best_level) {
673 void spawn(std::uint64_t k) {
678 const double semitones = pitch_st_ + (u_pitch * 2.0 - 1.0) * pitch_spray_st_;
682 const double grain_seconds = grain_ms_ * 0.001;
684 next.phase_increment = 1.0 / std::max(grain_seconds * sample_rate_, 1.0);
690 : live_centre_ - spray;
692 next.read_position = requested_read;
693 }
else if (!place_live_read(requested_read, next.ratio, grain_seconds,
694 next.read_position)) {
705 const int slot = allocate_slot();
706 if (slot < 0)
return;
707 if (next.read_position != requested_read) ++clamp_count_;
709 panner_.set_pan(std::clamp((u_pan * 2.0 - 1.0) * pan_spray_, -1.0, 1.0));
710 panner_.compute_gains(next.pan_left, next.pan_right);
713 grains_[
static_cast<std::size_t
>(slot)] = next;
741 bool place_live_read(
double start,
double ratio,
double grain_seconds,
double& out) {
742 const double duration = grain_seconds * sample_rate_;
743 const double head =
static_cast<double>(write_head_);
744 const double newest = head - std::max(0.0,
ratio - 1.0) * duration - 2.0;
746 std::max(0.0, head -
static_cast<double>(ring_length_) +
747 std::max(0.0, 1.0 -
ratio) * duration) +
756 void write_one(SampleType x) {
757 ring_[
static_cast<std::size_t
>(write_head_ % ring_length_)] = x;
763 static int wrap_index(std::int64_t index,
int length) {
764 std::int64_t
m =
index % length;
765 if (m < 0)
m += length;
766 return static_cast<int>(
m);
769 double buffer_frame(std::int64_t frame)
const {
770 if (buffer_ ==
nullptr || buffer_length_ <= 0)
return 0.0;
771 const int index = wrap_index(frame, buffer_length_);
776 for (
int c = 0;
c < buffer_channels_; ++
c) {
777 const double sample =
static_cast<double>(
778 buffer_[
static_cast<std::size_t
>(
index) *
static_cast<std::size_t
>(buffer_channels_) +
779 static_cast<std::size_t
>(
c)]);
780 if (!std::isfinite(sample))
return 0.0;
783 return sum /
static_cast<double>(buffer_channels_);
786 double ring_sample(std::int64_t index)
const {
787 if (ring_length_ <= 0)
return 0.0;
788 return static_cast<double>(ring_[
static_cast<std::size_t
>(wrap_index(index, ring_length_))]);
791 double read_source(
double position)
const {
792 const double floored = std::floor(
position);
793 const auto base =
static_cast<std::int64_t
>(floored);
799 y0 = buffer_frame(base);
800 y1 = buffer_frame(base + 1);
802 y0 = ring_sample(base);
803 y1 = ring_sample(base + 1);
818 void render(
const SampleType* in, SampleType* out_left, SampleType* out_right,
int n) {
819 const double buffer_span =
static_cast<double>(std::max(buffer_length_, 1));
820 for (
int i = 0;
i <
n; ++
i) {
821 const double input_sample = in !=
nullptr ?
static_cast<double>(in[
i]) : 0.0;
822 if (in !=
nullptr && !std::isfinite(input_sample)) {
827 out_left[
i] = SampleType{0};
828 out_right[
i] = SampleType{0};
831 if (in !=
nullptr && accepts_live_input()) {
836 live_centre_ =
static_cast<double>(write_head_) -
837 position01_ *
static_cast<double>(ring_length_);
842 while (next_onset_ <=
static_cast<double>(sample_clock_)) {
844 next_onset_ += onset_interval_samples(grain_index_);
850 for (
int s = 0;
s < max_grains_; ++
s) {
851 Grain&
g = grains_[
static_cast<std::size_t
>(
s)];
852 if (!
g.active)
continue;
853 const double y = read_source(
g.read_position) *
window_at(
g.phase) *
g.gain;
856 g.read_position +=
g.ratio;
857 g.phase +=
g.phase_increment;
858 if (
g.phase >= 1.0)
g.active =
false;
861 const double level = level_.next();
862 const double blend = mix_.next();
863 const double dry = input_sample;
864 const double wet_weight = in !=
nullptr ? blend : 1.0;
865 const double dry_weight = in !=
nullptr ? 1.0 - blend : 0.0;
867 out_left[
i] =
static_cast<SampleType
>(
869 out_right[
i] =
static_cast<SampleType
>(
873 playhead_ += stretch_;
874 if (playhead_ >= buffer_span) playhead_ -= buffer_span;
875 if (playhead_ < 0.0) playhead_ += buffer_span;
883 double sample_rate_ = 44100.0;
886 const SampleType* buffer_ =
nullptr;
887 int buffer_length_ = 0;
888 int buffer_channels_ = 1;
890 std::vector<SampleType> ring_{};
891 int ring_length_ = 0;
892 int guard_samples_ = 0;
893 std::int64_t write_head_ = 0;
894 double live_centre_ = 0.0;
900 double position01_ = 0.0;
901 double position_spray_ms_ = 0.0;
902 double pitch_st_ = 0.0;
903 double pitch_spray_st_ = 0.0;
904 double pan_spray_ = 0.0;
906 bool trapezoid_ =
false;
907 double level_db_ = 0.0;
908 double mix_target_ = 1.0;
916 std::vector<double> window_{};
917 double window_mean_ = 1.0;
918 double window_rms_ = 1.0;
921 double playhead_ = 0.0;
922 double next_onset_ = 0.0;
923 std::int64_t sample_clock_ = 0;
924 std::uint64_t grain_index_ = 0;
925 std::uint64_t steal_count_ = 0;
926 std::uint64_t clamp_count_ = 0;
928 SmoothedValue<double> level_{1.0};
929 SmoothedValue<double> mix_{1.0};
930 PannerT<double> panner_{};
Definition granular.hpp:150
static constexpr double kDefaultDensityHz
Definition granular.hpp:180
std::uint64_t grain_index() const
Grains born since reset(). Monotonic; this is the key every draw uses.
Definition granular.hpp:510
double window_taper() const
Definition granular.hpp:401
GrainInterp interp() const
Definition granular.hpp:429
void set_source(GrainSource source)
Definition granular.hpp:300
double window_at(double phase) const
Window value at a normalised grain phase — the same table read the audio path uses,...
Definition granular.hpp:477
void set_grain_ms(double ms)
Definition granular.hpp:365
void set_position(double pos01)
Playhead centre as a fraction of the source.
Definition granular.hpp:342
static constexpr double kRingSeconds
Definition granular.hpp:175
void set_max_grains(int count)
Grains in slots above the new budget stop immediately, so lowering the budget cannot leave a grain so...
Definition granular.hpp:381
static constexpr int kMaxGrainBudget
Definition granular.hpp:158
static constexpr std::uint64_t kFieldPitchSpray
Definition granular.hpp:240
void set_pan_spray(double s01)
Definition granular.hpp:420
StealPolicy steal_policy() const
Definition granular.hpp:389
Coherence coherence() const
Definition granular.hpp:426
double position_spray_ms() const
Definition granular.hpp:355
void set_coherence(Coherence coherence)
Definition granular.hpp:425
double grain_gain() const
Per-grain gain the next spawn will use, from the current law and params.
Definition granular.hpp:462
static constexpr int kMinGrainBudget
Definition granular.hpp:162
static constexpr std::uint64_t kFieldOnsetJitter
Definition granular.hpp:238
int causality_guard_samples() const
The derived causality guard, in samples: the worst-case total displacement a grain's read pointer can...
Definition granular.hpp:532
double pitch_semitones() const
Definition granular.hpp:413
static constexpr std::uint64_t kFieldPanSpray
Definition granular.hpp:241
static constexpr int kDefaultMaxGrains
Definition granular.hpp:165
void set_position_spray_ms(double ms)
Definition granular.hpp:351
void set_buffer(const SampleType *data, int length, int channels)
Borrows an external interleaved buffer.
Definition granular.hpp:305
std::uint64_t clamp_count() const
Spawns whose read position the causality clamp had to move.
Definition granular.hpp:516
static constexpr double kMaxDensityHz
Definition granular.hpp:182
static constexpr double kMaxPitchSpraySemitones
Definition granular.hpp:206
static constexpr double kDefaultStretch
Definition granular.hpp:198
void set_pitch_semitones(double st)
Definition granular.hpp:409
static constexpr double kMaxPitchSemitones
Definition granular.hpp:203
void write_live(const SampleType *in, int n)
Pushes n mono samples into the live ring.
Definition granular.hpp:315
GrainSource source() const
Definition granular.hpp:301
void process(const SampleType *in, SampleType *out_left, SampleType *out_right, int n)
Live render that consumes its own input, one sample at a time.
Definition granular.hpp:558
static constexpr double kDefaultJitter
Definition granular.hpp:192
static constexpr double kMinLevelDb
Definition granular.hpp:209
GrainView grain(int slot) const
Definition granular.hpp:495
double window_mean() const
Measured from the shipped table, not assumed.
Definition granular.hpp:472
double grain_ms() const
Definition granular.hpp:369
static constexpr double kMaxGrainMs
Definition granular.hpp:189
void set_stretch(double rate)
Playhead advance multiplier.
Definition granular.hpp:333
int ring_length() const
Definition granular.hpp:518
void set_mix(double mix01)
Dry/wet, 0 = dry .
Definition granular.hpp:440
int active_grain_count() const
Definition granular.hpp:487
bool window_trapezoid() const
Definition granular.hpp:407
void set_steal_policy(StealPolicy policy)
Definition granular.hpp:388
void prepare(double sample_rate)
Sizes the live ring and the window table.
Definition granular.hpp:252
static constexpr int kWindowTableSize
Definition granular.hpp:169
void reset()
Clears grains and the ring, rewinds the schedule, and restarts the random stream.
Definition granular.hpp:268
static constexpr double kMinDensityHz
Definition granular.hpp:181
double mean_overlap() const
Mean simultaneous overlap N̄ = D·T_g — the quantity both normalization laws are written in terms of.
Definition granular.hpp:459
static int derived_guard_samples(double sample_rate)
Definition granular.hpp:534
static constexpr double kMinGrainMs
Definition granular.hpp:188
static constexpr double kMaxStretch
Definition granular.hpp:199
static constexpr double kDefaultGrainMs
Definition granular.hpp:187
void set_window_taper(double t01)
0 is rectangular, 1 is Hann (cosine mode) or triangle (trapezoid mode); in between is a Tukey / flat-...
Definition granular.hpp:396
void process(SampleType *out_left, SampleType *out_right, int n)
Pure-source render.
Definition granular.hpp:544
static constexpr std::uint32_t kDefaultSeed
Definition granular.hpp:214
static constexpr double kControlSmoothingMs
Definition granular.hpp:220
static constexpr double kMinOnsetIntervalSamples
Definition granular.hpp:232
void set_density_hz(double hz)
Definition granular.hpp:359
double window_rms() const
Definition granular.hpp:473
static constexpr double kMaxPositionSprayMs
Definition granular.hpp:195
int max_grains() const
Definition granular.hpp:386
double async_jitter() const
Definition granular.hpp:376
void set_async_jitter(double j01)
0 is the exact clock; 1 is a true Poisson process.
Definition granular.hpp:372
std::size_t latency_samples() const
Zero.
Definition granular.hpp:455
void set_seed(std::uint32_t seed)
Fixed at reset(); never automatable (series law 2).
Definition granular.hpp:448
void set_interp(GrainInterp interp)
Definition granular.hpp:428
std::uint32_t seed() const
Definition granular.hpp:449
static constexpr std::uint64_t kFieldPositionSpray
Definition granular.hpp:239
void set_level_db(double db)
Definition granular.hpp:431
static constexpr double kMinDrawForLog
Definition granular.hpp:225
static constexpr double kMaxLevelDb
Definition granular.hpp:210
SampleType ring_storage_sample(int index) const
Physical live-ring storage for diagnostics.
Definition granular.hpp:523
GranularEngineT()
Definition granular.hpp:243
void set_window_trapezoid(bool trapezoid)
Definition granular.hpp:403
double stretch() const
Definition granular.hpp:337
double mix() const
Definition granular.hpp:445
void set_pitch_spray_semitones(double st)
Definition granular.hpp:415
std::uint64_t steal_count() const
Grains recycled out from under a still-sounding predecessor.
Definition granular.hpp:513
double density_hz() const
Definition granular.hpp:363
double position() const
Definition granular.hpp:349
void set_immediate(T value)
Definition smoothed_value.hpp:71
High-quality sample interpolation for delay lines and resampling.
@ g
Definition pitch.hpp:27
@ index
Definition gpu_pass.hpp:140
T semitones_to_ratio(T semitones)
Semitones → frequency ratio. +12 → 2, −12 → 0.5.
Definition units.hpp:105
T ms_to_samples(T ms, T sample_rate)
Milliseconds → samples at sample_rate Hz.
Definition units.hpp:147
@ half
Definition units.hpp:290
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
GrainSource
Where grains read from.
Definition granular.hpp:109
@ buffer
A borrowed static buffer with a traversable playhead.
Definition granular.hpp:110
@ live_ring
A continuously captured ring of recent input.
Definition granular.hpp:111
@ newest
The newest captured frame's phase increment (the original estimate).
Definition freeze_hold.hpp:126
@ right
Definition multi_channel_meter.hpp:67
@ left
Definition multi_channel_meter.hpp:66
@ exponential
Definition mod_tools.hpp:106
@ c
Definition spectrum_trace.hpp:35
constexpr std::uint64_t mix64(std::uint64_t z)
splitmix64 finalizer (Steele, Lea & Flood, OOPSLA 2014).
Definition rng.hpp:140
StealPolicy
What happens when a grain is due and the pool is full.
Definition granular.hpp:122
@ drop_new
Skip the incoming grain. Thins clouds rather than cutting.
Definition granular.hpp:126
@ oldest
Recycle the grain furthest through its window — the quietest one when durations match,...
Definition granular.hpp:123
@ quietest
Recycle the smallest current w(phase)·g. For mixed lengths.
Definition granular.hpp:125
@ EqualPower
Definition panner.hpp:25
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
@ a
Definition formant_filter_bank.hpp:20
@ u
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
@ e
Definition formant_filter_bank.hpp:20
Coherence
Which normalization law applies — a statement about whether grains are correlated with each other,...
Definition granular.hpp:116
@ incoherent
Scattered grains; powers add; 1/(w_rms·√N̄).
Definition granular.hpp:117
@ coherent
Grid-aligned grains; amplitudes add; 1/(w_mean·N̄).
Definition granular.hpp:118
GrainInterp
Fractional-read quality inside a grain.
Definition granular.hpp:132
@ cubic_hermite
4-point Catmull-Rom.
Definition granular.hpp:134
@ linear
2-point. Cheapest; audible imaging when pitching up.
Definition granular.hpp:133
GranularEngineT< float > GranularEngine
Definition granular.hpp:933
GranularEngineT< double > GranularEngine64
Definition granular.hpp:934
float unit_from(std::uint64_t key)
Stateless purpose-keyed draw in [0, 1).
Definition rng.hpp:164
float sum(const float *data, std::size_t count) noexcept
float sum_squares(const float *data, std::size_t count) noexcept
Hover-cursor tracking for platform window hosts.
Definition fwd.hpp:27
@ s
Definition input_events.hpp:41
@ h
Definition input_events.hpp:39
@ y
Definition input_events.hpp:42
@ m
Definition input_events.hpp:40
@ n
Definition input_events.hpp:40
@ p
Definition input_events.hpp:40
@ t
Definition input_events.hpp:41
@ w
Definition input_events.hpp:41
Deterministic randomness for modulation: a counter-free PRNG, a Gaussian draw, a stateless purpose-ke...
A read-only view of one grain, for metering and grain-cloud displays.
Definition granular.hpp:139
double pan_right
Definition granular.hpp:146
double phase
Definition granular.hpp:141
double ratio
Definition granular.hpp:143
double read_position
Definition granular.hpp:142
double pan_left
Definition granular.hpp:145
double gain
Definition granular.hpp:144
bool active
Definition granular.hpp:140
static SampleType linear(SampleType frac, SampleType y0, SampleType y1)
Linear interpolation between two samples.
Definition interpolator.hpp:33
static SampleType hermite(SampleType frac, SampleType ym1, SampleType y0, SampleType y1, SampleType y2)
Cubic Hermite (Catmull-Rom) interpolation.
Definition interpolator.hpp:41
The real-unit vocabulary shared by every DSP block in the catalog.