@audio/filter-moog-ladder 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/moog-ladder.js +61 -0
  2. package/package.json +38 -0
package/moog-ladder.js ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Moog 4-pole transistor ladder filter (-24 dB/oct lowpass with resonance).
3
+ * Zero-delay feedback (ZDF) via trapezoidal integration.
4
+ *
5
+ * Ref: Zavalishin, "The Art of VA Filter Design" (2012), Ch. 6.
6
+ * Välimäki & Smith, "Discrete-Time Synthesis of the Moog VCF" (2006).
7
+ *
8
+ * 4 cascaded one-pole trapezoidal integrators with implicit feedback solving.
9
+ * No unit delay in feedback path — resonance tracks cutoff accurately to Nyquist.
10
+ *
11
+ * @module audio-filter/analog/moog-ladder
12
+ * @param {Float32Array|Float64Array} data - audio buffer (modified in place)
13
+ * @param {Object} params
14
+ * @param {number} [params.fc=1000] - cutoff frequency Hz
15
+ * @param {number} [params.resonance=0] - resonance 0–1 (self-oscillation at 1)
16
+ * @param {number} [params.fs=44100] - sample rate
17
+ * @param {number} [params.drive=1] - input drive (saturation amount)
18
+ */
19
+
20
+ let {tan, tanh, PI, min} = Math
21
+
22
+ export default function moogLadder (data, params) {
23
+ let fc = params.fc || 1000
24
+ let res = params.resonance != null ? params.resonance : 0
25
+ let fs = params.fs || 44100
26
+ let drive = params.drive ?? 1
27
+
28
+ // Trapezoidal integrator coefficient
29
+ let g = tan(PI * min(fc, fs * 0.49) / fs)
30
+ let G = g / (1 + g) // one-pole gain factor
31
+ let G2 = G * G, G3 = G2 * G, G4 = G3 * G
32
+ let k = res * 4 // feedback coefficient: 0–4, self-oscillation at 4
33
+
34
+ // State: 4 one-pole integrator states
35
+ if (!params._s) params._s = new Float64Array(4)
36
+ let s = params._s
37
+
38
+ for (let i = 0, n = data.length; i < n; i++) {
39
+ // Estimate output from current state (zero-input response of cascade)
40
+ // S = weighted sum of integrator states propagated through the cascade
41
+ let S = G3 * s[0] + G2 * s[1] + G * s[2] + s[3]
42
+
43
+ // Implicit feedback solve: u*(1 + k*G^4) = input - k*S
44
+ let u = (data[i] - k * S) / (1 + k * G4)
45
+
46
+ // Nonlinear saturation at input (transistor ladder characteristic)
47
+ u = tanh(u * drive)
48
+
49
+ // 4 cascaded trapezoidal one-pole lowpass stages
50
+ let v = u
51
+ for (let j = 0; j < 4; j++) {
52
+ let y = G * (v - s[j]) + s[j] // trapezoidal integrator output
53
+ s[j] = 2 * y - s[j] // state update (2y - s for trapezoidal rule)
54
+ v = y
55
+ }
56
+
57
+ data[i] = v
58
+ }
59
+
60
+ return data
61
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@audio/filter-moog-ladder",
3
+ "version": "1.0.0",
4
+ "description": "Moog 4-pole transistor ladder filter (-24 dB/oct lowpass with resonance)",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "moog-ladder.js",
8
+ "exports": {
9
+ ".": "./moog-ladder.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": [
13
+ "moog-ladder.js"
14
+ ],
15
+ "keywords": [
16
+ "audio",
17
+ "dsp",
18
+ "filter",
19
+ "moog-ladder"
20
+ ],
21
+ "license": "MIT",
22
+ "author": "Dmitry Iv. <dfcreative@gmail.com>",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/audiojs/filter.git",
26
+ "directory": "packages/filter-moog-ladder"
27
+ },
28
+ "homepage": "https://github.com/audiojs/filter/tree/main/packages/filter-moog-ladder",
29
+ "bugs": {
30
+ "url": "https://github.com/audiojs/filter/issues"
31
+ },
32
+ "engines": {
33
+ "node": ">=18"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ }
38
+ }