Pitch-Tracked Additive Resynthesizer - User Guide

Sample-accurate additive synthesis driven by tracked F0 and intensity. Re-synthesises a sound with complete control over partial families (harmonic, inharmonic, FM, ring modulation), amplitude laws (1/k, spectral tilt, formant band, random), voicing policies, and stereo rendering.

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

What this does

This script implements pitch-tracked additive resynthesis — a classic technique that tracks the fundamental frequency (F0) and intensity envelope of a sound, then re-synthesises it using additive synthesis where partial frequencies are derived mathematically from the F0 curve via phase accumulation (not naive sin(2π·f₀(t)·t)). The result is a flexible, controllable reconstruction that can range from natural resynthesis to extreme timbral transformations.

What is additive resynthesis? Any sound can be represented as a sum of sinusoids (partials) with time-varying frequencies and amplitudes. This script extracts the pitch and intensity curves from the input, then builds a new sound from scratch using those curves, but with complete control over which partials are present (harmonic, odd-only, inharmonic stretched, FM sidebands, ring modulation) and how their amplitudes behave (1/k decay, spectral tilt, formant shaping, randomness). Unlike naive sine-wave synthesis, this uses phase accumulation to ensure coherent FM and correct instantaneous frequency changes.

Key Features:

Technical Implementation: (1) Feature extraction (Praat): Pitch object (75–600 Hz, 5 ms step) → F0 CSV. Intensity object → intensity CSV. (2) Python synthesis: Load F0 and intensity, interpolate to sample rate, smooth F0 (median + lowpass). (3) Partial frequency generation: For each partial family, compute instantaneous frequency array using phase-accumulation formulation. (4) Amplitude law application: Static or time-varying weights averaged across partials. (5) Sample-accurate oscillator: For each partial, accumulate phase = Σ (2π·freqₖ·Δt), compute sin, scale by amplitude weight and envelope, sum across partials. (6) Voicing policy: Unvoiced regions replaced with silence, shaped noise, or original audio. (7) Stereo rendering + normalization.

Quick start

  1. In Praat, select exactly one Sound object (mono or stereo).
  2. Run script…PitchTrackedAdditive.praat.
  3. Choose a preset from the dropdown (natural_voice, bright_harmonic, hollow_odd, inharmonic_bells, ring_metal, fm_electric, subharmonic_bass, formant_vowel).
  4. Adjust Num partials (1–128, default 16).
  5. Set Pitch floor/ceiling (30–600 Hz, default 75–600 Hz).
  6. Select Voicing policy for unvoiced regions.
  7. Click OK — script extracts pitch/intensity, calls Python additive engine, and creates output originalname_ptadd.
Quick tip: Start with natural_voice for faithful resynthesis. For bell-like textures, use inharmonic_bells (partials follow k^1.08). For classic FM synthesis, use fm_electric (modulator ratio 2:1, index 3.5). Enable Draw_visualization to see F0 contour, partial frequency ranges, and input/output spectrograms.
Important: Python + numpy + scipy + soundfile required — install with pip install numpy scipy soundfile. The script uses phase accumulation (not naive sin(2π·f₀·t)) so frequency modulation and time-varying pitch work correctly. Output duration can be longer or shorter than input (default = input duration). For unvoiced regions (consonants, silence), the voicing policy determines behaviour.

8 Presets

PresetPartial FamilyAmplitude LawPartialsStereoVoicingCharacter
natural_voiceharmonic1/k24monocopy originalWarm, natural resynthesis — preserves original unvoiced regions.
bright_harmonicharmonicequal32HaasnoiseBright, full spectrum — all partials equal amplitude.
hollow_oddodd_only1/k16monosilenceClarinet-like — only odd harmonics.
inharmonic_bellsinharmonic_power (β=1.08)1/k²12partial spreadsilenceBell-like — stretched partials, rapid decay.
ring_metalring_sidebands (120 Hz)equal8HaassilenceMetallic, ring-modulated texture.
fm_electricfm_sidebands (ratio=2, index=3.5)equal8HaasnoiseClassic FM synthesis — electric piano / bell tones.
subharmonic_basssubharmonic1/k8mononoiseSub-octave bass — reinforces low frequencies.
formant_vowelharmonicgaussian formant (1200 Hz, BW 400 Hz)20monocopy originalVowel-like formant emphasis.

Partial Families — Instantaneous Frequency Generation

f₀(t) = fundamental frequency from pitch tracking (Hz)
k = partial index (1, 2, 3, … N)
harmonicfₖ(t) = k × f₀(t)— natural harmonic series
odd_onlyfₖ(t) = (2k-1) × f₀(t)— clarinet-like (odd harmonics only)
even_onlyfₖ(t) = 2k × f₀(t)— even harmonics only
subharmonicfₖ(t) = f₀(t) / k— sub-octave series (bass reinforcement)
inharmonic_powerfₖ(t) = k^β × f₀(t)— stretched partials (bell, gong), β ≈ 1.02–1.2
frequency_shiftedfₖ(t) = k × f₀(t) + Δf— constant frequency shift (transposition)
ring_sidebandsfₖ⁺(t) = |k×f₀(t) + fmod|, fₖ⁻(t) = |k×f₀(t) – fmod|— upper & lower sidebands from ring modulation
fm_sidebandscarrier = f₀(t), modulator = r×f₀(t)
fₖ(t) = |f₀(t) ± n×r×f₀(t)|, n = 0…N
— Bessel-weighted sidebands, n=0 (carrier), n≥1 (upper/lower pairs)

Phase accumulation (sample-accurate synthesis)

For each partial k and sample index n:

φₖ[n] = φₖ[n-1] + 2π·fₖ[n] / fs

y[n] = envelope[n] × Σₖ Aₖ[n] × sin(φₖ[n] + θₖ)

This formulation correctly handles time-varying frequencies (vibrato, glissandi) whereas sin(2π·f₀(t)·t) does not.

Amplitude Laws — Per-Partial Weighting

1/k (1/k)
Aₖ ∝ 1/k — natural harmonic decay (~6 dB/octave)
1/k² (1/k²)
Aₖ ∝ 1/k² — steep high-frequency falloff
equal
Aₖ = 1 — all partials same amplitude (bright, buzzy)
spectral_tilt_db/oct
Aₖ = 10^{(tilt × log₂(k)) / 20} — tilt control in dB/octave
gaussian_formant_band
Aₖ = exp(-0.5 × ((fₖ – f₀)/BW)²) — Gaussian peak around formant center
random_static
Aₖ = random(0.1, 1.0) — fixed random per partial (static)
random_slow
Aₖ(t) = base + depth × 0.5 × sin(2π·fLFO·t + φ) — time-varying random LFO per partial
Formant band details: Gaussian-shaped amplitude bump centered at formant_center Hz with bandwidth formant_bw. Useful for emulating vowel formants or resonant bodies. Example: Aₖ = exp(-0.5 × ((fₖ – 1200)/500)²) creates a peak around 1200 Hz.

Parameters

Core Parameters

ParameterRange/OptionsDefaultDescription
Preset8 presets + customnatural_voiceQuick configuration for different musical characters
Partial_familyharmonic, odd_only, even_only, subharmonic, inharmonic_power, frequency_shifted, ring_sidebands, fm_sidebandsharmonicHow partial frequencies relate to fundamental
Amplitude_law1/k, 1/k², equal, spectral_tilt, gaussian_formant, random_static, random_slow1/kAmplitude weighting across partials
Num_partials1–12816Number of sinusoidal partials
Pitch_floor/ceiling30–600 Hz75/600Range for pitch tracking (adjust to input source)
Voicing_policysilence_unvoiced, noise_unvoiced, copy_original_unvoicedsilence_unvoicedBehaviour in unvoiced (non-pitched) regions

Advanced Parameters (fixed per preset, editable in form)

inharmonic_beta — exponent for inharmonic_power family (1.03 default)
frequency_shift_hz — constant shift for frequency_shifted family
ring_modulator_hz — modulator frequency for ring_sidebands (80 Hz default)
fm_modulator_ratio — modulator/carrier ratio for fm_sidebands (2.0 default)
fm_index — modulation index (Bessel sideband weighting) for fm_sidebands (2.0 default)
spectral_tilt_db_per_octave — dB/octave slope for spectral_tilt law (-6 default)
formant_center_hz / formant_bw_hz — Gaussian formant peak (1200 Hz / 500 Hz default)
attack_release_smoothing_ms — envelope smoothing (15 ms default)
stereo_mode — mono, haas_width (0.8 ms delay), partial_spread (L/R decorrelation)
normalize_mode — none, peak (scale to -1 dBFS), rms (match input loudness)

Applications

Faithful Resynthesis (natural_voice)

Use case: Reconstruct an acoustic sound with natural harmonic decay and original unvoiced regions (consonants, breath). Output matches input perceptually but allows parameter tweaks.

Settings: partial_family=harmonic, amplitude_law=1/k, num_partials=24, voicing_policy=copy_original_unvoiced.

Harmonic Transformation (odd_only / even_only)

Use case: Clarinet-like (odd harmonics) or flute-like (even harmonics) timbres from any input.

Settings: hollow_odd preset — removes even harmonics, creates hollow, reedy character.

Bell / Inharmonic Textures (inharmonic_bells)

Use case: Transform melodic input into bell-like, metallic sounds with stretched partials.

Settings: inharmonic_power with β=1.08, amplitude_law=1/k² (fast decay), stereo=partial_spread.

FM Synthesis from Acoustic Input (fm_electric)

Use case: Use the pitch curve of a voice or instrument to drive an FM synthesis engine — create electric piano, bell, or brass-like sounds that follow the original pitch contour.

Settings: fm_sidebands, ratio=2.0, index=3.5, amplitude_law=equal, voicing=noise_unvoiced.

Ring Modulation / Metallic Textures (ring_metal)

Use case: Add metallic inharmonic sidebands to any sound.

Settings: ring_sidebands with 120 Hz modulator, amplitude_law=equal, stereo=Haas.

Workflow: Voice → FM Electric Piano

Target: Sung melody (monophonic voice).
Settings: fm_electric preset (ratio=2, index=3.5), num_partials=12, voicing=noise_unvoiced.
Result: The pitch contour of the voice drives FM synthesis — output sounds like an electric piano playing the same melody, with breath replaced by filtered noise.

Workflow: Piano → Bell Choir

Target: Piano recording.
Settings: inharmonic_bells preset (β=1.08), amplitude_law=1/k², stereo=partial_spread, num_partials=16.
Result: Each piano note becomes a bell-like tone with stretched partials and fast decay — a celestial bell choir.

Troubleshooting Common Issues:
No output or very quiet: Check F0 tracking — if input has no clear pitch (percussion, noise), the script may output silence. Use noise_unvoiced or copy_original_unvoiced policy.
Metallic or buzzy sound: Increase num_partials or change amplitude law to 1/k for natural decay. For inharmonic families, reduce β closer to 1.0.
Clicking artifacts: Increase attack_release_smoothing_ms (15–30 ms) and ensure voiced/unvoiced fade length is adequate.
FM synthesis sounds wrong: Check that partial_family=fm_sidebands and fm_index is reasonable (1–5). Low index = few sidebands (pure tone), high index = many sidebands (harsh/bright).
Pitch tracking fails for low/high voices: Adjust pitch_floor and pitch_ceiling to match input. For bass (50–200 Hz), set floor=50, ceiling=300. For soprano (200–800 Hz), set floor=150, ceiling=800.

Voicing Policies Explained

silence_unvoiced: Unvoiced regions produce silence (fade-out/in at transitions).
noise_unvoiced: Bandpassed white noise (200–6000 Hz) shaped by envelope replaces unvoiced regions.
copy_original_unvoiced: Original audio is mixed back in only where unvoiced — preserves consonants, breath, and noise while replacing voiced regions with synthesis.
Randomness controls: For random_slow amplitude law, each partial gets an independent LFO (0.2–2 Hz) modulating amplitude between 0.3×base and 1.0×base. This creates evolving, organic textures — ideal for ambient or generative patches. Use the --seed parameter for reproducibility.

Mathematical Deep Dive

Phase Accumulation vs. Naive Sinusoids

Naive (incorrect for time-varying f): y(t) = sin(2π·f₀(t)·t)
Derivative gives d/dt (f₀(t)·t) = f₀(t) + t·f₀′(t) — extra term causes wrong frequency.

Phase accumulation (correct): φ[n] = φ[n-1] + 2π·f[n] / fₛ
y[n] = sin(φ[n]) — instantaneous frequency = f[n] exactly.

F0 Smoothing Chain

1. Median filter (window = 20 ms) — removes pitch tracking outliers.
2. Lowpass Butterworth (cutoff = 12 Hz) — smooths vibrato and natural pitch variation.
3. Voiced/unvoiced fade — cosine ramp (5 ms) to avoid clicks at transitions.

Envelope Shaping

Intensity (dB) → linear amplitude: A = 10^{(I - I_max)/20}
Attack/release smoothing: one-pole IIR filter α = exp(-1/(τ·fₛ))
y[n] = (1-α)·x[n] + α·y[n-1]