Granular Synthesis¶
Granular synthesis decomposes audio into tiny fragments ("grains") and reassembles them with independent control over timing, pitch, density, and spatialization. nanodsp wraps GrainflowLib for block-based granular processing.
Basic granular cloud¶
import numpy as np
from nanodsp._core import grainflow as gf
from nanodsp.buffer import AudioBuffer
# Load source audio into a GfBuffer
source = AudioBuffer.from_file("input.wav")
gf_buf = gf.GfBuffer(source.data, source.sample_rate)
# Create a granulator with 32 grains
grains = gf.GrainCollection(n_grains=32, sample_rate=source.sample_rate)
grains.set_buffer(gf_buf, gf.BUF_MAIN)
# Configure grain parameters
grains.set_param(gf.PARAM_WINDOW, gf.PTYPE_VALUE, 0.5) # grain envelope shape
grains.set_param(gf.PARAM_RATE, gf.PTYPE_VALUE, 1.0) # playback rate
grains.set_param(gf.PARAM_DELAY, gf.PTYPE_VALUE, 0.0) # position in source
# Set up a clock to trigger grains
phasor = gf.Phasor()
phasor.set_frequency(20.0, source.sample_rate) # 20 grains/sec
# Process in blocks
block_size = 512
n_blocks = source.frames // block_size
output = np.zeros((1, n_blocks * block_size), dtype=np.float32)
for i in range(n_blocks):
clock = phasor.process(block_size)
grains.set_auto_overlap(True)
block = grains.process(clock, block_size)
output[0, i * block_size:(i + 1) * block_size] = block[0]
result = AudioBuffer(output, sample_rate=source.sample_rate)
Stereo panning¶
The Panner distributes grains across the stereo field using equal-power quarter-sine interpolation.
# Create a stereo panner
panner = gf.Panner(mode=gf.PAN_BIPOLAR) # full L-R spread
# Process mono grains into stereo
mono_block = grains.process(clock, block_size)
stereo_block = panner.process(mono_block)
# stereo_block shape: [2, block_size]
Pan modes:
PAN_BIPOLAR-- grains pan across full stereo field (-1 to +1)PAN_UNIPOLAR-- grains pan from center to one side (0 to +1)PAN_STEREO-- preserves stereo source positioning
Live recording¶
The Recorder enables live recording into granular buffers with overdub and freeze.
recorder = gf.Recorder(sample_rate=48000.0)
recorder.set_buffer(gf_buf, gf.BUF_MAIN)
# Record a block of live audio
live_input = np.random.randn(1, block_size).astype(np.float32)
recorder.process(live_input, block_size)
Parameter control¶
GrainflowLib parameters are set via string names or enum constants. Each parameter supports multiple types:
# Direct value
grains.set_param(gf.PARAM_RATE, gf.PTYPE_VALUE, 1.0)
# Random offset (each grain gets value +/- random amount)
grains.set_param(gf.PARAM_RATE, gf.PTYPE_RANDOM, 0.1)
# Via string names
grains.set_param_str("rateOffset", gf.PTYPE_VALUE, 0.5)
grains.set_param_str("delayRandom", gf.PTYPE_RANDOM, 0.2)
Key parameters: PARAM_RATE (playback speed), PARAM_DELAY (source position), PARAM_WINDOW (envelope shape), PARAM_GLISSON (pitch glide), PARAM_DIRECTION (forward/reverse).