Ligeti Micropolyphonic Choir Machine — User Guide

Algorithmic texture generation inspired by György Ligeti's micropolyphony: creates complex choral textures through formal arcs, Gaussian distributions, and stereo asymmetry.

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 Ligeti-inspired micropolyphonic texture generation — creating complex choral clouds from a single sound source through algorithmic voice proliferation. The machine generates 2–60 independent voices, each with carefully controlled variations in timing, pitch, duration, panning, and amplitude. Inspired by György Ligeti's compositions like "Lux Aeterna" and "Atmosphères," this tool transforms monophonic or stereo material into dense, evolving textures through formal structural arcs, Gaussian distribution controls, and intentional stereo asymmetry.

Key Features:

What is Micropolyphony? Developed by György Ligeti in the 1960s, micropolyphony is a compositional technique featuring dense, complex textures created from many voices (typically 20–60), each with subtle individual variations in timing, pitch, and dynamics. Rather than traditional counterpoint with clear melodic lines, micropolyphony creates "sound masses" or "clouds" where individual voices blur into a collective texture. Key characteristics: (1) High voice count: Dozens of independent parts. (2) Micro-variations: Small pitch deviations (5–50 cents), timing offsets. (3) Formal evolution: Systematic changes across the texture over time. (4) Spatial distribution: Careful placement across stereo field. (5) Emergent complexity: Simple rules create rich perceptual results. This script operationalizes these principles for algorithmic sound transformation.

Technical Implementation: (1) Source selection: Single Sound object (mono or stereo) as source material. (2) Behavior selection: Choose preset or custom parameters defining structural logic. (3) Voice generation loop: For each voice (1 to n_voices): Calculate parameters based on structural logic (pitch, pan, timing), Apply pitch shift via resampling, Apply time stretch with duration variation, Apply fade envelope, Apply gain scaling, Apply panning (mono→stereo conversion), Apply time offset with distribution logic, Mix into output buffer. (4) Distribution logic: Flat (uniform) vs Gaussian (bell-curve) clustering. (5) Structural arcs: Parameters evolve systematically across voice indices. (6) Output normalization: Automatic gain management prevents clipping. The process creates a cumulative texture where individual voices blend into a cohesive mass.

Quick start

  1. In Praat, select exactly one Sound object (mono or stereo).
  2. Run script…ligeti_micropolyphonic_choir.praat.
  3. Choose Preset: Start with "Breathing Field" for classic Ligeti texture.
  4. Adjust Number_of_voices (40-60 for dense clouds).
  5. Set Time_offset_range_s (0.5-1.2s for temporal spread).
  6. Configure Max_pitch_cents (8-35 cents for microtonal variation).
  7. Enable Stereo_spread for spatial distribution.
  8. Set Attack_fade_ms (15-50ms for smooth voice entries).
  9. Click OK — texture generated, result named "Ligeti_[structure]".
Quick tip: Start with "Breathing Field" preset for classic Ligeti "Lux Aeterna" texture — uniform distribution, moderate pitch variation. Use "Static Spectral Fog" for dense, static clusters with Gaussian distribution. Try "Fracturing Mass" for dramatic evolution from purity to chaos. "Stereo Torsion" creates spatial tension with left-right asymmetry. Lower voice counts (20-30) create transparent textures; higher counts (50-60) create dense masses. Adjust Max_pitch_cents carefully: 5-15 cents = subtle chorusing, 20-35 cents = noticeable detuning, >40 cents = overt dissonance. Enable Normalize_output to prevent clipping. Processing time increases with voice count (60 voices ≈ 1-2 minutes).
Important: SOURCE MATERIAL MATTERS — Works best with sustained, harmonic-rich sources (choir vowels, strings, organ). Transient material may create rhythmic artifacts. Voice count trade-off: More voices = denser texture but longer processing. Pitch extremes: Max_pitch_cents > 50 may create beating/artifacts. Time offset range: Values > source duration may create noticeable gaps. Gaussian distribution clusters voices temporally (not uniform spread). Stereo asymmetry creates noticeable left-right differences — intentional compositional effect. Gain staging: Individual voices attenuated by √n_voices to maintain consistent loudness. Original dynamics flattened in output texture — consider post-processing.

Micropolyphonic Theory

Ligeti's Micropolyphony Principles

🎵 Core Aesthetic Principles

Texture over Melody: Individual voices subordinate to collective mass

Micro-Variation: Small differences create complex interference patterns

Systematic Evolution: Parameters change according to formal plans

Spatial Thinking: Stereo placement as compositional parameter

Emergent Complexity: Simple rules → rich perceptual results

Formal Arcs (Structural Evolution)

Voice-index-dependent parameter evolution:

# Fracturing Mass Arc Example: # Voice 1: pure (0 cents deviation) # Voice 60: maximum detuning (±35 cents) FOR voice FROM 1 TO n_voices: intensity = voice / n_voices # 0.017 → 1.0 current_range = pitch_max × intensity current_pitch = randomUniform(-current_range, current_range) EXAMPLE (n_voices=60, pitch_max=35): Voice 1: intensity=0.017, range=0.6 cents → almost pure Voice 30: intensity=0.5, range=17.5 cents → moderate detune Voice 60: intensity=1.0, range=35 cents → maximum detune

Gaussian vs Uniform Distributions

Distribution shapes for natural clustering:

# UNIFORM DISTRIBUTION (flat) offset = randomUniform(-time_range/2, time_range/2) # All positions equally likely # Result: Evenly spread voices # GAUSSIAN-LIKE DISTRIBUTION (bell curve) r1 = randomUniform(-0.5, 0.5) r2 = randomUniform(-0.5, 0.5) offset = (r1 + r2) × time_range # Central limit theorem approximation # Result: Voices cluster near center, fewer at edges # Creates "dense core with wispy edges" # Mathematical basis: # Sum of uniform variables → approximate normal # Mean=0, variance depends on time_range

Stereo Asymmetry Principles

Spatial Tension Design

Differential processing across channels:

# Stereo Torsion Logic: # Left channel (pan = -1): pure, stable # Right channel (pan = +1): detuned, chaotic # Continuous gradient between extremes pan_pos = randomUniform(-1, 1) # -1=left, +1=right tension = (pan_pos + 1) / 2 # 0=left, 1=right current_range = pitch_max × tension current_pitch = randomUniform(-current_range, current_range) # Panning gain calculation: l_gain = √((1 - pan_pos)/2) # Left gain r_gain = √((1 + pan_pos)/2) # Right gain EXAMPLE (pan_pos = -0.5): tension = 0.25 range = 25 × 0.25 = 6.25 cents l_gain = √(0.75) = 0.866 r_gain = √(0.25) = 0.5 # Mostly left, slightly detuned

Bimodal Stratification

High/Low Layer Separation

Creating distinct textural strata:

# Bimodal Web Logic: # Even voices → pitched UP (5 to max cents) # Odd voices → pitched DOWN (-max to -5 cents) # Creates two distinct layers that intertwine IF voice mod 2 = 0: # Even voice current_pitch = randomUniform(5, pitch_max) ELSE: # Odd voice current_pitch = randomUniform(-pitch_max, -5) # Additional differentiation possible: # Even voices: shorter duration, brighter # Odd voices: longer duration, darker # Creates woven texture with contrasting strands

Complete Processing Pipeline

SETUP: Select Sound object (source) Choose Preset or Custom parameters Validate: n_voices ≥ 2 INITIALIZATION: source_dur = Get total duration source_sr = Get sampling frequency output_dur = source_dur + time_range + 0.5 Create output buffer: "Ligeti_[structure]" VOICE GENERATION LOOP (1 to n_voices): # 1. PARAMETER CALCULATION Based on structure$: - uniform: random pitch - arc_fracture: pitch increases with voice index - asymmetry: pitch depends on pan position - bimodal: even/odd different pitch directions # 2. PITCH SHIFT pitch_ratio = 2^(current_pitch_cents / 1200) Override sampling frequency: source_sr × pitch_ratio Resample back to source_sr # 3. TIME STRETCH dur_factor = 1 ± random variation total_stretch = dur_factor × pitch_ratio Lengthen (overlap-add) if needed # 4. ENVELOPE Apply fade-in/out (Attack_fade_ms) # 5. GAIN Scale by: gain / √n_voices # 6. PANNING Convert mono→stereo with pan position l_gain = √((1-pan_pos)/2) r_gain = √((1+pan_pos)/2) # 7. TIME OFFSET Calculate offset based on distribution shape Trim or pad accordingly # 8. MIX Add to output buffer FINALIZATION: Normalize peak to 0.99 (if enabled) Play result Display generation summary

Behavioral Presets

Preset 1: Static Spectral Fog (Gaussian Mass)

🌫️ Dense, Motionless Texture

Structure: Uniform

Distribution: Gaussian (bell curve)

Character: Dense core with wispy edges, minimal motion

Inspiration: Ligeti's "static clouds" in Apparitions

Parameters: n_voices=60, pitch_max=8, time_range=0.5, dur_var=0.02

Use: Background pads, ambient beds, spectral masses

Preset 2: Fracturing Mass (Gradual Detuning Arc)

⚡ Evolution from Purity to Chaos

Structure: Arc_fracture (formal evolution)

Distribution: Flat (uniform)

Character: Systematic pitch expansion across voices

Inspiration: Textural development in Atmosphères

Parameters: n_voices=60, pitch_max=35, time_range=1.0, dur_var=0.08

Use: Dramatic transformations, structural development

Preset 3: Stereo Torsion (Left=Pure, Right=Detuned)

🎧 Spatial Tension Field

Structure: Asymmetry (pan-dependent pitch)

Distribution: Flat (uniform)

Character: Left channel stable, right channel chaotic

Inspiration: Spatialized textures in Requiem

Parameters: n_voices=50, pitch_max=25, time_range=0.6, dur_var=0.05

Use: Immersive composition, spatial effects, headphone music

Preset 4: Bimodal Web (High/Low Split)

🕸️ Interwoven Textural Layers

Structure: Bimodal (even/odd differentiation)

Distribution: Flat (uniform)

Character: Two distinct strata woven together

Inspiration: Contrapuntal thinking in micropolyphony

Parameters: n_voices=40, pitch_max=20, time_range=0.8, dur_var=0.1

Use: Complex textures, foreground/background interplay

Preset 5: Breathing Field (Uniform Cloud)

🌌 Classic Ligeti Texture

Structure: Uniform (traditional micropolyphony)

Distribution: Flat (uniform)

Character: Evenly distributed, organic motion

Inspiration: Lux Aeterna, choral micropolyphony

Parameters: n_voices=40, pitch_max=12, time_range=1.2, dur_var=0.08

Use: Choral transformations, ethereal textures, classic micropolyphony

Preset 6: Custom

🔧 Full Parameter Control

Structure: User-defined combination

Distribution: User choice (flat or Gaussian)

Character: Determined by custom parameters

Parameters: Full manual control

Use: Experimental designs, specific textural goals

Parameters

Preset Selection

ParameterTypeDefaultDescription
PresetoptionBreathing FieldBehavioral preset (1-6)

Custom Parameters (when Preset = Custom)

ParameterTypeDefaultRangeDescription
Number_of_voicespositive602-100Total independent voices in texture
Time_offset_range_spositive0.80.1-5.0Maximum time offset between voices
Duration_var_ratiopositive0.050.0-0.3Duration variation (± this ratio)
Max_pitch_centspositive15.00.1-50.0Maximum pitch deviation in cents
Stereo_spreadboolean1 (yes)yes/noEnable stereo panning distribution
Attack_fade_mspositive300-100Fade-in/out time per voice
Voice_gainpositive1.00.1-2.0Individual voice amplitude
Normalize_outputboolean1 (yes)yes/noNormalize final output to 0.99 peak

Internal Behavior Parameters (Set by Presets)

ParameterTypeDescription
structure$stringStructural logic (uniform, arc_fracture, asymmetry, bimodal)
dist_shape$stringDistribution shape (flat, gaussian)
n_voicesintegerActual voice count after preset logic
time_rangerealActual time offset range
pitch_maxrealActual maximum pitch deviation
dur_varrealActual duration variation
gainrealActual voice gain
faderealActual fade time in ms

Applications

Choral Transformation

Use case: Transforming solo vocal recordings into choral ensembles

Technique: "Breathing Field" preset with 40-60 voices

Example: Solo soprano → Lux Aeterna-style choral cloud

Orchestral Texture Generation

Use case: Creating string pads, brass clouds from single samples

Technique: "Static Spectral Fog" with Gaussian distribution

Example: Single violin sustains → dense string cluster

Spatial Composition

Use case: Immersive, spatially distributed textures

Technique: "Stereo Torsion" with asymmetry enabled

Workflow:

Algorithmic Composition

Use case: Generative music with evolving textures

Technique: "Fracturing Mass" for formal development

Advantages:

Example: Psychoacoustic studies of texture perception

Sound Design for Media

Use case: Creating atmospheric beds, tension textures

Technique: "Bimodal Web" for complex, layered sounds

Example: Sci-fi ambiance, dream sequences, transitional textures

Application: Film/TV scoring, game audio, installation sound

Teaching Micropolyphonic Principles

Use case: Demonstrating Ligeti's techniques aurally

Technique: Students adjust parameters, hear textural changes

Learning outcomes:

Practical Workflow Examples

🎬 Film Score Textures (Suspense/Drama)

Goal: Evolving tension cloud for dramatic scenes

Settings:

  • Preset: Fracturing Mass
  • Source: Low string sustain
  • Number_of_voices: 50
  • Max_pitch_cents: 25
  • Time_offset_range_s: 1.2
  • Stereo_spread: Yes

Result: Slowly fracturing string cluster with systematic detuning

🎵 Choral Ambiance (New Music)

Goal: Lux Aeterna-style vocal cloud

Settings:

  • Preset: Breathing Field
  • Source: Soprano vowel ("ah")
  • Number_of_voices: 40
  • Max_pitch_cents: 12
  • Time_offset_range_s: 0.8
  • Attack_fade_ms: 40

Result: Traditional micropolyphonic choral texture

🔬 Psychoacoustic Research (Texture Perception)

Goal: Controlled stimuli for perception studies

Settings:

  • Preset: Custom
  • Structure: Uniform
  • Distribution: Gaussian
  • Number_of_voices: 30 (controlled variable)
  • Max_pitch_cents: 15 (fixed)
  • Documentation: Full parameter record

Result: Reproducible textural stimuli for experimental research

Advanced Techniques

Source material optimization:
  • Sustained tones: Best results with minimal transients
  • Harmonic richness: Complex spectra create richer interference
  • Dynamic consistency: Avoid large amplitude variations
  • Duration: 2-10 seconds ideal for voice proliferation
  • Pre-processing: Consider noise reduction, equalization

Optimal sources: choir vowels, string sustains, organ chords, synth pads

Post-processing strategies:
  • Reverberation: Adds space, blends voices further
  • EQ: Shape spectral character of the mass
  • Compression: Control dynamic range of dense textures
  • Automation: Apply parameter changes over time
  • Layering: Combine multiple texture generations

Troubleshooting Common Issues

Problem: Output contains rhythmic artifacts/pulsing
Cause: Source material has transients, voices aligning periodically
Solution: Use more sustained source, increase time_offset_range, enable Gaussian distribution
Problem: Texture sounds "digital" or artificial
Cause: Too much pitch variation, insufficient voices
Solution: Reduce Max_pitch_cents (try 5-15), increase voice count, add fade time
Problem: Stereo image unbalanced or skewed
Cause: Random pan distribution clustering, asymmetry preset effects
Solution: Check stereo_spread setting, listen in mono for balance, adjust panning logic
Problem: Processing very slow
Cause: High voice count, long source duration
Solution: Reduce n_voices, shorten source material, use faster computer

Technical Deep Dive

Mathematical Foundations

Gaussian Distribution Approximation

Central Limit Theorem application:

# Simplified Gaussian approximation # Sum of uniform random variables → normal distribution def gaussian_like(scale): # Two uniform variables u1 = random.uniform(-0.5, 0.5) u2 = random.uniform(-0.5, 0.5) # Sum approximates normal with mean=0, variance=1/6 return (u1 + u2) * scale # Properties: # Mean: 0 # Variance: scale² × (1/6) # 68% of values within ±scale/√6 # 95% within ±scale/√1.5 # In script: offset = gaussian_like(time_range) # Creates bell-curve clustering around 0

Pitch Ratio Calculation

Cents to frequency ratio conversion:

# Musical cents definition: # 1200 cents = octave (2:1 frequency ratio) # Each cent = 2^(1/1200) ≈ 1.00057779 def cents_to_ratio(cents): return 2 ** (cents / 1200) # Examples: cents_to_ratio(0) = 1.000000 # no change cents_to_ratio(15) ≈ 1.008709 # +15 cents cents_to_ratio(-25) ≈ 0.985593 # -25 cents cents_to_ratio(1200) = 2.000000 # octave up # Implementation: pitch_ratio = 2 ^ (current_pitch_cents / 1200) new_sampling_rate = original_sr × pitch_ratio # Resample back to original_sr = pitch shift

Gain Staging Mathematics

Voice Summation and Loudness

Managing cumulative amplitude:

# Problem: Summing N uncorrelated voices # If each voice has amplitude A, sum grows as √N # Ideal gain per voice: voice_gain_ideal = target_gain / sqrt(N) # Example: 40 voices, target peak = 0.9 per_voice = 0.9 / sqrt(40) ≈ 0.9 / 6.325 ≈ 0.142 # In script: gain_per_voice = gain / sqrt(n_voices) # Rationale: # Sum of N uncorrelated signals with amplitude A: # RMS_total ≈ A × √N # To maintain constant loudness, scale each voice by 1/√N # This prevents clipping while maintaining texture density

Panning Laws

Constant-Power Panning

Stereo amplitude calculation:

# Constant-power panning maintains equal total power # regardless of pan position def pan_gains(pan): # pan ∈ [-1, 1] left_gain = sqrt((1 - pan) / 2) right_gain = sqrt((1 + pan) / 2) return left_gain, right_gain # Verification: # Total power = left_gain² + right_gain² # = (1-pan)/2 + (1+pan)/2 = 1 # Examples: pan_gains(-1) # hard left: (1.0, 0.0) pan_gains(0) # center: (0.707, 0.707) pan_gains(0.5) # right-ish: (0.5, 0.866) pan_gains(1) # hard right: (0.0, 1.0) # In script: l_gain = sqrt((1-pan_pos)/2) r_gain = sqrt((1+pan_pos)/2)