Evolving Granular Processor — User Guide

Time-evolving granular synthesis: fragments source audio into overlapping grains with continuously changing density, pitch, duration, and statistical properties across the sound's timeline.

Author: Shai Cohen Affiliation: Department of Music, Bar-Ilan University, Israel Version: 0.1 (2025) License: MIT License Repo: https://github.com/ShaiCohen-ops/Praat-plugin_AudioTools
Contents:

What this does

This script implements evolving granular synthesis — a technique that fragments source audio into hundreds or thousands of tiny grains (20-150ms), then reassembles them with continuously changing parameters across time. Unlike static granular synthesis where settings remain constant, this processor creates temporal evolution: grain density can grow from sparse to dense, pitch can sweep upward across the timeline, or statistical properties can shift through distinct regions. Each grain is windowed with a Hann envelope, randomly positioned within temporal constraints, pitch-shifted individually, and mixed into an output buffer. Result: transformed textures ranging from subtle temporal smearing to radical microsound clouds, with the character evolving from beginning to end.

Key Features:

⚠️ PERFORMANCE WARNING

This script uses intensive sample-by-sample processing and may take several minutes to complete.
  • Processing time depends on: Duration × Average density × Grain duration
  • Example: 10-second sound at 15 grains/sec = 150 grains = ~2-3 minutes
  • Each grain processed individually with pitch shifting and windowing
  • Sample-by-sample mixing (no fast convolution)
  • Recommendation: Start with 5-10 grains/sec for testing, then increase
  • Lower density = faster processing: Use sparse densities during experimentation
What is evolving granular synthesis? Traditional granular synthesis: Fragments audio into short grains, processes uniformly across entire sound. Parameters static throughout. Evolving granular synthesis: Parameters change over time, creating temporal narrative. This script implements three evolution paradigms: (1) Density growth: Grain count increases from sparse to dense (texture thickens over time). (2) Pitch sweep: Pitch shift progressively increases (frequency rises across timeline). (3) Statistical shift: Three distinct temporal regions with different grain characteristics (sectional transformation). Each grain: Extracted from source at random position, Windowed with Hann envelope (smooth attack/decay), Pitch-shifted via overlap-add resynthesis, Amplitude-randomized for variation, Mixed into output at specified time position. Thousands of overlapping grains create complex textures. Evolution creates dynamic, non-static transformations.

Technical Implementation: (1) Setup: Create silent output buffer (same duration as source), Calculate total grain count from average density × duration, Convert stereo to mono if needed. (2) Grain generation loop: For each grain: Determine temporal position based on evolution type, Calculate current parameters (density/pitch/amplitude), Randomly select source extraction point, Extract grain from source with rectangular window, Apply pitch shift if needed (overlap-add method, min 0.04s grain), Apply Hann window envelope × amplitude factor, Mix grain into output sample-by-sample. (3) Evolution application: Density growth: Acceptance probability varies with position, Pitch sweep: Progressive semitone shift linearly increases, Statistical shift: Three regions with distinct characteristics. (4) Finalization: Scale peak to 0.95 (prevent clipping from grain accumulation), Display processing summary. Key insight: Sample-by-sample mixing allows precise grain placement but is computationally expensive. Progress updates every 100 grains help monitor long processes. Randomization parameters control variation within evolution framework.

Quick start

  1. In Praat, select exactly one Sound object.
  2. Run script…Evolving Granular.praat.
  3. Set Initial_density and Final_density (grains per second, start low: 5-10).
  4. Set Grain_duration_min and Grain_duration_max (0.05-0.15s typical).
  5. Choose Evolution_type: Density growth, Pitch sweep, or Statistical shift.
  6. For Pitch sweep, set Pitch_shift_semitones (how many semitones at end).
  7. Adjust randomness parameters (position, pitch, amplitude variation).
  8. Click OK — processing begins with progress updates every 100 grains.
  9. Wait for completion (may take minutes), result appears as "originalname_granular_evolved".
Quick tip: START WITH LOW DENSITY for testing — 5-10 grains/sec processes quickly and lets you evaluate the effect before committing to dense processing. Use Density growth (10→25) as first experiment — creates natural sparse-to-dense evolution. Set grain duration range 0.05-0.15s for balanced texture (shorter = more granular, longer = more recognizable source). Position_randomness 0.3 creates natural variation without extreme scrambling. Watch the Info window for progress — updates every 100 grains show percentage completion. Processing time estimation: Duration(s) × Average_density × 0.5 = approximate seconds (e.g., 10s sound × 15 grains/s × 0.5 = 75 seconds processing). Stereo sounds automatically converted to mono (faster processing). Result automatically normalized to 0.95 peak (prevents clipping).
Important: SLOW PROCESSING — this is sample-by-sample granular synthesis, not real-time. Dense settings (25+ grains/sec) on long sounds (>30s) can take 10+ minutes. No cancel button during processing — once started, must complete. High densities + long durations = thousands of grains × pitch shifting × sample mixing. Pitch shifting requires minimum grain duration 0.04s — shorter grains skip pitch processing. Very short grains (<0.03s) create artifacts if pitch-shifted. Position_randomness >0.5 creates extreme temporal scrambling (source unrecognizable). Pitch_randomness >5 creates chaotic pitch variation. Amplitude_randomness >0.5 creates unstable dynamics. Statistical shift mode hardcoded with three regions — no customization (use other modes for flexibility). Output may be quieter than source due to grain spacing — normalization compensates. Original sound preserved (script works on copy).

Granular Theory

Grain Fundamentals

What is a Grain?

Basic definition:

Grain = Short audio fragment (typically 20-150ms) Duration: grain_duration (user-specified range) Source: Extracted from input audio at source_position Window: Hann envelope applied (smooth attack/decay) Pitch: Optional frequency shift via overlap-add Amplitude: Scaled by amplitude factor (with randomness) Position: Placed in output at grain_start time Hann window formula: envelope(t) = [1 - cos(2π × t/duration)] / 2 Where t = time within grain (0 to duration) Result: Smooth fade in/out (avoids clicks) Example grain: Duration: 0.08s (80ms) Source position: 2.35s in original audio Output position: 5.12s in result Pitch shift: +3 semitones Amplitude factor: 0.9 (10% reduction)

Why Window Grains?

Avoiding discontinuities:

🔊 Click Prevention

Without windowing: Abrupt start/end creates discontinuity

Waveform jumps from 0 to sample value instantly

Result: Audible clicks, high-frequency artifacts


With Hann window: Smooth amplitude envelope

Grain fades in smoothly, fades out smoothly

Zero amplitude at boundaries (no discontinuity)

Result: Click-free, musical texture


Hann window characteristics:

Symmetric (same attack and decay)

Peak at center (full amplitude at midpoint)

Zero at edges (t=0 and t=duration)

Smooth curve (no sharp corners)

Grain Density

Density as Grains Per Second

Calculating grain count:

Total grains = Average density × Sound duration Where: Average density = (Initial_density + Final_density) / 2 Sound duration = Length in seconds Example: 10-second sound, density 10→20 grains/sec Average density = (10 + 20) / 2 = 15 grains/sec Total grains = 15 × 10 = 150 grains Example: 30-second sound, density 5→15 grains/sec Average density = (5 + 15) / 2 = 10 grains/sec Total grains = 10 × 30 = 300 grains Density perception: <5 grains/sec: Sparse, individual grains audible 5-15 grains/sec: Moderate, textured but not dense 15-30 grains/sec: Dense, continuous cloud texture >30 grains/sec: Very dense, smooth sustained sound Processing time increases linearly with total grain count

Density Distribution Over Time

Evolution of grain placement:

Density Growth mode:
Initial_density = 10, Final_density = 25

At t=0% (beginning): current_density = 10 grains/sec Sparse texture, clear grain separation

At t=50% (middle): current_density = 17.5 grains/sec Medium density, overlapping grains

At t=100% (end): current_density = 25 grains/sec Dense texture, continuous cloud

Linear interpolation formula: current = initial + (final - initial) × (time/duration)

Result: Smooth transition from sparse to dense

Pitch Shifting

Overlap-Add Method

How grains are pitch-shifted:

Pitch shift in semitones: pitch_factor = 2^(semitones / 12) Examples: +12 semitones (1 octave up): 2^(12/12) = 2.0 (double frequency) +7 semitones (perfect fifth): 2^(7/12) ≈ 1.498 -12 semitones (1 octave down): 2^(-12/12) = 0.5 (half frequency) +3 semitones (minor third): 2^(3/12) ≈ 1.189 Praat implementation: Lengthen (overlap-add): pitch_floor, pitch_ceiling, pitch_factor Parameters: pitch_floor = 80 Hz (minimum detectable pitch) pitch_ceiling = 600 Hz (maximum detectable pitch) pitch_factor = frequency multiplier Constraint: Grain must be ≥ 0.04s for pitch shifting Shorter grains skip pitch processing (too short for analysis) Minimum = ~3 periods at 80 Hz = 0.0375s ≈ 0.04s safety margin Note: Pitch shifting changes grain duration Higher pitch = shorter grain (time compression) Lower pitch = longer grain (time expansion)

Pitch Randomization

Adding variation to pitch shifts:

Final pitch shift = Base pitch + Pitch_randomness × Gaussian(0,1) Where: Base pitch = Evolution-determined shift (e.g., progressive sweep) Pitch_randomness = User parameter (standard deviation in semitones) Gaussian(0,1) = Random value from normal distribution (mean=0, std=1) Example: Pitch sweep mode at t=50% Base pitch = 7 semitones × 0.5 = 3.5 semitones Pitch_randomness = 2.0 semitones Random value = -0.7 (from Gaussian) Final pitch = 3.5 + (2.0 × -0.7) = 2.1 semitones Effect: Low randomness (<1): Coherent pitch evolution, clear sweep Medium randomness (1-3): Natural variation, musical High randomness (>5): Chaotic pitch cloud, incoherent Typical usage: 1.5-2.5 semitones for subtle variation

Temporal Positioning

Grain Placement Strategy

Three position types:

1. GRAIN OUTPUT POSITION (where grain appears in result): Base: normalized_time × duration (evolution-determined) Randomized: base + position_randomness × Gaussian(0,1) 2. GRAIN SOURCE POSITION (where grain extracted from original): Base: grain_start (same as output, or independent) Randomized: base + position_randomness × Gaussian(0, variance) Constrained: Must be within [0, duration - grain_dur] 3. GRAIN CENTER (midpoint for duration calculation): grain_start = grain_center - grain_dur / 2 Ensures grain distributed symmetrically around target time Position_randomness effect: 0.0: All grains at exact calculated positions (deterministic) 0.1-0.3: Subtle jitter, natural variation 0.5-0.7: Moderate scrambling, temporal smearing >1.0: Extreme randomization, source unrecognizable Example: grain_center = 5.0s, randomness = 0.3, Gaussian = 0.8 Actual center = 5.0 + 0.3 × 0.8 = 5.24s If grain_dur = 0.1s: grain_start = 5.24 - 0.05 = 5.19s

Amplitude Processing

Grain Amplitude Control

Amplitude factor calculation:

Amplitude factor = Base amplitude + Amplitude_randomness × Gaussian(0,1) Constraints: Minimum: 0.2-0.3 (prevents complete silence) Maximum: 1.3-1.5 (prevents extreme amplification) Example: Density growth mode Base amplitude = 1.0 (neutral) Amplitude_randomness = 0.2 Random value = -0.5 Factor = 1.0 + (0.2 × -0.5) = 0.9 Clamped to [0.3, 1.5] → 0.9 (within range) Example: Statistical shift mode, Region 3 Base amplitude = 0.5 (reduced for high density) Amplitude_randomness = 0.2 Random value = 1.2 Factor = 0.5 + (0.2 × 1.2) = 0.74 Clamped to [0.2, 1.3] → 0.74 (within range) Applied to windowed grain: grain_sample = source_sample × Hann(t) × amplitude_factor Effect: Low randomness (<0.1): Uniform grain levels Medium randomness (0.2-0.3): Natural dynamic variation High randomness (>0.5): Unstable, choppy dynamics

Complete Grain Processing Pipeline

FOR each grain (1 to total_grains): STEP 1: Determine temporal position Based on evolution type: Density growth: Linear distribution with acceptance probability Pitch sweep: Uniform random distribution Statistical shift: Uniform random, then region-based parameters Calculate grain_center (target time in output) Apply position_randomness STEP 2: Calculate grain parameters grain_dur = min + (max - min) × random(0,1) grain_start = grain_center - grain_dur / 2 Validate: grain_start ≥ 0 AND grain_start + grain_dur ≤ duration If invalid: Skip grain STEP 3: Determine source extraction position source_pos = grain_start + position_randomness × Gaussian(0, variance) Clamp to [0, duration - grain_dur] STEP 4: Calculate pitch shift Base pitch from evolution type Add pitch_randomness × Gaussian(0,1) STEP 5: Calculate amplitude factor Base amplitude from evolution type Add amplitude_randomness × Gaussian(0,1) Clamp to safe range STEP 6: Extract and process grain Extract grain from source at [source_pos, source_pos + grain_dur] IF grain_dur ≥ 0.04s AND |pitch_shift| > 0.1 semitones: Apply overlap-add pitch shifting Update grain_dur (changed by pitch shift) ENDIF Apply Hann window: sample × [1 - cos(2πt/dur)] / 2 × amp_factor STEP 7: Mix into output FOR each sample in grain: grain_time = sample_index / sampling_rate output_time = grain_start + grain_time output_sample = round(output_time × sampling_rate) + 1 IF output_sample valid: current_value = output[output_sample] grain_value = grain[sample_index] output[output_sample] = current_value + grain_value ENDIF ENDFOR STEP 8: Cleanup Delete temporary grain object IF grain mod 100 = 0: Display progress message ENDIF ENDFOR FINALIZATION: Scale peak to 0.95 (prevent clipping) Rename to "originalname_granular_evolved" Display summary

Evolution Types

Mode 1: Density Growth

📈 Progressive Grain Density Increase

Concept: Grain count increases from sparse to dense over time

Method: Acceptance probability scales with normalized time

Parameters: Initial_density, Final_density

Effect: Texture thickens gradually from beginning to end

Technical implementation:

FOR each potential grain: normalized_time = (grain_number - 1) / total_grains current_density = initial + (final - initial) × normalized_time Acceptance probability: time_probability = current_density / average_density IF random(0,1) < time_probability: Generate and place grain ELSE: Skip grain ENDIF ENDFOR Result: More grains placed toward end (high density region) Fewer grains at beginning (low density region) Example: Initial=10, Final=25, 10s duration t=0s (0%): current_density=10, probability=10/17.5=0.57 t=5s (50%): current_density=17.5, probability=17.5/17.5=1.0 t=10s (100%): current_density=25, probability=25/17.5=1.43 Beginning: 57% of grains placed (sparse) End: All grains placed + extras (dense)

Musical characteristics:

Best practices for Density Growth:
  • Use moderate ratios (10→25, not 5→100) for smooth evolution
  • Longer durations (>10s) make evolution more gradual and perceptible
  • Lower position_randomness (0.2-0.3) preserves temporal coherence
  • Works well with percussive sources (grain attacks emphasized)

Mode 2: Pitch Sweep

🎶 Progressive Frequency Rise

Concept: Pitch shift increases linearly from 0 to target semitones

Method: Each grain pitch-shifted by time-proportional amount

Parameters: Pitch_shift_semitones (final shift at end)

Effect: Smooth pitch glide upward across duration, amplitude decreases inversely

Technical implementation:

FOR each grain: grain_center = random(0, duration) (uniform distribution) normalized_time = grain_center / duration Progressive pitch shift: current_pitch = Pitch_shift_semitones × normalized_time final_pitch = current_pitch + pitch_randomness × Gaussian(0,1) Inverse amplitude evolution: amp_factor = 1.2 - normalized_time × 0.4 + amp_random Result: Starts at 1.2 (20% boost), ends at 0.8 (20% reduction) Compensates for perceived loudness increase with pitch Apply pitch shift to grain (if dur ≥ 0.04s) ENDFOR Result: Grains at beginning unpitched (0 semitones) Grains at end maximally pitched (target semitones) Smooth spectral glide effect Example: Pitch_shift_semitones = 7 (perfect fifth), 10s duration t=0s: pitch=0, amp=1.2 (normal pitch, slightly louder) t=5s: pitch=3.5, amp=1.0 (minor third up, neutral) t=10s: pitch=7, amp=0.8 (perfect fifth up, quieter)

Musical characteristics:

Best practices for Pitch Sweep:
  • Use musical intervals: 7 sem