@audio/filter-korg35 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.
- package/korg35.js +72 -0
- package/package.json +38 -0
package/korg35.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Korg MS-20 style filter (Korg35).
|
|
3
|
+
* Zero-delay feedback (ZDF) via trapezoidal integration.
|
|
4
|
+
*
|
|
5
|
+
* Ref: Zavalishin, "The Art of VA Filter Design" (2012), Ch. 5.
|
|
6
|
+
* Stilson & Smith, "Analyzing the Korg MS-20 Filter" (1996).
|
|
7
|
+
*
|
|
8
|
+
* The Korg35 is a 2-pole filter with nonlinear feedback. Unlike SVF, it uses two
|
|
9
|
+
* cascaded one-pole sections with the output fed back to the input. The nonlinear
|
|
10
|
+
* saturation in the feedback path gives the aggressive MS-20 character.
|
|
11
|
+
*
|
|
12
|
+
* Lowpass: output from second stage.
|
|
13
|
+
* Highpass: input minus lowpass output — exactly complementary only at
|
|
14
|
+
* resonance=0; the feedback term in the HP tap makes LP+HP diverge from the
|
|
15
|
+
* input as resonance increases (see readme).
|
|
16
|
+
*
|
|
17
|
+
* Only 2 real poles feed the resonance loop (vs. moog-ladder's 4), so the
|
|
18
|
+
* loop phase never reaches -180° at any audible, non-negligible-gain
|
|
19
|
+
* frequency: resonance adds damping/saturation character but no resonant
|
|
20
|
+
* peak and no sustained self-oscillation at any resonance setting (see readme).
|
|
21
|
+
*
|
|
22
|
+
* @module audio-filter/analog/korg35
|
|
23
|
+
* @param {Float32Array|Float64Array} data - audio buffer (modified in place)
|
|
24
|
+
* @param {Object} params
|
|
25
|
+
* @param {number} [params.fc=1000] - cutoff frequency Hz
|
|
26
|
+
* @param {number} [params.resonance=0] - resonance 0–1 (aggressive damping/saturation; no self-oscillation — see readme)
|
|
27
|
+
* @param {number} [params.fs=44100] - sample rate
|
|
28
|
+
* @param {number} [params.drive=1] - input drive (saturation amount)
|
|
29
|
+
* @param {string} [params.type='lowpass'] - 'lowpass' or 'highpass'
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
let {tan, tanh, PI, min} = Math
|
|
33
|
+
|
|
34
|
+
export default function korg35 (data, params) {
|
|
35
|
+
let fc = params.fc || 1000
|
|
36
|
+
let res = params.resonance != null ? params.resonance : 0
|
|
37
|
+
let fs = params.fs || 44100
|
|
38
|
+
let drive = params.drive ?? 1
|
|
39
|
+
let hp = params.type === 'highpass'
|
|
40
|
+
|
|
41
|
+
// Trapezoidal integrator coefficient
|
|
42
|
+
let g = tan(PI * min(fc, fs * 0.49) / fs)
|
|
43
|
+
let G = g / (1 + g) // one-pole gain
|
|
44
|
+
let G2 = G * G // two-pole gain
|
|
45
|
+
let k = res * 2 // feedback coefficient: 0–2
|
|
46
|
+
|
|
47
|
+
// State: 2 one-pole integrator states
|
|
48
|
+
if (!params._s) params._s = new Float64Array(2)
|
|
49
|
+
let s = params._s
|
|
50
|
+
|
|
51
|
+
for (let i = 0, n = data.length; i < n; i++) {
|
|
52
|
+
// Estimate 2-pole output from state (zero-input cascade response)
|
|
53
|
+
let S = G * s[0] + s[1]
|
|
54
|
+
|
|
55
|
+
// Implicit feedback solve
|
|
56
|
+
let u = (data[i] - k * S) / (1 + k * G2)
|
|
57
|
+
|
|
58
|
+
// Nonlinear saturation in feedback path (MS-20 character)
|
|
59
|
+
u = tanh(u * drive)
|
|
60
|
+
|
|
61
|
+
// Two cascaded trapezoidal one-pole lowpass stages
|
|
62
|
+
let y1 = G * (u - s[0]) + s[0]
|
|
63
|
+
s[0] = 2 * y1 - s[0]
|
|
64
|
+
|
|
65
|
+
let y2 = G * (y1 - s[1]) + s[1]
|
|
66
|
+
s[1] = 2 * y2 - s[1]
|
|
67
|
+
|
|
68
|
+
data[i] = hp ? data[i] - y2 - k * y2 : y2
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return data
|
|
72
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@audio/filter-korg35",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Korg MS-20 style filter (Korg35)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "korg35.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./korg35.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"korg35.js"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"audio",
|
|
17
|
+
"dsp",
|
|
18
|
+
"filter",
|
|
19
|
+
"korg35"
|
|
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-korg35"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/audiojs/filter/tree/main/packages/filter-korg35",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/audiojs/filter/issues"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
}
|
|
38
|
+
}
|