@audio/reverb-shimmer 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/package.json +29 -0
- package/shimmer.js +54 -0
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@audio/reverb-shimmer",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shimmer reverb — octave-up pitch-shifted feedback tail (Valhalla/Eno-Lanois class)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "shimmer.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./shimmer.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"shimmer.js"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"audio",
|
|
17
|
+
"dsp",
|
|
18
|
+
"reverb",
|
|
19
|
+
"shimmer"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"author": "Dmitry Iv. <dfcreative@gmail.com>",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/shimmer.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Shimmer reverb — a reverb core whose feedback path is pitch-shifted up an octave,
|
|
2
|
+
// so the tail climbs into sparkling upper octaves (Valhalla Shimmer / Eno-Lanois class).
|
|
3
|
+
// The in-loop shifter is a 2-head varispeed ring buffer (×2 read rate, crossfaded grains) —
|
|
4
|
+
// loop-friendly; offline high-quality shifting lives in @audio/shift-*.
|
|
5
|
+
|
|
6
|
+
const COMBS = [1557, 1617, 1491, 1422]
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {Float32Array} data — mono PCM, processed in place
|
|
10
|
+
* @param {object} opts — { decay=0.7 (0..1), shimmer=0.5 (0..1 octave-up feedback amount),
|
|
11
|
+
* damping=0.3, mix=0.4, fs=44100 }
|
|
12
|
+
*/
|
|
13
|
+
export default function shimmer (data, { decay = 0.7, shimmer = 0.5, damping = 0.3, mix = 0.4, fs = 44100 } = {}) {
|
|
14
|
+
let sc = fs / 44100
|
|
15
|
+
let combs = COMBS.map(d => ({ buf: new Float64Array(Math.max(1, Math.round(d * sc))), i: 0, lp: 0 }))
|
|
16
|
+
let fb = 0.5 + 0.45 * decay
|
|
17
|
+
|
|
18
|
+
// octave-up varispeed shifter: write head advances 1, two read heads advance 2
|
|
19
|
+
let N = Math.max(64, Math.round(4096 * sc))
|
|
20
|
+
let ring = new Float64Array(N)
|
|
21
|
+
let w = 0, r = 0 // r = fractional read position (advances by 2)
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < data.length; i++) {
|
|
24
|
+
// previous wet sum
|
|
25
|
+
let wet = 0
|
|
26
|
+
for (let c of combs) wet += c.buf[c.i]
|
|
27
|
+
wet /= combs.length
|
|
28
|
+
|
|
29
|
+
// octave-up copy of the wet signal
|
|
30
|
+
ring[w] = wet
|
|
31
|
+
let head = (pos) => {
|
|
32
|
+
let p = pos % N; if (p < 0) p += N
|
|
33
|
+
let i0 = p | 0, frac = p - i0
|
|
34
|
+
return ring[i0] * (1 - frac) + ring[(i0 + 1) % N] * frac
|
|
35
|
+
}
|
|
36
|
+
// two half-window-offset read heads with triangular crossfade
|
|
37
|
+
let phase = (r % (N / 2)) / (N / 2)
|
|
38
|
+
let g1 = 1 - Math.abs(2 * phase - 1)
|
|
39
|
+
let up = head(r) * g1 + head(r + N / 2) * (1 - g1)
|
|
40
|
+
r = (r + 2) % N
|
|
41
|
+
w = (w + 1) % N
|
|
42
|
+
|
|
43
|
+
let inject = data[i] * 0.7 + up * shimmer
|
|
44
|
+
for (let c of combs) {
|
|
45
|
+
let y = c.buf[c.i]
|
|
46
|
+
c.lp = y * (1 - damping) + c.lp * damping
|
|
47
|
+
c.buf[c.i] = inject + c.lp * fb
|
|
48
|
+
if (++c.i >= c.buf.length) c.i = 0
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
data[i] = data[i] * (1 - mix) + wet * mix
|
|
52
|
+
}
|
|
53
|
+
return data
|
|
54
|
+
}
|