npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@audio/denoise-dewow

v0.1.0

Published

Wow & flutter correction — pitch-drift removal for tape/vinyl/cassette transfers

Readme

@audio/denoise-dewow npm MIT

Wow & flutter correction — pitch-drift removal for tape/vinyl/cassette transfers

npm install @audio/denoise-dewow
import dewow, { analyze } from '@audio/denoise-dewow'

Estimates the transport's instantaneous speed over time and corrects it by variable-rate resampling — the classical (non-ML) counterpart of Celemony Capstan. Three estimators: track the phase-vocoder instantaneous frequency of stable spectral partials ('partial', default — McAulay & Quatieri 1986 partial tracking + Godsill & Rayner, Digital Audio Restoration, 1998, ch. 6), lock onto one known tone such as mains hum or a calibration tone ('reference' — Czyżewski et al., Wow detection and compensation employing spectral processing of audio, JAES 2007), or track monophonic pitch ('pitch'@audio/pitch-pyin). See Howarth & Wolfe, Correction of Wow and Flutter Effects in Analogue Tape Transfers, AES 117th/118th Convention, 2004/2005, and Nichols, The Digital Restoration of Wow and Flutter Distorted Gramophone Recordings, 1999.

let corrected = dewow(recording, { fs: 44100 })                       // default: track partials
let corrected = dewow(recording, { fs, mode: 'reference', refFreq: 50 }) // lock onto 50 Hz mains hum
let corrected = dewow(recording, { fs, mode: 'pitch', smooth: 3 })    // monophonic voice/instrument

let meter = analyze(recording, { fs })
// → { speed, times, hop, wow, flutter, wowPeak, flutterPeak, confidence, tracks? }

recording is a mono Float32Array or an array of channels ([L, R, …]); analysis always runs on the mono mix, and one shared curve corrects every channel, so a stereo pair stays sample-aligned. Returns new arrays — never in place.

| Param | Default | | |---|---|---| | fs | 44100 | Sample rate | | mode | 'partial' | 'partial' | 'reference' | 'pitch' | | refFreq | — | Known tone/hum frequency (Hz) — required for mode: 'reference' | | frameSize | 4096 | STFT frame for peak-picking / track continuity | | hopSize | 512 | STFT hop — also the per-hop curve's own sample rate (its Nyquist, fs/(2·hopSize), caps flutter frequency) | | smooth | 0.05 | Zero-phase smoothing time constant (s) separating wow (below) from flutter (residual) | | wow | true | Correct the low-passed (<~6 Hz) component | | flutter | true | Correct the residual (faster) component | | maxDeviation | 0.05 | Clamp the corrected speed ratio to [1−x, 1+x] | | minTrack | 0.5 | Shortest partial kept, in seconds — 'partial' mode only | | minFreq / maxFreq | 50 / 2000 | f0 search range — 'pitch' mode only | | keepLength | true | Output length equals input length |

analyze() is the estimator alone — the "wow & flutter meter" — with no audio output. wow/flutter are the unweighted RMS deviation in %; wowPeak/flutterPeak are the unweighted peak deviation in %. These are not the IEC 60386 / DIN 45507 figure, which applies a psychoacoustic weighting curve (peaking near 4 Hz) before measuring — that weighting filter isn't implemented here, so a reading from this meter isn't directly comparable to a spec-sheet wow-and-flutter number. confidence is the fraction of hops with a usable estimate (a stable track, a present reference tone, or voiced pitch); tracks (mode 'partial' only) lists the accepted partials.

Resolution trade-offs, measured, not assumed:

  • Flutter ceiling. The per-hop curve is itself sampled at fs/hopSize (86 Hz at the defaults) — anything above its Nyquist, fs/(2·hopSize) ≈ 43 Hz, can't be represented at all. Below that, the phase-vocoder estimate needs the analysed signal to be roughly stationary across its whole analysis window, not just one hop — a 4096-sample window (93 ms) averages away a 30 Hz flutter almost entirely (measured: a synthetic ±0.4% 30 Hz flutter reads back as ~0, correlation −0.18 against the true curve). 'partial' mode works around this with a second, short window (1024 samples, 23.2 ms) purely for the frequency reading, while the long window still does peak-picking / track continuity (which needs the frequency resolution to keep this repo's own test chord's 220/330/440/660 Hz partials from crosstalking). That recovers 30 Hz flutter to within the tolerances below, but a shorter hopSize is the honest fix for tracking flutter closer to the nominal 100 Hz ceiling.
  • 'reference' mode and low reference frequencies. A 50/60 Hz mains hum needs a much longer reading window than a musical partial does — one cycle of 50 Hz is 20 ms, comparable to the flutter-tracking window above, so 'reference' mode scales its window to the target frequency (~4.5 cycles) instead of using the fixed one. It also band-passes (Q 5) around refFreq before reading phase: program content sharing that band (speech has real energy down at 50 Hz) otherwise corrupts the reading (measured: 50 Hz hum at −30 dB under 6 s of speech, curve correlation 0.94 unfiltered → 0.97 filtered).
  • 'pitch' mode and real speech/instruments. smooth doubles as the mode's own vibrato/prosody-vs-drift cutoff (speed = f0 / lowpass(f0, smooth)): the default 0.05 s tracks pitch fast enough that genuine slow wow gets absorbed into the baseline and cancels out of the ratio, leaving only fast flutter as a visible deviation. A smooth of several seconds recovers slow wow, at the cost of also absorbing real vibrato/prosody as if it were flutter — natural speech intonation moves far more (measured: tens of percent) than a 2% wow defect, so this mode is a functional correction, not a precision one, on unpitched or lightly-inflected material. It's also frame-independent (@audio/pitch-pyin has no Viterbi/HMM smoothing across frames — see that package's own README) — no octave-jump correction is applied.

Correction. Once the speed curve is built, correction is a single windowed-sinc read (@audio/resample-sinc, 16 zero-crossings) per output sample at a warped position — pos[n] = pos[n-1] + 1/speed(pos[n-1]), narrowing the anti-alias cutoff whenever the local read rate exceeds 1×. Measured on the synthetic 2%[email protected] Hz + 0.4%-flutter-@30 Hz defect used in the tests: the 440 Hz partial's own residual frequency deviation drops from 1.4% to 0.18% RMS (target ≤0.2%); a clean, undistorted input passes through at ≥40 dB SNR (the estimator finds speed≈1 and a sinc read at near-integer positions is near-identity). A plain sample-domain SNR against the pristine original is not meaningful after correction, even a mathematically exact one — see the source comment on localSnr in test.js for why, and use analyze()'s own residual-deviation-style check instead if you need a number.

Not implemented: azimuth/head-alignment error (a frequency-independent time skew across the stereo image, not a speed error — out of scope), dropout/gap repair (@audio/denoise-repair), or anything ML-based. Celemony Capstan and similar tools additionally use trained models to separate genuine musical vibrato from mechanical wow on program material with no stable partial or reference tone at all; this package only ever measures speed from spectral evidence actually present in the signal.

Use when: tape hiss/wobble on cassette or reel-to-reel transfers, turntable speed instability (belt/motor wear), 16mm/optical-track flutter — anything with a stable tone, a mains-hum residual, or several seconds of sustained pitched content to lock onto. Not for: dropouts, azimuth error, or program material with no stable partial and no reference tone (a cappella breath, pure noise, hard cuts) — the estimator has nothing to track.


Part of @audio/denoise — the denoise family umbrella.

MIT © audiojs