@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.
Files changed (2) hide show
  1. package/package.json +29 -0
  2. 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
+ }