CWT Granular Resampler — Wavelet-Driven Cross-Synthesis
Takes two Sounds: A (analysis) and B (grain source). Significant pooled |CWT(t,f)| cells from A generate an expected number of grains from B; fractional counts are realised stochastically. Each grain is read from B at a playback-rate ratio derived from the firing wavelet bin, with duration scaled by that bin's frequency. The result maps A's time–frequency morphology onto B's timbral material.
What this does
This script implements a continuous wavelet transform (CWT) driven granular resynthesizer for Praat AudioTools. It takes two Sounds: Sound A provides the analysis — its time–frequency structure is extracted via a complex Morlet CWT. Sound B provides the raw audio material. Each trigger-grid cell that survives peak selection and thresholding produces an expected grain count from its dB level. That count can become zero, one, or several grains after stochastic fractional rounding. Each grain is read from Sound B at a playback-rate ratio derived from the firing wavelet frequency and given a duration inversely related to that frequency.
Key Features:
- Two‑source cross‑synthesis — Sound A (analysis) and Sound B (grain source) are independent.
- Complex Morlet CWT — shared analysis core with CWT_Scalogram, vectorised for speed.
- Coarse trigger grid — |CWT| is pooled onto a coarser time–frequency grid; every cell above threshold can fire grains.
- Peak selection modes — Spectral ridge compares the two frequency neighbours, Local maxima uses a 4‑neighbour time/frequency test, and All cells applies no peak test beyond thresholding.
- Density‑driven grain count — each cell contributes an expected grain count proportional to its dB level above threshold; the integer part always fires and the fractional part is realised stochastically.
- Transposition & duration scaling — playback rate = f_k / Reference_frequency_Hz, clamped to ±Transposition_range_st semitones. Grain duration ∝ 1/f_k.
- Two position modes — Sequential (playhead walks through B) or Energy‑weighted random (RMS probability density).
- Anti‑aliased source copies — v1.2 band‑limits B per half‑octave rate group to prevent fold‑over on pitch‑up.
- Single‑pass overlap‑add — each grain is a Formula (part) command: transposition, window, pan, and summation in one go.
- Stereo output — constant‑power pan with per‑grain jitter.
- Dry/wet mix — Sound A can be blended with the wet granular output after the dry signal is gain-matched to the wet peak; the finished mix is then peak-scaled to Output_peak.
- Visualisation — trigger map (scalogram with grain positions overlaid) and output waveform.
(dB − threshold) / (0 − threshold) × Grain_density_multiplier. This is a linear function of dB, not of magnitude or energy. At a −40 dB threshold, a −20 dB cell scores 0.5, where its magnitude ratio is 0.1 and its energy ratio 0.01. Quiet cells therefore keep far more presence than a linear reconstruction would give them.
Signal path
→ pooled onto coarse trigger grid (Trigger_step_ms × Trigger_bands_per_octave)
→ dB re global peak → optional 4‑neighbour local‑maximum mask → threshold → grain list
B (grain source) → mono → oversampled (1–8×) → band‑limited per rate group
→ read by each grain at its own rate → windowed, panned, summed into output buffer
How each CWT cell maps to a grain
Time: onset = cell_time ± jitter (up to half a trigger step).
Pitch: rate = f_k / Reference_frequency_Hz, clamped to ±Transposition_range_st semitones.
Duration: d_k = Base_grain_duration_ms × fGeo / f_k, clamped to 3–400 ms. This is the 1/f relation of the Morlet scale itself.
Density: expected = ((dB − threshold) / (0 − threshold)) × Grain_density_multiplier, fractional part realised stochastically.
Position: Sequential: a normalised position u is computed over the analysed duration of A, warped by Position_warp, then mapped into the valid read range of B: approximately position = u × (durB − grain_read_duration) before position jitter. Energy‑weighted random: drawn from B's RMS profile.
Pan: constant‑power pan with per‑grain uniform draw scaled by Pan_jitter.
Level: amp = 10^(dB/20) re the trigger‑grid peak.
4 Presets
| Preset | Character | Freq range (Hz) | Voices/oct | Trigger step (ms) | Threshold (dB) | Density mult. | Transposition range |
|---|---|---|---|---|---|---|---|
| Balanced cloud | Default, versatile | 80 – 6000 | 12 | 20 | −32 | 1.5 | User setting (default ±24 st) |
| Sparse pointillist | Few, well‑separated grains | 80 – 6000 | 12 | 30 | −20 | 1.0 | User setting (default ±24 st) |
| Dense wash | Thick, continuous texture | 60 – 8000 | 16 | 12 | −40 | 2.5 | User setting (default ±24 st) |
| Shimmer | High, short grains | 300 – 10000 | 16 | 10 | −36 | 2.0 | User setting (default ±24 st) |
--run), pause blocks auto-continue with the current values.
Grain mapping — where in B the grain reads from
Sequential
A playhead walks through B. The grain's position is ((t − t0) / durA)^Position_warp. warp = 1 is linear; warp > 1 holds the read head near the start; warp < 1 pushes it towards the end.
Energy‑weighted random
Positions are drawn from B's RMS profile — probability proportional to RMS (not RMS²). Dense sections of B are sampled more often, creating a timbral density that follows B's own energy envelope.
Implementation notes
Trigger pooling: v1.2 performs explicit 2D max pooling: each coarse trigger cell takes the maximum of all fine CWT cells that fall inside its time–frequency bounds. This is not bilinear or point resampling. v1.1 point-sampled the fine grid, so a transient between trigger centres could be missed.
Anti‑aliased source copies: Reading B at rate r can fold source content above
outputNyquist/r back into the audible band. v1.2 band-limits B once per half-octave rate group, using Filter (pass Hann band): 0, cutoff. This prevents the predictable pitch-up fold-over within the grouped-rate design while keeping render cost manageable.
Single-pass overlap-add: Each grain is rendered with one
Formula (part) command:
self + gain * window(x) * object(srcB, pos + rate*(x - onset))
object() returns 0 outside the referenced Sound's domain, so no range guard is needed. In one reference benchmark this measured about 0.58 ms per grain for 30 ms grains at 44.1 kHz; actual performance depends on the machine and parameter set.
Windows: Hanning or Gaussian (truncated at 2.5σ, offset‑corrected so edges reach exactly zero). Both reach zero at the grain edges, so overlap‑add introduces no step discontinuity.
Oversampling: Sound B is resampled up front (1×, 2×, 4×, or 8×). Oversampling reduces interpolation error; the grouped low-pass source copies address pitch-up aliasing. In one reference test on a 5 kHz tone, measured interpolation error improved from about −37 dB at 1× to −50 dB at 4× and −79 dB at 8×.
Applications
Speech → Granular texture
Use case: Analyse a spoken phrase (A) and use a synth pad or environmental recording (B) as the grain source. The output follows the speech's rhythmic and spectral contour but sounds like the pad.
Settings: Balanced cloud preset, Sequential mapping, warp = 1. Time-frequency energy associated with speech formants, frication, and plosive transients can concentrate triggers in corresponding regions, creating a "talking pad" effect.
Drum loop → Rhythmic re‑synthesis
Use case: Analyse a drum loop (A) and use a sustained synth tone (B). The drum loop's transient structure triggers grains from the synth, turning a rhythmic pattern into a pitch‑based texture.
Settings: Sparse pointillist preset, Energy‑weighted random. Low-frequency-dominant events may concentrate triggers in lower CWT bands, while brighter transients may activate higher bands, preserving much of the loop's rhythmic timing in a different timbral domain.
Environmental recording → Morphological mapping
Use case: Analyse a bird call or field recording (A) and use a musical instrument (B). The output carries the time‑frequency morphology of the environment but sounds like the instrument.
Settings: Shimmer preset, Sequential mapping with warp = 2 (holds the read head near B's start). The result is a dense, shimmering texture that follows the contour of the environmental recording.
Workflow: Speech → Pad (talking pad)
A: A spoken sentence.
B: A sustained synth pad.
Settings: Balanced cloud, Sequential, warp = 1, dry/wet = 0.8.
Result: The speech's time-frequency concentrations generate grains from the pad. The pad timbre can therefore follow the speech's rhythmic and spectral motion — a "talking pad" effect.
Workflow: Drum loop → Synth (rhythmic re‑synthesis)
A: A breakbeat drum loop.
B: A synthesizer with a rich harmonic spectrum.
Settings: Sparse pointillist, Energy‑weighted random, transposition range = ±12 st.
Result: Each drum hit triggers a grain from the synth. Low-frequency-dominant drum events tend to favour lower trigger bands, while brighter transients can favour higher ones. The rhythmic timing of the loop can remain recognisable while the audible material comes from the synth.
Workflow: Bird call → Piano (morphological mapping)
A: A bird call or field recording.
B: A piano recording.
Settings: Shimmer, Sequential, warp = 2, grain window = Gaussian.
Result: The bird call's fast frequency modulations and rhythmic structure map onto the piano. The output is a shimmering, piano‑based texture that follows the bird's contour.
• No grains fire: The threshold may be too high, peak selection may reject most cells, or the trigger grid may be too coarse. Lower Trigger_threshold_dB, reduce Trigger_step_ms, or try a less restrictive Peak_selection mode.
• Output is aliased / distorted: Higher Source_oversampling can reduce interpolation error, while v1.2's rate-grouped low-pass source copies protect pitch-up grains from predictable fold-over. Very wide transposition ranges can still produce demanding edge cases near Nyquist.
• Grains run past the end of B: If B is shorter than the mean grain read length, grains tail into silence. Use a longer B or shorten Base_grain_duration_ms.
• Output is silent but grains fired: Check Dry_wet — if it is 0, the output is pure dry (Sound A) and the wet grains are muted. If it is 1, the wet signal may be very quiet — check Output_peak and the grain amplitudes.
• Trigger map shows no dots: The trigger grid cells are below the threshold. Lower the threshold or choose a different Peak_selection mode.
• Very few grains are generated: Expected counts below 1 are not simply rounded down: their fractional parts fire probabilistically. Increase Grain_density_multiplier or lower the threshold if the overall expected density is too low.
Output & report
Output Sound
Named granular_<A>_x_<B>. Stereo or mono, then peak-scaled to Output_peak. When dry/wet mixing is used, Sound A is first gain-matched to the wet peak before the dry and wet contributions are combined. The output buffer is slightly longer than the analysed duration to accommodate onset jitter and grain tails.
Visualisation
When Draw_trigger_map is enabled: scalogram of A (dB) with generated grain positions overlaid as coloured circles (radius ∝ amplitude, colour ∝ amplitude), plus the output waveform. For very large grain counts the plotted markers may be visually subsampled for rendering speed. The title/summary bands report analysis and engine settings.
Info window
Reports grain distribution by octave (count, share, mean duration, transposition span), synthesis statistics (including mean duration, overlap, grain rate, playback/transposition statistics, wet peak/RMS, and B consumption), and analysis/render statistics such as CWT grid, trigger grid, headroom, grain-budget scaling, and elapsed time.
nocheck, so a machine without audio device will not abort the run.