@audio/eq-fir 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/fir.js +66 -0
  2. package/package.json +31 -0
package/fir.js ADDED
@@ -0,0 +1,66 @@
1
+ // FIR convolution EQ — linear-phase filter from an arbitrary magnitude response.
2
+ // Frequency-sampling design (odd-length type-I symmetric FIR, Hann-windowed against Gibbs),
3
+ // zero-latency apply (group delay compensated).
4
+ // FFmpeg firequalizer class; the Matchering match-EQ substrate.
5
+
6
+ /**
7
+ * Design linear-phase FIR coefficients from a magnitude response.
8
+ * @param {Array<{f:number,gain:number}>|function} response — dB gain points (linearly
9
+ * interpolated over frequency, clamped outside) or `f => dB` function
10
+ * @param {object} opts — { taps=511 (made odd), fs=44100 }
11
+ * @returns {Float64Array} symmetric coefficients
12
+ */
13
+ export function design (response, { taps = 511, fs = 44100 } = {}) {
14
+ let N = taps | 1 // force odd (type-I)
15
+ let dbAt
16
+ if (typeof response === 'function') dbAt = response
17
+ else {
18
+ let pts = [...response].sort((a, b) => a.f - b.f)
19
+ dbAt = f => {
20
+ if (f <= pts[0].f) return pts[0].gain
21
+ if (f >= pts[pts.length - 1].f) return pts[pts.length - 1].gain
22
+ for (let i = 1; i < pts.length; i++) {
23
+ if (f <= pts[i].f) {
24
+ let t = (f - pts[i - 1].f) / (pts[i].f - pts[i - 1].f)
25
+ return pts[i - 1].gain + t * (pts[i].gain - pts[i - 1].gain)
26
+ }
27
+ }
28
+ return pts[pts.length - 1].gain
29
+ }
30
+ }
31
+
32
+ let half = (N - 1) / 2
33
+ let M = new Float64Array(half + 1)
34
+ for (let k = 0; k <= half; k++) M[k] = 10 ** (dbAt(k * fs / N) / 20)
35
+
36
+ // h[n] = (1/N)·[M0 + 2·Σ M_k·cos(2πk(n−α)/N)], α = (N−1)/2 → symmetric, linear phase
37
+ let h = new Float64Array(N)
38
+ for (let n = 0; n < N; n++) {
39
+ let sum = M[0]
40
+ for (let k = 1; k <= half; k++) sum += 2 * M[k] * Math.cos(2 * Math.PI * k * (n - half) / N)
41
+ let win = 0.5 - 0.5 * Math.cos(2 * Math.PI * n / (N - 1)) // Hann against Gibbs ripple
42
+ h[n] = (sum / N) * win
43
+ }
44
+ return h
45
+ }
46
+
47
+ /**
48
+ * Apply the EQ in place, delay-compensated (output time-aligned with input).
49
+ * @param {Float32Array} data — mono PCM
50
+ * @param {object} opts — { response, taps, fs } or precomputed { coefs }
51
+ */
52
+ export default function firEq (data, opts = {}) {
53
+ let h = opts.coefs || design(opts.response, opts)
54
+ let N = h.length, half = (N - 1) / 2
55
+ let out = new Float32Array(data.length)
56
+ for (let i = 0; i < data.length; i++) {
57
+ let sum = 0
58
+ for (let j = 0; j < N; j++) {
59
+ let idx = i - j + half
60
+ if (idx >= 0 && idx < data.length) sum += h[j] * data[idx]
61
+ }
62
+ out[i] = sum
63
+ }
64
+ data.set(out)
65
+ return data
66
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@audio/eq-fir",
3
+ "version": "1.0.0",
4
+ "description": "FIR convolution EQ — linear-phase, arbitrary magnitude response (frequency-sampling design; firequalizer / match-EQ class)",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "fir.js",
8
+ "exports": {
9
+ ".": "./fir.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": [
13
+ "fir.js"
14
+ ],
15
+ "keywords": [
16
+ "audio",
17
+ "dsp",
18
+ "eq",
19
+ "fir",
20
+ "linear-phase",
21
+ "firequalizer"
22
+ ],
23
+ "license": "MIT",
24
+ "author": "Dmitry Iv. <dfcreative@gmail.com>",
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ }
31
+ }