Build with this node

Start with a prompt in Studio, or copy it into your own coding agent.

Ask Studio

@maruaudio/dsp/voices/grain-pool plays grains once something else has decided where, at what rate and for how long. A grain reads a mono source table from a start frame at a constant read step (negative reads backwards), is gated by one of the five grain-window curves over its length, and lands in the stereo field through a fixed left/right gain pair. Each frame, every live grain adds into caller-owned per-owner accumulators (an owner is whatever the caller groups grains by, such as a note voice), so each group can take its own envelope.

Parameters

Call / argumentCoded behavior
initGrainPool(ownerCapacity)Declares the positive integer number of owner accumulators, independently of the 128 grain slots
startGrain(pool, owner, start, step, length, shape, gainLeft, gainRight)Returns the slot, or −1 for an owner outside capacity, invalid signed32 start/length, or nonfinite f32 step/gains; start and length are floored
stepSource frames per output frame, stored as f32; negative reads backwards
shapeGrain-window index, floored; values outside [0, 5) use window 0
retuneGrainStep(pool, slot, step)Changes the rate of a live grain without moving its read head
grainPoolRenderFrame(pool, table, base, frames, left, right)Reads table[base + position] for positions in [0, frames); returns the live count, or −1 for invalid source/accumulator geometry without consuming grains

Lifecycle

initGrainPool(ownerCapacity) allocates the 128-slot arrays and renders the five window curves once (5 × 512 points) → startGrain per scheduled grain (a free slot, else the oldest grain is stolen and steals is counted for the owner's output bridge) → clear the accumulators, then grainPoolRenderFrame once per output frame → stopGrainPoolOwner(pool, owner) when a voice retires → resetGrainPool stops every grain and keeps the rendered curves.

When to use it

The grain engine inside a granular instrument or texture effect, under a scheduler that picks grain start points, rates and pans.

Limits

  • At most 128 grains (GRAIN_POOL_MAX_GRAINS); every loop scans all slots.
  • Position is recomputed each frame from the integer start and age (plus a fractional origin after a retune), so long sources do not drift in f32.
  • Linear interpolation; a frame outside [0, frames) reads 0, and the frame after the last reads as 0 for interpolation.
  • The window is read with setValueCurveAtTime's linear interpolation at age / length.
  • Both accumulator arrays must cover the declared owner capacity. The source region must be an integer range fully inside the table.
  • Table and output arrays are borrowed for one call. During preparation, isGrainPoolMemoryValid checks fixed backing and disjointness from each other and private pool storage. Revalidate when replacing a view; keep pool arrays private and clear the accumulators before rendering each frame.

Algorithm properties and proofs

No Lean 4 proof is mapped to this node.

Explore proofs alongside performance

Dependencies

Dependents

No other node imports this one.

Used by