Dynamic Stochastic Grain Field — User Guide

An inhomogeneous-Poisson granular synthesizer whose event density evolves over time while grain frequencies follow an octave-domain trajectory. Each event becomes a Hann-windowed sine grain with random phase, optional pitch jitter, overlap-aware amplitude compensation, and optional grain-level stereo placement.

Author: Shai Cohen Affiliation: Department of Music, Bar-Ilan University, Israel Version: 0.5 (2026) License: MIT License Repo: Praat AudioTools
Contents:

What this does

Dynamic Stochastic Grain Field generates a cloud of short sine grains from scratch. The important feature is that the probability of a new grain appearing is allowed to change continuously over the duration. A sparse beginning can therefore become statistically dense, or a dense texture can thin out, without placing events on a regular grid.

Think of the process in four layers:
  1. A time-varying event rate says how many grains per second are expected locally.
  2. A Poisson process turns that rate into an irregular sequence of actual onset times.
  3. Each onset receives a duration, frequency, phase, amplitude, and pan position.
  4. The stored grains are rendered exactly into mono or stereo audio.

The density curve is a statistical target, not a deterministic command that guarantees an exact number of grains in each time interval. Two runs with the same settings can therefore have different local clusters and gaps. A positive Random seed makes that realization reproducible.

Why this is not Xenakis's GENDYN

The historical term dynamic stochastic synthesis is strongly associated with Iannis Xenakis. In Xenakis's stochastic-synthesis lineage, including GENDYN, the waveform itself is represented by breakpoints whose positions evolve stochastically in time and amplitude. This script does not implement that algorithm.

Here the stochastic process operates at the level of granular events: it determines when grains occur and randomizes grain-level properties such as duration, pitch offset, phase, and, in some spatial modes, pan. The individual waveform inside every grain is a sine wave under a Hann envelope. The name used by the current script — Dynamic Stochastic Grain Field — therefore describes the implementation more accurately.

Practical distinction: GENDYN continuously reshapes a waveform through stochastic breakpoint motion. This tool generates a stochastic population of discrete grains whose statistical density and pitch center evolve over time.

Quick start

  1. Run Dynamic_Stochastic_Synthesis.praat; no input Sound is required.
  2. Choose a preset or leave Custom.
  3. Set Duration, Base frequency, Initial density, Final density, and Frequency evolution (octaves).
  4. Choose Mono or one of the three grain-level stereo modes.
  5. Enable Edit grain details when you want to change duration range, pitch jitter, random seed, or edge fade.
  6. Click OK. The final Sound remains selected and is named dynamic_stochastic_grains_<preset>.
Useful first comparison: render Sparse-to-Dense Bloom twice with seed 0. The overall statistical arc should remain similar while the individual clusters differ. Then use the same positive seed in the grain-details page to reproduce exactly the same grain schedule and random grain properties.

Event density: target versus realization

The two density controls define the instantaneous Poisson intensity lambda(t) in grains per second. The script linearly interpolates between them:

lambda(t) = d0 + (d1 - d0) * t / T d0 = Initial density d1 = Final density T = total duration

If the density rises from 20 to 200 grains/s, the process becomes increasingly likely to generate short inter-onset intervals near the end. It does not first generate a fixed number of uniformly distributed events and then relabel them with different densities.

How the onset times are generated

The cumulative intensity is

Lambda(t) = d0*t + (d1-d0)*t^2 / (2*T)

The script advances through cumulative-intensity space with exponential random increments and then analytically inverts Lambda(t) to obtain the next event time. The resulting onset list is already chronological.

Expected and actual grain counts

For a linear density trajectory, the expected number of events is

expected grains = T * (d0 + d1) / 2

That number is an expectation only. The actual Poisson realization can contain more or fewer grains. The Info window and visualization report both values. The script refuses settings whose expected count exceeds 8000 and also aborts if an unusually large realization reaches 10,000 grains.

Pitch trajectory and grain model

Frequency evolution is measured in octaves

The center frequency follows an exponential trajectory in frequency but a straight line in octave space:

f_center(t) = Base_frequency * 2^(Evolution_octaves * t/T)

+1 therefore ends one octave above the starting center, +2 ends two octaves above it, -1 ends one octave below it, and 0 holds the center frequency constant.

Frequency jitter

Each grain receives an independent uniform offset in octave space:

f_grain = f_center * 2^(uniform(-jitter, +jitter))

This makes the jitter perceptually symmetric as a pitch ratio rather than as a fixed number of hertz. Frequencies below 20 Hz are raised to 20 Hz and counted in QC. The upper synthesis guard is 0.45 * sample rate; if the requested base frequency could place the trajectory above that guard, the base is reduced automatically.

One grain

Each event is a sine oscillator with a random starting phase and a Hann amplitude envelope:

grain(age) = A * sin(2*pi*f*age + phase) * 0.5 * (1 - cos(2*pi*age/duration))

Duration is chosen uniformly between the current minimum and maximum grain durations. A grain beginning close to the end of the requested sound is shortened so that it ends exactly at the output boundary.

Density is not secretly a loudness envelope

More simultaneous grains would normally increase RMS level. To reduce that confound, per-grain amplitude is divided by the square root of the expected local overlap:

expected overlap = max(1, lambda(t) * mean grain duration) grain amplitude ~ 1 / sqrt(expected overlap)

A small random amplitude factor remains. The compensation is approximate — it uses the expected local overlap and mean duration, not the exact number of simultaneously active grains — but it makes density primarily a texture/occupancy control rather than simply a volume control.

Presets

Presets set the central stochastic/granular parameters and the spatial mode. They do not replace the chosen sample rate, normalization, visualization, playback, random seed, or edge fade. The duration is overridden only by the presets explicitly shown below as 8 or 10 seconds.

PresetDurationBase HzDensity /sEvolutionGrain msJitterSpatial
Sparse-to-Dense Bloomform value8015 -> 60+0.5 oct30-100+/-0.18 octMono
Steep Density Buildform value10020 -> 200+1.2 oct10-50+/-0.30 octStereo Evolution
Slow Two-Octave Drift10 s6010 -> 80+2.0 oct50-150+/-0.20 octRotating Cloud
Short-Grain Rising Cascadeform value18040 -> 180+1.5 oct10-40+/-0.35 octWide Field
Medium Density Growth8 s9025 -> 100+0.8 oct25-90+/-0.22 octStereo Evolution
Dense Pitch Cloudform value11035 -> 120+1.0 oct20-60+/-0.10 octMono
High-Density Short Grainsform value20050 -> 250+1.8 oct8-30+/-0.40 octWide Field
Sparse Long-Grain Cloud10 s708 -> 40+0.3 oct40-120+/-0.15 octRotating Cloud

Parameters

Main form

ParameterDefaultMeaning
Duration_s6.0Total synthesis duration; valid up to 120 s.
Sample_rate_Hz44100Direct rendering rate, 8000-192000 Hz.
Base_frequency_Hz120Center frequency at time 0, before pitch jitter.
Initial_density30Poisson event intensity at the beginning, in expected grains/s.
Final_density150Poisson event intensity at the end, in expected grains/s.
Frequency_evolution_octaves+1.0Total center-frequency change from start to end; accepts -8 to +8 octaves.
Spatial_modeMonoMono, Stereo Evolution, Rotating Cloud, or Wide Field.
Edit_grain_detailsoffOpens the optional compact second page after preset values have been applied.
Normalize_outputyesOptional final target peak normalization to 0.90.
Draw_visualizationyesDraw target/realized statistics, grain field, measured spectrogram, waveform, and QC.
Play_resultyesPlay the final Sound.

Grain-details page

ParameterDefaultMeaning
Min grain duration20 msLower endpoint of the uniform duration range.
Max grain duration80 msUpper endpoint; limited to 2000 ms.
Frequency jitter0.25 octUniform +/- octave offset around the evolving center; 0-2 octaves.
Random seed00 uses the current/unpredictable RNG state; a positive integer produces a reproducible stochastic realization. After a fixed-seed schedule is generated, the script restores Praat's unpredictable RNG initialization.
Edge fade0.02 sShort final protection fade at both ends, capped at 20% of total duration.

Spatial modes

Stereo is created per grain. A grain receives one pan value from its onset time and keeps that pan for its whole duration. Left and right gains use equal-power panning:

left = cos(pi/2 * pan) right = sin(pi/2 * pan)
ModePan behavior
MonoOne-channel render; no stereo gain pair is applied.
Stereo EvolutionPan follows normalized onset time from left toward right, with independent +/-0.12 stochastic deviation, then is constrained to 0.02-0.98.
Rotating CloudGrain positions sample a sinusoidal pan trajectory whose rotation rate rises linearly from 0.08 to 0.28 cycles/s over the sound. The accumulated rotation phase is integrated analytically.
Wide FieldEach grain is randomly assigned to a near-left region (0.03-0.23) or near-right region (0.77-0.97).

These modes do not create width by complementary EQ or by filtering a finished mono signal. The left/right balance is part of each grain's render.

Exact chunked rendering

The output is rendered in chronological chunks of at most 1 second. Chunking is only a way to keep Praat formula strings manageable; it is not supposed to alter the stochastic timeline.

Crossing grains are preserved

If a grain begins in one chunk and ends in the next, both chunks evaluate it using the same original onset, duration, frequency, random phase, and envelope age. The second chunk therefore continues the already-running Hann grain instead of restarting it. After rendering, the chunks are concatenated end-to-end.

No silent grain dropping: the old 40-grains-per-1.5-second limit is gone. The current script allows up to 380 active grain terms in any one-second chunk. If that safety limit would be exceeded, synthesis stops with an explicit message instead of quietly omitting events.

The expected count is capped before scheduling, but long grains and high densities can still create many simultaneously relevant terms in a chunk. Reducing density or grain duration lowers that workload.

Output and level behavior

PropertyBehavior
SourceGenerated entirely from sine grains; no selected Sound is required.
Sample rateRendered directly at Sample_rate_Hz; there is no later resampling stage.
ChannelsMono in Mono mode; two channels in all three spatial modes.
Edge protectionOne combined linear fade-in/fade-out using the requested edge-fade value, capped at 20% of duration.
NormalizationIf enabled, non-zero output is scaled to a target peak of 0.90. Pre-normalization peak and RMS are measured first and reported.
Object namedynamic_stochastic_grains_<preset>.

Visualization and QC

The current figure separates the stochastic control process from the measured audio:

PanelWhat it shows
A — Event DensityThe linear target intensity as a line and the actual Poisson realization measured in time bins. Local bars are expected to fluctuate around the target rather than match it exactly.
B — Actual Grain FieldEach sampled line segment runs from a realized grain onset to its end at the realized grain frequency. Frequency is displayed logarithmically. In stereo modes, color indicates broad left/center/right pan regions.
C — Model -> MeasurementA measured spectrogram of the higher-RMS representative output channel, with a sampled subset of the actual grain frequencies drawn as guides.
D — Measured OutputThe measured waveform of that representative channel.

The QC strip reports expected and actual event counts, realized mean density, seed status, realized frequency range, mean grain duration, spatial mode, and pre/final level measurements. The Info window additionally reports the maximum number of terms used in any one-second chunk and the number of low-frequency corrections.