@audio/effect-graindelay 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 (3) hide show
  1. package/README.md +5 -0
  2. package/graindelay.js +55 -0
  3. package/package.json +29 -0
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @audio/effect-graindelay
2
+
3
+ > Granular delay — per-grain pitch/time scatter (Ableton Grain Delay class)
4
+
5
+ Planned — not implemented yet. See the umbrella README for status and sources.
package/graindelay.js ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Granular delay — grains read from the delay line with per-grain delay scatter and
3
+ * pitch transposition (Ableton Grain Delay class). Two staggered Hann-windowed heads
4
+ * retrigger every half grain; feedback writes the wet grain stream back into the line.
5
+ */
6
+
7
+ let { sin, cos, PI } = Math
8
+
9
+ export default function graindelay (data, params) {
10
+ let time = params.time ?? 0.25 // base delay, s
11
+ let spray = params.spray ?? 0.02 // random extra delay per grain, s
12
+ let pitch = params.pitch ?? 0 // semitones, per-grain transposition
13
+ let jitter = params.jitter ?? 0 // random pitch scatter, semitones
14
+ let grain = params.grain ?? 0.08 // grain length, s
15
+ let feedback = params.feedback ?? 0.3
16
+ let mix = params.mix ?? 0.5
17
+ let fs = params.fs || 44100
18
+
19
+ let gN = Math.max(32, (grain * fs) | 0)
20
+ let size = Math.ceil((time + spray + grain) * fs * 2 ** Math.max(0, (pitch + jitter) / 12)) + gN + 2
21
+ if (!params._buf || params._buf.length < size) {
22
+ params._buf = new Float64Array(size)
23
+ params._w = 0
24
+ params._rnd = params.seed ?? 0x9e3779b9
25
+ // two heads, staggered by half a grain
26
+ params._heads = [{ n: 0 }, { n: -(gN >> 1) }].map(h => ({ ...h, start: 0, off: time * fs, rate: 1 }))
27
+ }
28
+ let buf = params._buf, w = params._w, heads = params._heads
29
+ let N = buf.length
30
+ let rnd = () => ((params._rnd = (params._rnd * 1664525 + 1013904223) >>> 0) / 4294967296)
31
+
32
+ for (let i = 0, l = data.length; i < l; i++) {
33
+ let x = data[i]
34
+ let wet = 0
35
+ for (let h of heads) {
36
+ if (h.n < 0) { h.n++; continue } // stagger warmup
37
+ if (h.n === 0) { // retrigger: new scatter + rate
38
+ h.start = w
39
+ h.off = (time + spray * rnd()) * fs
40
+ h.rate = 2 ** ((pitch + jitter * (rnd() * 2 - 1)) / 12)
41
+ }
42
+ let pos = h.start - h.off + h.n * h.rate
43
+ let base = Math.floor(pos), frac = pos - base
44
+ let a = buf[((base % N) + N) % N], b = buf[(((base + 1) % N) + N) % N]
45
+ let win = 0.5 - 0.5 * cos(2 * PI * h.n / gN)
46
+ wet += (a + frac * (b - a)) * win
47
+ if (++h.n >= gN) h.n = 0 // grain done → retrigger next sample
48
+ }
49
+ buf[w] = x + feedback * wet
50
+ w = (w + 1) % N
51
+ data[i] = x * (1 - mix) + wet * mix
52
+ }
53
+ params._w = w
54
+ return data
55
+ }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@audio/effect-graindelay",
3
+ "version": "1.0.0",
4
+ "description": "Granular delay — per-grain pitch/time scatter (Ableton Grain Delay class)",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "keywords": [
8
+ "audio",
9
+ "dsp",
10
+ "effect",
11
+ "graindelay"
12
+ ],
13
+ "license": "MIT",
14
+ "author": "Dmitry Iv. <dfcreative@gmail.com>",
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "main": "graindelay.js",
19
+ "exports": {
20
+ ".": "./graindelay.js",
21
+ "./package.json": "./package.json"
22
+ },
23
+ "files": [
24
+ "graindelay.js"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public"
28
+ }
29
+ }