@audio/reverb-dattorro 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/dattorro.js +92 -0
- package/package.json +29 -0
package/dattorro.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Dattorro plate reverb — input diffusion (4 allpasses) into a figure-eight tank of two
|
|
2
|
+
// cross-coupled branches (allpass → delay → damping → decay → allpass → delay).
|
|
3
|
+
// Modulation omitted (static tank). Structure, delay lengths and output taps from Dattorro, "Effect Design Part 1:
|
|
4
|
+
// Reverberator and Other Filters", JAES 45(9), 1997 (reference rate 29761 Hz, scaled to fs).
|
|
5
|
+
|
|
6
|
+
const REF = 29761
|
|
7
|
+
|
|
8
|
+
class Delay {
|
|
9
|
+
constructor (n) { this.buf = new Float64Array(Math.max(1, n)); this.i = 0 }
|
|
10
|
+
read (tap) { let j = this.i - tap; j %= this.buf.length; if (j < 0) j += this.buf.length; return this.buf[j] }
|
|
11
|
+
step (x) { this.buf[this.i] = x; if (++this.i >= this.buf.length) this.i = 0 }
|
|
12
|
+
tail () { return this.read(this.buf.length - 1) }
|
|
13
|
+
}
|
|
14
|
+
class Allpass {
|
|
15
|
+
constructor (n, g) { this.d = new Delay(n); this.g = g }
|
|
16
|
+
step (x) {
|
|
17
|
+
let z = this.d.read(this.d.buf.length - 1)
|
|
18
|
+
let y = x + z * -this.g
|
|
19
|
+
this.d.step(y)
|
|
20
|
+
return z + y * this.g
|
|
21
|
+
}
|
|
22
|
+
read (tap) { return this.d.read(tap) }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {Float32Array|Float32Array[]} data — mono buffer or [L, R], processed in place
|
|
27
|
+
* @param {object} opts — { decay=0.5 (0..1), damping=0.3 (0..1), bandwidth=0.9995,
|
|
28
|
+
* mix=0.3, fs=44100 }
|
|
29
|
+
*/
|
|
30
|
+
export default function dattorro (data, { decay = 0.5, damping = 0.3, bandwidth = 0.9995, mix = 0.3, fs = 44100 } = {}) {
|
|
31
|
+
let sc = fs / REF
|
|
32
|
+
let S = n => Math.max(1, Math.round(n * sc))
|
|
33
|
+
|
|
34
|
+
// input chain
|
|
35
|
+
let lpIn = 0
|
|
36
|
+
let inAp = [new Allpass(S(142), 0.75), new Allpass(S(107), 0.75), new Allpass(S(379), 0.625), new Allpass(S(277), 0.625)]
|
|
37
|
+
// tank branch 1
|
|
38
|
+
let ap1 = new Allpass(S(672), 0.7)
|
|
39
|
+
let del1a = new Delay(S(4453))
|
|
40
|
+
let lp1 = 0
|
|
41
|
+
let ap1b = new Allpass(S(1800), 0.5)
|
|
42
|
+
let del1b = new Delay(S(3720))
|
|
43
|
+
// tank branch 2
|
|
44
|
+
let ap2 = new Allpass(S(908), 0.7)
|
|
45
|
+
let del2a = new Delay(S(4217))
|
|
46
|
+
let lp2 = 0
|
|
47
|
+
let ap2b = new Allpass(S(2656), 0.5)
|
|
48
|
+
let del2b = new Delay(S(3163))
|
|
49
|
+
|
|
50
|
+
let stereo = data[0]?.length !== undefined
|
|
51
|
+
let L = stereo ? data[0] : data
|
|
52
|
+
let R = stereo ? data[1] : null
|
|
53
|
+
let n = L.length
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < n; i++) {
|
|
56
|
+
let dry = stereo ? (L[i] + R[i]) * 0.5 : L[i]
|
|
57
|
+
|
|
58
|
+
lpIn = dry * bandwidth + lpIn * (1 - bandwidth)
|
|
59
|
+
let x = lpIn
|
|
60
|
+
for (let ap of inAp) x = ap.step(x)
|
|
61
|
+
|
|
62
|
+
// figure-eight: each branch is fed by the other branch's end
|
|
63
|
+
let in1 = x + del2b.tail() * decay
|
|
64
|
+
let in2 = x + del1b.tail() * decay
|
|
65
|
+
|
|
66
|
+
let a = ap1.step(in1)
|
|
67
|
+
del1a.step(a)
|
|
68
|
+
lp1 = del1a.tail() * (1 - damping) + lp1 * damping
|
|
69
|
+
let b = ap1b.step(lp1 * decay)
|
|
70
|
+
del1b.step(b)
|
|
71
|
+
|
|
72
|
+
let c = ap2.step(in2)
|
|
73
|
+
del2a.step(c)
|
|
74
|
+
lp2 = del2a.tail() * (1 - damping) + lp2 * damping
|
|
75
|
+
let d = ap2b.step(lp2 * decay)
|
|
76
|
+
del2b.step(d)
|
|
77
|
+
|
|
78
|
+
// output taps (Dattorro Table 2), scaled
|
|
79
|
+
let yL = 0.6 * (del2a.read(S(266)) + del2a.read(S(2974)) - ap2b.read(S(1913)) + del2b.read(S(1996))
|
|
80
|
+
- del1a.read(S(1990)) - ap1b.read(S(187)) - del1b.read(S(1066)))
|
|
81
|
+
let yR = 0.6 * (del1a.read(S(353)) + del1a.read(S(3627)) - ap1b.read(S(1228)) + del1b.read(S(2673))
|
|
82
|
+
- del2a.read(S(2111)) - ap2b.read(S(335)) - del2b.read(S(121)))
|
|
83
|
+
|
|
84
|
+
if (stereo) {
|
|
85
|
+
L[i] = L[i] * (1 - mix) + yL * mix
|
|
86
|
+
R[i] = R[i] * (1 - mix) + yR * mix
|
|
87
|
+
} else {
|
|
88
|
+
L[i] = L[i] * (1 - mix) + (yL + yR) * 0.5 * mix
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return data
|
|
92
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@audio/reverb-dattorro",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Dattorro plate reverb — input diffusers + figure-eight tank (JAES 1997 topology and taps)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "dattorro.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./dattorro.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dattorro.js"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"audio",
|
|
17
|
+
"dsp",
|
|
18
|
+
"reverb",
|
|
19
|
+
"dattorro"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"author": "Dmitry Iv. <dfcreative@gmail.com>",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
}
|
|
29
|
+
}
|