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:
- Three Evolution Modes — Density growth, Pitch sweep, Statistical shift
- Grain Parameter Control — Duration, density, pitch, amplitude
- Temporal Randomization — Position, pitch, amplitude jitter
- Sample-Level Precision — Grain mixing at individual sample resolution
- Progress Monitoring — Updates every 100 grains during processing
- Automatic Normalization — Peak scaling to 0.95 prevents clipping
⚠️ 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.
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:
- Natural evolution: Mimics organic growth, gradual buildup
- Temporal narrative: Clear beginning (sparse) → end (dense) trajectory
- Textural transformation: Individual grains audible → continuous cloud
- Dynamic arc: Quiet, open space → loud, filled texture
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:
- Spectral trajectory: Ascending frequency sweep, Doppler-like effect
- Harmonic interest: Use musical intervals (7=fifth, 12=octave, 5=fourth)
- Timbral transformation: Low/dark → high/bright progression
- Dynamic compensation: Amplitude reduction at high pitch prevents harshness
Best practices for Pitch Sweep:
- Use musical intervals: 7 sem