Granular Particle Field — User Guide

A stereo granular renderer in which each grain is treated as a particle with its own output time, source position, pitch, envelope, amplitude and pan position.

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

What this does

Granular Particle Field converts the selected Sound to a mono synthesis source, extracts a fixed number of short grains, and places them into a new stereo output buffer. Each grain is independently assigned a source position, optional varispeed pitch shift, envelope, optional LFO-derived amplitude, and pan position.

The tool separates three different coordinates that are easy to confuse:

Channel behavior: mono and multichannel inputs are both rendered from a mono fold of the source. The result is always a newly constructed stereo Sound; the original stereo or multichannel image is not preserved.

What is a particle here?

In this script, particle is the event-level representation of one granular sound fragment. It is not a physical particle simulation. One particle stores:

output time
source start position
grain duration
pitch shift
envelope
LFO amplitude
pan position

The audio fragment itself is the grain; “particle” emphasizes that the grain also has a position and other attributes inside a time–pan field.

Quick start

  1. Select exactly one Sound object.
  2. Run Granular_Particle_Field.praat.
  3. Choose Custom or one of the five named presets.
  4. For Custom, set the grain count and duration, then choose the envelope, pitch, panning, time distribution and source traversal.
  5. Set Output_duration_s to 0 to use the input duration, or enter another duration.
  6. Run the script. The result is named <source>_particles_<preset>.

Presets

PresetGrainsBase durationEnvelopeTimeSourcePanPitch / LFO
Dense Cloud30030 msGaussianRandomRandomRandomPitch off; LFO off
Sparse Field30150 msHannLinearSequentialPosition-derivedPitch off; LFO off
Rhythmic Pulse8040 msRectangularLinearSequentialFixed centrePitch off; 4 Hz LFO
Shimmer15060 msGaussianExponentialRandomRandom0 ± 7 st; 0.25 Hz LFO
Long Resonance15800 msHannExponentialRandomPosition-derived0 ± 3 st; 0.15 Hz LFO

Named presets overwrite their synthesis settings, but Output_duration_s, Draw_visualization and Play_result remain user-controlled.

Output-time distribution

Pitch is drawn first because downward varispeed can lengthen a grain. The script therefore reserves one common legal start span based on the longest possible pitch outcome:

baseGrainDur = min(requestedGrainDur, inputDuration)

minSemitones = basePitch - pitchVariation
minPitchFactor = 2^(minSemitones / 12)

maxPossibleGrainDur = baseGrainDur / minPitchFactor
outputGridSpan = max(0, outputDuration - maxPossibleGrainDur)

Linear

Particles are evenly spaced across 0 ... outputGridSpan. With one particle, it is placed at the midpoint of that span.

Exponential

Particle indices are mapped through:

u = (i - 1) / (N - 1)
time = span × (1 - exp(-3u)) / (1 - exp(-3))

This curve compresses successive event spacing toward the end, so the field becomes progressively denser there.

Random

Each particle receives an independent uniform random start inside the safe span.

Finite output buffer: when the requested output is shorter than a rendered grain, the grain begins at 0 and only the portion that lies inside the output buffer is written.

Source traversal

The requested grain duration is first limited to the source duration:

baseGrainDur = min(Grain_duration_s, inputDuration)
maxSourceStart = inputDuration - baseGrainDur

Sequential

Source starts move linearly from the first legal start position to the last. With one grain, the source position is the midpoint of the legal range.

Random

Each grain start is chosen independently and uniformly from the same legal range.

The script keeps these positions as zero-based offsets and adds the Sound's actual start time only when extracting audio, so non-zero Praat time domains are handled correctly.

Pitch shift & grain duration

If pitch shifting is enabled, each particle draws:

semitones = Pitch_shift_semitones
            + uniform(-Pitch_variation_semitones,
                      +Pitch_variation_semitones)

pitchFactor = 2^(semitones / 12)

The grain is then processed with Override sampling frequency. This is varispeed, not pitch-preserving transposition:

new sampling frequency = sourceSampleRate × pitchFactor
rendered duration       = baseGrainDur / pitchFactor

The script does not resample the grain back to the original sampling frequency before mixing; the time-domain lookup performs the varispeed read. Strong upward shifts can therefore have the usual aliasing risk of this simple method.

Envelope & LFO

Hann

gain(x) = 0.5 × [1 - cos(2πx / grainDuration)]

Gaussian

The Gaussian is centred at the grain midpoint with σ = duration/6 and is explicitly edge-normalized so that the theoretical boundaries reach zero.

Rectangular

No envelope is applied. The original extracted samples are used directly, so discontinuities at grain edges are possible.

LFO

The LFO is evaluated once per particle at that particle's output start time:

grainAmplitude =
    0.5 × [1 + sin(2π × LFO_frequency × outputTime)]

That scalar is then applied to the entire grain. The script does not run a continuous sine modulation through the samples inside the grain.

Stereo panning

Pan uses the interval 0–1: 0 = left, 0.5 = centre, 1 = right.

ModePan law
Position-derivedpan = sourceOffset / maxSourceStart. The complete legal source-start range maps exactly from L to R. If only one source start is possible, pan = 0.5.
RandomIndependent uniform pan in 0–1 for every particle.
FixedUse Fixed_pan, validated to 0–1.

Channel gains use constant-power panning:

gainL = sqrt(1 - pan)
gainR = sqrt(pan)

The two output channels receive the same mono grain at these gains; there is no separate source material for L and R.

Parameters & limits

ParameterDefaultBehavior
PresetCustomCustom plus five named particle-field settings.
Number_of_grains1001–5000.
Grain_duration_s0.050Must be > 0; internally limited to the input duration.
Envelope_shapeHannHann, Gaussian or Rectangular.
Apply_pitch_shiftOffEnable per-grain varispeed pitch changes.
Pitch_shift_semitones0Centre of the pitch range.
Pitch_variation_semitones0Uniform ± variation; must be ≥ 0.
Panning_modePosition-derivedPosition-derived, Random or Fixed.
Fixed_pan0.5Validated to 0–1 when Fixed is active.
Apply_LFOOffApply one sine-derived amplitude value per grain.
LFO_frequency0.5 HzMust be > 0 when LFO is active.
Time_distributionLinearLinear, Exponential or Random output starts.
Grain_sourceSequentialSequential or Random source starts.
Output_duration_s00 = input duration; otherwise the requested output duration.
Draw_visualizationOnDraw Source → Output → Particle field → Summary.
Play_resultOnPlay the created Sound.

Both input and output must be at least two samples long at the source sampling rate.

Visualization

The Picture window contains four user-facing regions:

Particle blue has one semantic meaning. With LFO disabled, all particles use the same marker style. With LFO enabled, marker size and blue intensity encode the stored per-grain LFO amplitude.

Output behavior

Mixing

Each shifted grain is added directly into the two mono output buffers only over its own write interval. Overlapping particles are summed.

Normalization

After L/R are combined to stereo, the script measures the Sinc70 absolute peak. Every non-silent result is peak-normalized to 0.95:

if peak > 0:
    Scale peak to 0.95

This is true peak normalization and can increase a quiet particle field.

Naming

<source name>_particles_<preset name>

The original Sound remains unchanged. Temporary mono source and mix buffers are removed after rendering. Because no random-seed control is exposed, presets and Custom settings that use random source positions, random pan or pitch variation can produce a different realization on each run.