Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pulp::signal::GranularEngineT< SampleType > Class Template Reference

#include <granular.hpp>

Public Member Functions

 GranularEngineT ()
void prepare (double sample_rate)
 Sizes the live ring and the window table.
void reset ()
 Clears grains and the ring, rewinds the schedule, and restarts the random stream.
void set_source (GrainSource source)
GrainSource source () const
void set_buffer (const SampleType *data, int length, int channels)
 Borrows an external interleaved buffer.
void write_live (const SampleType *in, int n)
 Pushes n mono samples into the live ring.
void set_stretch (double rate)
 Playhead advance multiplier.
double stretch () const
void set_position (double pos01)
 Playhead centre as a fraction of the source.
double position () const
void set_position_spray_ms (double ms)
double position_spray_ms () const
void set_density_hz (double hz)
double density_hz () const
void set_grain_ms (double ms)
double grain_ms () const
void set_async_jitter (double j01)
 0 is the exact clock; 1 is a true Poisson process.
double async_jitter () const
void set_max_grains (int count)
 Grains in slots above the new budget stop immediately, so lowering the budget cannot leave a grain sounding from a slot the pool no longer scans.
int max_grains () const
void set_steal_policy (StealPolicy policy)
StealPolicy steal_policy () const
void set_window_taper (double t01)
 0 is rectangular, 1 is Hann (cosine mode) or triangle (trapezoid mode); in between is a Tukey / flat-topped trapezoid whose tapers occupy taper/2 of the grain at each end.
double window_taper () const
void set_window_trapezoid (bool trapezoid)
bool window_trapezoid () const
void set_pitch_semitones (double st)
double pitch_semitones () const
void set_pitch_spray_semitones (double st)
void set_pan_spray (double s01)
void set_coherence (Coherence coherence)
Coherence coherence () const
void set_interp (GrainInterp interp)
GrainInterp interp () const
void set_level_db (double db)
void set_mix (double mix01)
 Dry/wet, 0 = dry .
double mix () const
void set_seed (std::uint32_t seed)
 Fixed at reset(); never automatable (series law 2).
std::uint32_t seed () const
std::size_t latency_samples () const
 Zero.
double mean_overlap () const
 Mean simultaneous overlap N̄ = D·T_g — the quantity both normalization laws are written in terms of.
double grain_gain () const
 Per-grain gain the next spawn will use, from the current law and params.
double window_mean () const
 Measured from the shipped table, not assumed.
double window_rms () const
double window_at (double phase) const
 Window value at a normalised grain phase — the same table read the audio path uses, exposed so a test measures what the engine plays.
int active_grain_count () const
GrainView grain (int slot) const
std::uint64_t grain_index () const
 Grains born since reset(). Monotonic; this is the key every draw uses.
std::uint64_t steal_count () const
 Grains recycled out from under a still-sounding predecessor.
std::uint64_t clamp_count () const
 Spawns whose read position the causality clamp had to move.
int ring_length () const
SampleType ring_storage_sample (int index) const
 Physical live-ring storage for diagnostics.
int causality_guard_samples () const
 The derived causality guard, in samples: the worst-case total displacement a grain's read pointer can reach away from its nominal centre, which is the maximum position spray plus the longest source span a grain can consume at the highest pitch-up ratio.
void process (SampleType *out_left, SampleType *out_right, int n)
 Pure-source render.
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.

Static Public Member Functions

static int derived_guard_samples (double sample_rate)

Static Public Attributes

static constexpr int kMaxGrainBudget = 64
 Slot capacity of the grain pool.
static constexpr int kMinGrainBudget = 32
 Floor of the max_grains range.
static constexpr int kDefaultMaxGrains = 48
 [design parameter] default 48 grains, range 32 .. 64.
static constexpr int kWindowTableSize = 2048
 Window table resolution.
static constexpr double kRingSeconds = 4.0
 Live ring length in seconds.
static constexpr double kDefaultDensityHz = 40.0
 [design parameter] default 40 grains/s, range 0.1 .. 2000.
static constexpr double kMinDensityHz = 0.1
static constexpr double kMaxDensityHz = 2000.0
static constexpr double kDefaultGrainMs = 50.0
 [design parameter] default 50 ms, range 1 .
static constexpr double kMinGrainMs = 1.0
static constexpr double kMaxGrainMs = 500.0
static constexpr double kDefaultJitter = 0.0
 [design parameter] default 0 (synchronous clock), range 0 .. 1.
static constexpr double kMaxPositionSprayMs = 500.0
 [design parameter] default 0 ms, range 0 .. 500 ms.
static constexpr double kDefaultStretch = 1.0
 [design parameter] default 1, range 0 .. 4. Zero is freeze.
static constexpr double kMaxStretch = 4.0
static constexpr double kMaxPitchSemitones = 24.0
 [design parameter] default 0 st, range −24 .
static constexpr double kMaxPitchSpraySemitones = 12.0
 [design parameter] default 0 st, range 0 .. 12 st.
static constexpr double kMinLevelDb = -24.0
 [design parameter] default 0 dB, range −24 .. +12 dB.
static constexpr double kMaxLevelDb = 12.0
static constexpr std::uint32_t kDefaultSeed = 0x9E3779B9u
 Seed.
static constexpr double kControlSmoothingMs = 10.0
 Control-smoothing time for output level and dry/wet.
static constexpr double kMinDrawForLog = 1e-12
 Floor on a keyed draw before −ln(u).
static constexpr double kMinOnsetIntervalSamples = 1.0
 Floor on the inter-onset interval, in samples.
static constexpr std::uint64_t kFieldOnsetJitter = 1
static constexpr std::uint64_t kFieldPositionSpray = 2
static constexpr std::uint64_t kFieldPitchSpray = 3
static constexpr std::uint64_t kFieldPanSpray = 4

Constructor & Destructor Documentation

◆ GranularEngineT()

template<typename SampleType = float>
pulp::signal::GranularEngineT< SampleType >::GranularEngineT ( )
inline

Member Function Documentation

◆ active_grain_count()

template<typename SampleType = float>
int pulp::signal::GranularEngineT< SampleType >::active_grain_count ( ) const
inline

◆ async_jitter()

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::async_jitter ( ) const
inline

◆ causality_guard_samples()

template<typename SampleType = float>
int pulp::signal::GranularEngineT< SampleType >::causality_guard_samples ( ) const
inline

The derived causality guard, in samples: the worst-case total displacement a grain's read pointer can reach away from its nominal centre, which is the maximum position spray plus the longest source span a grain can consume at the highest pitch-up ratio.

Not a free constant.

◆ clamp_count()

template<typename SampleType = float>
std::uint64_t pulp::signal::GranularEngineT< SampleType >::clamp_count ( ) const
inline

Spawns whose read position the causality clamp had to move.

◆ coherence()

template<typename SampleType = float>
Coherence pulp::signal::GranularEngineT< SampleType >::coherence ( ) const
inline

◆ density_hz()

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::density_hz ( ) const
inline

◆ derived_guard_samples()

template<typename SampleType = float>
int pulp::signal::GranularEngineT< SampleType >::derived_guard_samples ( double sample_rate)
inlinestatic

◆ grain()

template<typename SampleType = float>
GrainView pulp::signal::GranularEngineT< SampleType >::grain ( int slot) const
inline

◆ grain_gain()

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::grain_gain ( ) const
inline

Per-grain gain the next spawn will use, from the current law and params.

◆ grain_index()

template<typename SampleType = float>
std::uint64_t pulp::signal::GranularEngineT< SampleType >::grain_index ( ) const
inline

Grains born since reset(). Monotonic; this is the key every draw uses.

◆ grain_ms()

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::grain_ms ( ) const
inline

◆ interp()

template<typename SampleType = float>
GrainInterp pulp::signal::GranularEngineT< SampleType >::interp ( ) const
inline

◆ latency_samples()

template<typename SampleType = float>
std::size_t pulp::signal::GranularEngineT< SampleType >::latency_samples ( ) const
inline

Zero.

Grains read past samples only; the position offset is musical, not codec latency.

◆ max_grains()

template<typename SampleType = float>
int pulp::signal::GranularEngineT< SampleType >::max_grains ( ) const
inline

◆ mean_overlap()

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::mean_overlap ( ) const
inline

Mean simultaneous overlap N̄ = D·T_g — the quantity both normalization laws are written in terms of.

◆ mix()

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::mix ( ) const
inline

◆ pitch_semitones()

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::pitch_semitones ( ) const
inline

◆ position()

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::position ( ) const
inline

◆ position_spray_ms()

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::position_spray_ms ( ) const
inline

◆ prepare()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::prepare ( double sample_rate)
inline

Sizes the live ring and the window table.

The only member that allocates.

◆ process() [1/2]

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::process ( const SampleType * in,
SampleType * out_left,
SampleType * out_right,
int n )
inline

Live render that consumes its own input, one sample at a time.

This overload exists because the write_live + process pair CANNOT be block-size independent: writing a whole block before rendering any of it puts samples in the ring that a grain rendered at the start of that block can legally read, and rendering sample-by-sample does not. Interleaving the two removes the ambiguity, so this is the overload whose output is identical at 1×N and N×1 — and it is also the natural shape for a plugin callback. It is additionally the only overload with a dry signal, so set_mix applies here and nowhere else.

◆ process() [2/2]

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::process ( SampleType * out_left,
SampleType * out_right,
int n )
inline

Pure-source render.

In live-ring mode the caller must have pushed this block's input with write_live first.

◆ reset()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::reset ( )
inline

Clears grains and the ring, rewinds the schedule, and restarts the random stream.

Restarting the stream costs nothing here: the draws are a pure function of the grain index, so rewinding the index to zero is reseeding. Never allocates.

◆ ring_length()

template<typename SampleType = float>
int pulp::signal::GranularEngineT< SampleType >::ring_length ( ) const
inline

◆ ring_storage_sample()

template<typename SampleType = float>
SampleType pulp::signal::GranularEngineT< SampleType >::ring_storage_sample ( int index) const
inline

Physical live-ring storage for diagnostics.

Logical validity is still governed by write_head_; after an audio fault this may expose stale storage that the renderer must not read until it has been overwritten.

◆ seed()

template<typename SampleType = float>
std::uint32_t pulp::signal::GranularEngineT< SampleType >::seed ( ) const
inline

◆ set_async_jitter()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_async_jitter ( double j01)
inline

0 is the exact clock; 1 is a true Poisson process.

◆ set_buffer()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_buffer ( const SampleType * data,
int length,
int channels )
inline

Borrows an external interleaved buffer.

The engine never owns, copies, or frees it; length is in FRAMES and channels is the interleave.

◆ set_coherence()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_coherence ( Coherence coherence)
inline

◆ set_density_hz()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_density_hz ( double hz)
inline

◆ set_grain_ms()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_grain_ms ( double ms)
inline

◆ set_interp()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_interp ( GrainInterp interp)
inline

◆ set_level_db()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_level_db ( double db)
inline

◆ set_max_grains()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_max_grains ( int count)
inline

Grains in slots above the new budget stop immediately, so lowering the budget cannot leave a grain sounding from a slot the pool no longer scans.

◆ set_mix()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_mix ( double mix01)
inline

Dry/wet, 0 = dry .

. 1 = wet. Only the interleaved process overload has a dry signal to blend; the two-output overload is a pure source and ignores this.

◆ set_pan_spray()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_pan_spray ( double s01)
inline

◆ set_pitch_semitones()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_pitch_semitones ( double st)
inline

◆ set_pitch_spray_semitones()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_pitch_spray_semitones ( double st)
inline

◆ set_position()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_position ( double pos01)
inline

Playhead centre as a fraction of the source.

In buffer mode this jumps the playhead; in live mode it is how far behind the write head grains sample, as a fraction of the ring.

◆ set_position_spray_ms()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_position_spray_ms ( double ms)
inline

◆ set_seed()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_seed ( std::uint32_t seed)
inline

Fixed at reset(); never automatable (series law 2).

◆ set_source()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_source ( GrainSource source)
inline

◆ set_steal_policy()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_steal_policy ( StealPolicy policy)
inline

◆ set_stretch()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_stretch ( double rate)
inline

Playhead advance multiplier.

1 is realtime, 0 freezes, 4 scans four times as fast. In live-ring mode there is no traversal to scale — the playhead IS the write head — so stretch acts purely as the freeze switch there: 0 holds the captured centre, anything above 0 tracks.

◆ set_window_taper()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_window_taper ( double t01)
inline

0 is rectangular, 1 is Hann (cosine mode) or triangle (trapezoid mode); in between is a Tukey / flat-topped trapezoid whose tapers occupy taper/2 of the grain at each end.

◆ set_window_trapezoid()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::set_window_trapezoid ( bool trapezoid)
inline

◆ source()

template<typename SampleType = float>
GrainSource pulp::signal::GranularEngineT< SampleType >::source ( ) const
inline

◆ steal_count()

template<typename SampleType = float>
std::uint64_t pulp::signal::GranularEngineT< SampleType >::steal_count ( ) const
inline

Grains recycled out from under a still-sounding predecessor.

◆ steal_policy()

template<typename SampleType = float>
StealPolicy pulp::signal::GranularEngineT< SampleType >::steal_policy ( ) const
inline

◆ stretch()

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::stretch ( ) const
inline

◆ window_at()

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::window_at ( double phase) const
inline

Window value at a normalised grain phase — the same table read the audio path uses, exposed so a test measures what the engine plays.

◆ window_mean()

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::window_mean ( ) const
inline

Measured from the shipped table, not assumed.

At full cosine taper these are the exact Hann identities 1/2 and √(3/8).

◆ window_rms()

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::window_rms ( ) const
inline

◆ window_taper()

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::window_taper ( ) const
inline

◆ window_trapezoid()

template<typename SampleType = float>
bool pulp::signal::GranularEngineT< SampleType >::window_trapezoid ( ) const
inline

◆ write_live()

template<typename SampleType = float>
void pulp::signal::GranularEngineT< SampleType >::write_live ( const SampleType * in,
int n )
inline

Pushes n mono samples into the live ring.

See the note on process's interleaved overload for why the two-call form is not block-size independent.

Member Data Documentation

◆ kControlSmoothingMs

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kControlSmoothingMs = 10.0
staticconstexpr

Control-smoothing time for output level and dry/wet.

Only these two are smoothed — every per-grain parameter is captured once at spawn, so it cannot zipper. [design parameter] default 10 ms, range 1 .. 100 ms.

◆ kDefaultDensityHz

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kDefaultDensityHz = 40.0
staticconstexpr

[design parameter] default 40 grains/s, range 0.1 .. 2000.

◆ kDefaultGrainMs

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kDefaultGrainMs = 50.0
staticconstexpr

[design parameter] default 50 ms, range 1 .

. 500 ms. Roads' typical granular range is 1–100 ms; the ceiling here is higher so the engine also covers slow overlapping-drone territory.

◆ kDefaultJitter

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kDefaultJitter = 0.0
staticconstexpr

[design parameter] default 0 (synchronous clock), range 0 .. 1.

◆ kDefaultMaxGrains

template<typename SampleType = float>
int pulp::signal::GranularEngineT< SampleType >::kDefaultMaxGrains = 48
staticconstexpr

[design parameter] default 48 grains, range 32 .. 64.

◆ kDefaultSeed

template<typename SampleType = float>
std::uint32_t pulp::signal::GranularEngineT< SampleType >::kDefaultSeed = 0x9E3779B9u
staticconstexpr

Seed.

Fixed at reset(), never automatable (series law 2). [design parameter] any uint32_t; no automation range applies.

◆ kDefaultStretch

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kDefaultStretch = 1.0
staticconstexpr

[design parameter] default 1, range 0 .. 4. Zero is freeze.

◆ kFieldOnsetJitter

template<typename SampleType = float>
std::uint64_t pulp::signal::GranularEngineT< SampleType >::kFieldOnsetJitter = 1
staticconstexpr

◆ kFieldPanSpray

template<typename SampleType = float>
std::uint64_t pulp::signal::GranularEngineT< SampleType >::kFieldPanSpray = 4
staticconstexpr

◆ kFieldPitchSpray

template<typename SampleType = float>
std::uint64_t pulp::signal::GranularEngineT< SampleType >::kFieldPitchSpray = 3
staticconstexpr

◆ kFieldPositionSpray

template<typename SampleType = float>
std::uint64_t pulp::signal::GranularEngineT< SampleType >::kFieldPositionSpray = 2
staticconstexpr

◆ kMaxDensityHz

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kMaxDensityHz = 2000.0
staticconstexpr

◆ kMaxGrainBudget

template<typename SampleType = float>
int pulp::signal::GranularEngineT< SampleType >::kMaxGrainBudget = 64
staticconstexpr

Slot capacity of the grain pool.

Pinned to the top of max_grains' declared range rather than independently ranged: raising it would require re-deriving the worst-case gain bound. [design parameter] 64 slots, pinned to the max_grains range ceiling.

◆ kMaxGrainMs

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kMaxGrainMs = 500.0
staticconstexpr

◆ kMaxLevelDb

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kMaxLevelDb = 12.0
staticconstexpr

◆ kMaxPitchSemitones

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kMaxPitchSemitones = 24.0
staticconstexpr

[design parameter] default 0 st, range −24 .

. +24 st. The ±24 bound is what sets r_max = 4 in the causality guard.

◆ kMaxPitchSpraySemitones

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kMaxPitchSpraySemitones = 12.0
staticconstexpr

[design parameter] default 0 st, range 0 .. 12 st.

◆ kMaxPositionSprayMs

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kMaxPositionSprayMs = 500.0
staticconstexpr

[design parameter] default 0 ms, range 0 .. 500 ms.

◆ kMaxStretch

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kMaxStretch = 4.0
staticconstexpr

◆ kMinDensityHz

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kMinDensityHz = 0.1
staticconstexpr

◆ kMinDrawForLog

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kMinDrawForLog = 1e-12
staticconstexpr

Floor on a keyed draw before −ln(u).

Guards log(0), which the 53-bit draw can produce with probability 2^-53. Any value far below the smallest non-zero draw works; range 1e-18 .. 1e-6.

◆ kMinGrainBudget

template<typename SampleType = float>
int pulp::signal::GranularEngineT< SampleType >::kMinGrainBudget = 32
staticconstexpr

Floor of the max_grains range.

[design parameter] 32, the bottom of the declared 32..64 budget.

◆ kMinGrainMs

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kMinGrainMs = 1.0
staticconstexpr

◆ kMinLevelDb

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kMinLevelDb = -24.0
staticconstexpr

[design parameter] default 0 dB, range −24 .. +12 dB.

◆ kMinOnsetIntervalSamples

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kMinOnsetIntervalSamples = 1.0
staticconstexpr

Floor on the inter-onset interval, in samples.

Bounds the spawn work per sample to one grain, which is what makes process wait-free rather than merely allocation-free. It never binds at jitter = 0, where the interval is fs/D ≥ 24 samples at the maximum density and 48 kHz. [design parameter] default 1 sample, range 1 .. 8.

◆ kRingSeconds

template<typename SampleType = float>
double pulp::signal::GranularEngineT< SampleType >::kRingSeconds = 4.0
staticconstexpr

Live ring length in seconds.

The floor of the range is not free: the causality guard below derives to a fixed 2.5 s at any sample rate, so the ring must clear that with enough margin left for a full grain span. [design parameter] default 4 s, range 3 .. 8 s.

◆ kWindowTableSize

template<typename SampleType = float>
int pulp::signal::GranularEngineT< SampleType >::kWindowTableSize = 2048
staticconstexpr

Window table resolution.

Powers of two only, so the phase index is a multiply. [design parameter] default 2048, range 512 .. 4096.


The documentation for this class was generated from the following file:
  • /home/runner/work/pulp/pulp/core/signal/include/pulp/signal/granular.hpp