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/eq-fit

v0.1.0

Published

Fit an N-band parametric EQ (RBJ peaking + shelves) to a target magnitude curve — greedy init + Levenberg–Marquardt refinement

Downloads

148

Readme

@audio/eq-fit npm MIT

Fit an N-band parametric EQ to a target magnitude curve

npm install @audio/eq-fit
import fit from '@audio/eq-fit'

The missing math between curve producers (@audio/spectral-target, @audio/measure-response, or a plain list of {f, gain} points) and band consumers (@audio/eq-parametric). Two stages: greedy residual-peak-picking places an initial band at each remaining error's largest excursion (in the spirit of AutoEQ's — jaakkopasanen/AutoEq — initial-guess heuristic, reimplemented here rather than ported); then Levenberg–Marquardt (Marquardt 1963) jointly refines every band's frequency, gain, and Q against the whole curve at once. Coefficients and |H(f)| come from @audio/biquad's RBJ Audio-EQ-Cookbook implementation — this module only searches for good {fc, Q, gain}, it never derives filter math of its own.

// from control points
let r = fit([{ f: 100, gain: 4 }, { f: 3000, gain: -3 }, { f: 8000, gain: 2 }], { bands: 6 })
r.bands     // [{ type: 'peak', fc, Q, gain }, ...] — ready for @audio/eq-parametric's params.bands
r.preamp    // dB, ≤ 0 — headroom so the fitted chain never clips
r.error     // weighted RMS dB error of the fit
r.response  // f => dB, the fitted cascade's response incl. preamp

// from a function
fit(f => (f > 5000 ? -6 : 0), { bands: 4, shelves: true })

// from a @audio/measure-response result
import measureResponse from '@audio/measure-response'
fit(measureResponse(impulseResponse, { fs }), { fs })

// apply it
import parametricEq from '@audio/eq-parametric'
parametricEq(data, { bands: r.bands, fs: 44100 })

// interchange with AutoEQ / Equalizer APO
import { toEqualizerApo, fromEqualizerApo } from '@audio/eq-fit'
toEqualizerApo(r)             // "Preamp: -2.1 dB\nFilter 1: ON PK Fc 100 Hz Gain 4.0 dB Q 1.41\n…"
fromEqualizerApo(apoText)     // { bands, preamp } — reads AutoEQ's own ParametricEQ.txt files too

| Param | Default | | |---|---|---| | fs | 44100 | Sample rate, Hz | | bands | 8 | Max band count — fewer are returned once tolerance is met | | fMin / fMax | 20 / 20000 | Fitted frequency range, Hz | | shelves | true | Allow one low-shelf + one high-shelf at the range's edges | | maxGain | 12 | Max |gain| per band, dB | | minQ / maxQ | 0.3 / 10 | Per-band Q bounds | | tolerance | 0.5 | Stop the greedy init once weighted RMS error is ≤ this, dB | | weight | uniform | f => number — the eval grid is itself log-spaced, so uniform weight is already "uniform per octave"; only pass this to favor/discount a region | | grid | 256 | Log-spaced evaluation points | | iterations | 200 | Levenberg–Marquardt iteration cap (stops earlier on convergence) | | preamp | true | false → skip the headroom calc, result.preamp is always 0 |

target accepts any of: {f, gain}[] control points (log-frequency interpolation), an f => dB function, a Float32Array of dB-per-bin over [0, fs/2] (the @audio/spectral-target bin-grid convention), or {freqs, db} (the @audio/measure-response shape). Deterministic — no randomness anywhere in either stage, so the same input always returns the same bands.

preamp follows AutoEQ's own ParametricEQ.txt convention: -max(0, peak of the fitted cascade), so response(f) (which includes it) never exceeds 0 dB — the fitted EQ is safe to apply to a full-scale signal without digital clipping.

Use when: turning a measured or target curve — a headphone correction target, a room-EQ deviation curve, a captured reference spectrum — into a small parametric band list a real-time EQ (or a hardware unit that only understands PK/LSC/HSC text) can use, instead of the full-resolution FIR alternative (@audio/eq-fir).

Algorithm

  1. Init — for each side (shelves on): if the target's own value at fMin/fMax exceeds ±0.1 dB, place a shelf there (Q 0.707, corner where the (⅓-octave smoothed) curve crosses half that edge value). Remaining slots: repeatedly smooth the current residual (⅓-octave), place a peaking band at its largest excursion (gain = residual there, Q from the −3 dB-of-gain half-power width of the local lobe), subtract, repeat until bands is used or weighted RMS ≤ tolerance.
  2. Refine — joint nonlinear least squares over every band's (ln fc, gain, ln Q) at once: Levenberg–Marquardt (Marquardt 1963, the diag(JᵀJ)-scaled damping, not Levenberg's plain identity; initial damping τ·max(diag(JᵀJ)) per Marquardt's own §4) with a numeric Jacobian (central differences) and box constraints enforced by clamp-and-reproject. Stops on relative cost improvement < 1e-6 or iterations (Nocedal & Wright, Numerical Optimization 2nd ed., §10.3).
  3. Bands with |gain| < 0.1 dB are dropped, the rest sorted by fc.

Measured on this package's own test suite: exact recovery (fc within 2%, gain within 0.2 dB, Q within 10%, RMS < 0.05 dB) of a synthetic 3-band target; RMS < 0.3 dB fitting a 10-band target with deliberately overlapping bands using bands: 10; RMS ≤ 0.5 dB / max ≤ 1.5 dB on a synthetic Harman-tilt headphone curve; the fitted bands run through @audio/eq-parametric and measured back via FFT of the impulse response match response(f) within 0.1 dB; fitting 10 bands on the default 256-point grid takes ~90 ms.

References

  • Bristow-Johnson, R. (2005). "Audio EQ Cookbook." — biquad coefficients, via @audio/biquad.
  • Marquardt, D.W. (1963). "An Algorithm for Least-Squares Estimation of Nonlinear Parameters." J. Soc. Indust. Appl. Math. 11(2):431–441.
  • Nocedal, J. & Wright, S. (2006). Numerical Optimization (2nd ed.), §10.3 — Levenberg–Marquardt as damped Gauss–Newton.
  • jaakkopasanen/AutoEq — the ParametricEQ.txt text format and the headphone-correction use case this module targets; test.js parses a real result file from AutoEQ's own archive.
  • Equalizer APO — the ParametricEQ.txt config syntax toEqualizerApo/fromEqualizerApo read and write.

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

MIT © audiojs