Skip to content

Repository files navigation

@audio/stretch test npm license

Try it in the browser: Pitch and tempo changer. Runs on this package, nothing is uploaded.

Time stretching algorithms — umbrella over @audio/stretch-* atoms.

Atom Algorithm Domain Quality CPU Best for
@audio/stretch-wsola WSOLA time ★★★★ low speech, solo voice
@audio/stretch-psola PSOLA time ★★★★ medium speech, monophonic instruments
@audio/stretch-pvoc plain phase vocoder freq ★★ medium educational baseline
@audio/stretch-pvoc-lock phase-locked vocoder freq ★★★★ medium general music
@audio/stretch-pghi phase-gradient vocoder freq ★★★★ medium vibrato, glides, chirps
@audio/stretch-pvsola vocoder reset to the waveform freq+time ★★★★★ medium slowing speech, solo voice
@audio/stretch-transient transient-aware vocoder freq ★★★★★ medium music with percussion
@audio/stretch-hybrid HPSS hybrid freq+time ★★★★ high full mixes — drums over tonal
@audio/stretch-paulstretch PaulStretch freq — medium extreme stretch (ambient, drones)
@audio/stretch-sms Sinusoidal Modeling sinusoidal ★★★★ high harmonic / tonal material

For pitch shifting, see the @audio/shift-* family.

Usage

Install the umbrella (all atoms):

npm install @audio/stretch
import { transient, wsola } from '@audio/stretch'

let slower = transient(samples, { factor: 2 })          // 2× slower, same pitch
let fast   = wsola(samples, { factor: 0.75 })           // 1.33× faster

let write = transient({ factor: 1.5 })                  // real-time streaming
write(block1)
write(block2)
write()                                                  // → remaining samples

Or install just the atom you need — each is self-contained:

npm install @audio/stretch-transient
import transient from '@audio/stretch-transient'
let out = transient(samples, { factor: 2 })

Float32Array in/out; a channel array [L, R] (or Float64Array) is accepted and processed per channel — parity with @audio/shift. Output sizes may be variable — small or empty early chunks are normal in streaming.

Time domain

wsola — @audio/stretch-wsola

Waveform Similarity Overlap-Add, in the segment form of SoundTouch and sox tempo. Segments of the input are laid one hop apart, each crossfaded into the one before, and each read where its head best continues the one before (normalized cross-correlation within ±delta of where the time map puts it). The waveform is copied, not resynthesized, so a voice keeps its glottal pulse shape and its consonants keep their attacks. A phase vocoder keeps each harmonic but not their alignment to one another, which is why speech through it sounds distant. Channels share one search. No FFT.

import wsola from '@audio/stretch-wsola'

wsola(data, { factor: 1.5 })
wsola([left, right], { factor: 0.5, sampleRate: 48000 })
Param Default
factor 1 Time stretch ratio, or a function of input seconds
sampleRate 44100
frameSize 40 ms Segment
hopSize frameSize · 3/5 Output hop; segments cross over the rest (16 ms, one period at 62.5 Hz)
delta 8 ms Search (±), half that period

Use when: shortening speech, a solo voice or a monophonic instrument; slowing them, pvsola (copying repeats noise and reverberation into a flanger).
Not for: chords and mixes. One alignment cannot fit several pitches; use the frequency-domain methods.

psola — @audio/stretch-psola

Pitch-Synchronous Overlap-Add. Detects pitch period via autocorrelation, then windows grains at pitch cycle boundaries. Because grains align with the pitch cycle there are no phase discontinuities at overlaps — cleaner than WSOLA for monophonic pitched signals.

import psola from '@audio/stretch-psola'

psola(data, { factor: 1.5 })
psola(data, { factor: 0.75, sampleRate: 48000 })
psola(data, { factor: 2, minFreq: 100, maxFreq: 400 })  // male voice range
Param Default
factor 1 Time stretch ratio
sampleRate 44100 For pitch detection frequency range
minFreq 80 Lowest expected pitch (Hz)
maxFreq 500 Highest expected pitch (Hz)

Use when: Speech, solo vocals, monophonic instruments, factors 0.5–2×.
Not for: Polyphonic material — autocorrelation finds one pitch period so chords get mangled. Extreme ratios (>2×) cause gaps.

Frequency domain

pvoc — @audio/stretch-pvoc

Plain phase vocoder. Each bin's phase advances at its instantaneous frequency independently. Magnitudes are preserved but incoherent inter-harmonic phase relationships give complex signals a diffuse, "underwater" quality.

import pvoc from '@audio/stretch-pvoc'
pvoc(data, { factor: 2 })
Param Default
factor 1 Time stretch ratio
frameSize 2048 FFT size (power of 2)
hopSize frameSize/4 Hop between frames

Use when: Educational baseline, simple tonal signals.
Not for: General music — use pvoc-lock or transient.

pvoc-lock — @audio/stretch-pvoc-lock

Phase-locked vocoder (Laroche & Dolson, 1999). After propagating phases, locks non-peak bins to their nearest spectral peak's rotation. Restores harmonic phase coherence, eliminating phasiness.

import pvocLock from '@audio/stretch-pvoc-lock'
pvocLock(data, { factor: 2 })

Same options as pvoc.

Use when: General music — tonal/ambient material where transient resets aren't needed.
Not for: Percussive material where attacks matter — use transient.

pvsola — @audio/stretch-pvsola

A phase-locked vocoder whose frames restart from the input's own waveform wherever it fits (Moinet & Dutoit, DAFx 2011). Where a voice is periodic its frames take the input's own phases and keep the shape of its glottal pulses; noise, breath and reverberation stay the vocoder's, so slowing never repeats them (WSOLA's flanger) nor spreads the voice (the vocoder's phasiness).

import pvsola from '@audio/stretch-pvsola'
pvsola(data, { factor: 1.5 })
Param Default
factor 1 Time stretch ratio, or a function of input seconds
sampleRate 44100
frameSize 46 ms Frame, a power of 2
shift 4.5 ms Shift searched for a reset (±)

Use when: slowing speech or a solo voice.
Not for: shortening, where wsola keeps attacks better.

pghi — @audio/stretch-pghi

Phase Gradient Heap Integration — "Phase Vocoder Done Right" (Průša & Holighaus, 2017). Synthesis phase is integrated from the analysis phase gradients, visiting bins in magnitude order via a max-heap: no peak picking, no transient heuristics; chirps, vibrato and glides stay coherent by construction.

import pghi from '@audio/stretch-pghi'

pghi(data, { factor: 2 })
Param Default
factor 1 Time stretch ratio
frameSize 2048 FFT size (power of 2)
hopSize frameSize/8 Hop — gradient integration wants dense frames
tolerance 1e-6 Bins below tolerance×max get random phase

Use when: modulated material — vibrato, glissandi, pitch-unstable sources.
Not for: steady polyphony — pvoc-lock's identity locking reproduces intra-partial phases exactly where gradient integration only approximates them.

transient — @audio/stretch-transient

Transient-aware phase-locked vocoder (Röbel, 2003). Measures spectral flux between frames; on a sharp onset it resets to the original analysis phase instead of propagating it, preserving attack sharpness on drums and plucks. Implies phase locking.

import transient from '@audio/stretch-transient'

transient(data, { factor: 2 })
transient(data, { factor: 1.5, transientThreshold: 2.0 })  // less sensitive detection
Param Default
factor 1 Time stretch ratio
frameSize 2048 FFT size (power of 2)
hopSize frameSize/4 Hop between frames
transientThreshold 1.5 Spectral flux threshold (higher = fewer resets)

Use when: The right default for most music — percussion, mixed sources.
Not for: Voice/speech — use psola. Extreme stretch — use paulstretch.

hybrid — @audio/stretch-hybrid

Harmonic/percussive hybrid (Driedger & Müller). Median-filter HPSS splits the spectrogram into two layers; the harmonic layer goes through the phase-locked vocoder, the percussive layer through short-frame OLA — chords stay coherent and attacks stay sharp, where one algorithm must trade one for the other.

import hybrid from '@audio/stretch-hybrid'

hybrid(data, { factor: 2 })
Param Default
factor 1 Time stretch ratio
frameSize 2048 FFT size for HPSS + harmonic path
percFrame 512 OLA frame for the percussive layer
harmMedian 17 Median filter across time (frames)
percMedian 17 Median filter across frequency (bins)

Use when: full mixes — drums over tonal material.
Cost: separation + two stretches ≈ 4–6× the CPU of pvoc-lock alone.

paulstretch — @audio/stretch-paulstretch

Extreme time stretching via phase randomization (Nasca, 2006). Preserves magnitudes but replaces all phases with random values, producing smooth, dreamlike textures. Designed for large factors.

import paulstretch from '@audio/stretch-paulstretch'

paulstretch(data, { factor: 8 })
paulstretch(data, { factor: 100, frameSize: 8192 })
Param Default
factor 8 Time stretch ratio (best >2×)
frameSize 4096 FFT size (larger = smoother)
seed 0x1f123bb5 PRNG seed (deterministic output)

Use when: Ambient music, sound design, drone generation, 8×–1000× stretch.
Not for: Small ratios (<2×) — sounds washed out. Not for preserving rhythm or transients.

Sinusoidal

sms — @audio/stretch-sms

Sinusoidal Modeling Synthesis (Serra 1989, McAulay-Quatieri 1986). Decomposes audio into individually tracked sinusoidal partials and resynthesizes at the new time rate. Each partial's frequency and magnitude are interpolated independently — no phase spreading or bin-by-bin artifacts.

import sms from '@audio/stretch-sms'

sms(data, { factor: 2 })
sms(data, { factor: 0.5, maxTracks: 80 })
sms(data, { factor: 3, frameSize: 4096 })
Param Default
factor 1 Time stretch ratio
frameSize 2048 FFT frame size
hopSize frameSize/4 Hop between frames
maxTracks 60 Max simultaneous sinusoidal tracks
minMag 1e-4 Peak detection threshold (linear)
freqDev 3 Max frequency deviation (bins) for track continuation
residualMix 1 Stochastic residual blended into the sinusoidal output

Use when: Harmonic / tonal content — instruments, chords, vocals — where the phase vocoder introduces smearing. Default residualMix=1 blends breath, noise, and transient energy alongside the sinusoidal model.
Not for: Noise-dominated material.

Quality metrics — @audio/quality

Every atom is self-contained (framing/OLA helpers inlined; the phase-locking engine lives in @audio/spectral-pvoc). Output quality is evaluated with @audio/quality:

import { lsd, spectralSim, chordBalance, chordRetention, modulationDepth } from '@audio/quality'

lsd (log-spectral distance), spectralSim (cosine similarity), chordBalance, chordRetention, and modulationDepth (AM depth per partial) evaluate algorithm output against a reference.

Research & comparison

Command What it does
node scripts/compare.js writes compare.html — interactive waveforms, playback, internal-vs-external comparisons
node scripts/bench.js throughput and ×realtime numbers for batch and streaming
node scripts/diagnose.js targeted diagnostics for specific algorithm behaviors

Demo for a lightweight browser listening matrix.

See also

References

  • Verhelst, W. & Roelands, M. (1993). "An overlap-add technique based on waveform similarity (WSOLA)." ICASSP.
  • Laroche, J. & Dolson, M. (1999). "Improved phase vocoder time-scale modification of audio." IEEE Trans. Speech Audio Processing.
  • Röbel, A. (2003). "A new approach to transient processing in the phase vocoder." DAFx.
  • Nasca, P. (2006). "PaulStretch — extreme time stretching." paulnasca.com.
  • Moulines, E. & Charpentier, F. (1990). "Pitch-synchronous waveform processing techniques for text-to-speech synthesis using diphones." Speech Communication, 9(5-6).
  • Driedger, J. & Müller, M. (2016). "A review of time-scale modification of music signals." Applied Sciences, 6(2).
  • Serra, X. (1989). "A System for Sound Analysis/Transformation/Synthesis Based on a Deterministic plus Stochastic Decomposition." PhD thesis, Stanford.
  • McAulay, R.J. & Quatieri, T.F. (1986). "Speech analysis/synthesis based on a sinusoidal representation." IEEE Trans. ASSP, 34(4).

About

Time stretch algorithms

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages