{"_id":"@audio/eq-fit","name":"@audio/eq-fit","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@audio/eq-fit","version":"0.1.0","description":"Fit an N-band parametric EQ (RBJ peaking + shelves) to a target magnitude curve — greedy init + Levenberg–Marquardt refinement","type":"module","sideEffects":false,"main":"fit.js","types":"./fit.d.ts","exports":{".":{"types":"./fit.d.ts","default":"./fit.js"},"./package.json":"./package.json"},"dependencies":{"@audio/biquad":"^1.2.1"},"devDependencies":{"@audio/eq-parametric":"^1.1.1","fourier-transform":"^2.4.1","tst":"^9.4.0"},"scripts":{"test":"node test.js"},"keywords":["audio","dsp","eq","equalizer","parametric","fit","autoeq","levenberg-marquardt"],"license":"MIT","author":{"name":"Dmitry Iv.","email":"dfcreative@gmail.com"},"engines":{"node":">=18"},"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/audiojs/eq.git","directory":"packages/eq-fit"},"funding":"https://github.com/sponsors/audiojs","gitHead":"2fbb65959d53cd174cf5e994065afb2754fb6375","_id":"@audio/eq-fit@0.1.0","bugs":{"url":"https://github.com/audiojs/eq/issues"},"homepage":"https://github.com/audiojs/eq#readme","_nodeVersion":"25.9.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-QKWTGjzbgvpbSZe/TElD+6bmK3kdpClkKXnHtno7B0bKKQeMvU1y94f+5WDunE/LOA8YM8yVTFP8yF0ZofL+nQ==","shasum":"67acac49c7ee980eed44bae3a6d47b10dad2ec26","tarball":"https://registry.npmjs.org/@audio/eq-fit/-/eq-fit-0.1.0.tgz","fileCount":5,"unpackedSize":34997,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCoGKZgXB6dzF8Vjpa0bvIHHyIUlJk558ZCu4TITZHySwIhAMd+7j7RcucDcxoJVc9Ly2/StR3vPWvrAdJCvq6i6qF9"}]},"_npmUser":{"name":"dy","email":"df.creative@gmail.com"},"directories":{},"maintainers":[{"name":"jamen","email":"jamenmarz@gmail.com"},{"name":"dfcreative","email":"df.creative@gmail.com"},{"name":"dy","email":"df.creative@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/eq-fit_0.1.0_1787959372482_0.1308576331588427"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-28T23:22:52.297Z","0.1.0":"2026-08-28T23:22:52.602Z","modified":"2026-08-28T23:22:52.859Z"},"maintainers":[{"name":"jamen","email":"jamenmarz@gmail.com"},{"name":"dfcreative","email":"df.creative@gmail.com"},{"name":"dy","email":"df.creative@gmail.com"}],"description":"Fit an N-band parametric EQ (RBJ peaking + shelves) to a target magnitude curve — greedy init + Levenberg–Marquardt refinement","homepage":"https://github.com/audiojs/eq#readme","keywords":["audio","dsp","eq","equalizer","parametric","fit","autoeq","levenberg-marquardt"],"repository":{"type":"git","url":"git+https://github.com/audiojs/eq.git","directory":"packages/eq-fit"},"author":{"name":"Dmitry Iv.","email":"dfcreative@gmail.com"},"bugs":{"url":"https://github.com/audiojs/eq/issues"},"license":"MIT","readme":"# @audio/eq-fit [![npm](https://img.shields.io/npm/v/@audio/eq-fit)](https://www.npmjs.com/package/@audio/eq-fit) [![MIT](https://img.shields.io/badge/MIT-%E0%A5%90-white)](https://github.com/krishnized/license)\n\nFit an N-band parametric EQ to a target magnitude curve\n\n```\nnpm install @audio/eq-fit\n```\n\n```js\nimport fit from '@audio/eq-fit'\n```\n\nThe 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](https://github.com/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.\n\n```js\n// from control points\nlet r = fit([{ f: 100, gain: 4 }, { f: 3000, gain: -3 }, { f: 8000, gain: 2 }], { bands: 6 })\nr.bands     // [{ type: 'peak', fc, Q, gain }, ...] — ready for @audio/eq-parametric's params.bands\nr.preamp    // dB, ≤ 0 — headroom so the fitted chain never clips\nr.error     // weighted RMS dB error of the fit\nr.response  // f => dB, the fitted cascade's response incl. preamp\n\n// from a function\nfit(f => (f > 5000 ? -6 : 0), { bands: 4, shelves: true })\n\n// from a @audio/measure-response result\nimport measureResponse from '@audio/measure-response'\nfit(measureResponse(impulseResponse, { fs }), { fs })\n\n// apply it\nimport parametricEq from '@audio/eq-parametric'\nparametricEq(data, { bands: r.bands, fs: 44100 })\n\n// interchange with AutoEQ / Equalizer APO\nimport { toEqualizerApo, fromEqualizerApo } from '@audio/eq-fit'\ntoEqualizerApo(r)             // \"Preamp: -2.1 dB\\nFilter 1: ON PK Fc 100 Hz Gain 4.0 dB Q 1.41\\n…\"\nfromEqualizerApo(apoText)     // { bands, preamp } — reads AutoEQ's own ParametricEQ.txt files too\n```\n\n| Param | Default | |\n|---|---|---|\n| `fs` | `44100` | Sample rate, Hz |\n| `bands` | `8` | Max band count — fewer are returned once `tolerance` is met |\n| `fMin` / `fMax` | `20` / `20000` | Fitted frequency range, Hz |\n| `shelves` | `true` | Allow one low-shelf + one high-shelf at the range's edges |\n| `maxGain` | `12` | Max \\|gain\\| per band, dB |\n| `minQ` / `maxQ` | `0.3` / `10` | Per-band Q bounds |\n| `tolerance` | `0.5` | Stop the greedy init once weighted RMS error is ≤ this, dB |\n| `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 |\n| `grid` | `256` | Log-spaced evaluation points |\n| `iterations` | `200` | Levenberg–Marquardt iteration cap (stops earlier on convergence) |\n| `preamp` | `true` | `false` → skip the headroom calc, `result.preamp` is always `0` |\n\n`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.\n\n`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.\n\n**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`).\n\n## Algorithm\n\n1. **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`.\n2. **Refine** — joint nonlinear least squares over every band's `(ln fc, gain, ln Q)` at once: Levenberg–Marquardt ([Marquardt 1963](https://doi.org/10.1137/0111030), 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](https://doi.org/10.1007/978-0-387-40065-5)).\n3. Bands with \\|gain\\| < 0.1 dB are dropped, the rest sorted by `fc`.\n\nMeasured 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.\n\n## References\n\n- Bristow-Johnson, R. (2005). [\"Audio EQ Cookbook.\"](https://www.w3.org/TR/audio-eq-cookbook/) — biquad coefficients, via `@audio/biquad`.\n- Marquardt, D.W. (1963). [\"An Algorithm for Least-Squares Estimation of Nonlinear Parameters.\"](https://doi.org/10.1137/0111030) *J. Soc. Indust. Appl. Math.* 11(2):431–441.\n- Nocedal, J. & Wright, S. (2006). *Numerical Optimization* (2nd ed.), §10.3 — Levenberg–Marquardt as damped Gauss–Newton.\n- [jaakkopasanen/AutoEq](https://github.com/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.\n- [Equalizer APO](https://sourceforge.net/projects/equalizerapo/) — the `ParametricEQ.txt` config syntax `toEqualizerApo`/`fromEqualizerApo` read and write.\n\n---\n\nPart of [@audio/eq](https://github.com/audiojs/eq) — the eq family umbrella.\n\nMIT © [audiojs](https://github.com/audiojs)\n","readmeFilename":"README.md","_rev":"1-89c7a807f7226e49ac04193933c8c531"}