@audio/reverb-convolution 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/convolution.js +82 -0
  2. package/package.json +32 -0
package/convolution.js ADDED
@@ -0,0 +1,82 @@
1
+ // Convolution reverb — convolve with an impulse response (room, plate, spring, cabinet…).
2
+ // Small IRs run direct-form; long IRs run uniform-partitioned FFT overlap-add with a
3
+ // frequency-domain delay line (Wefers/Gardner class). Both paths are differential-tested
4
+ // against each other. Wet tail truncated at the input length.
5
+
6
+ import { fft, ifft } from 'fourier-transform'
7
+
8
+ const PART = 2048 // partition size; FFT size = 2·PART
9
+ const DIRECT_MAX = 1024 // direct-form below this IR length
10
+
11
+ function direct (data, h) {
12
+ let out = new Float64Array(data.length)
13
+ for (let j = 0; j < h.length; j++) {
14
+ let k = h[j]
15
+ if (k === 0) continue
16
+ for (let i = j; i < data.length; i++) out[i] += k * data[i - j]
17
+ }
18
+ return out
19
+ }
20
+
21
+ function partitioned (data, h) {
22
+ let nParts = Math.ceil(h.length / PART)
23
+ let F = 2 * PART
24
+ // IR partition spectra
25
+ let H = []
26
+ let seg = new Float64Array(F)
27
+ for (let p = 0; p < nParts; p++) {
28
+ seg.fill(0)
29
+ for (let i = 0; i < PART; i++) seg[i] = h[p * PART + i] || 0
30
+ let [re, im] = fft(seg)
31
+ H.push([Float64Array.from(re), Float64Array.from(im)])
32
+ }
33
+ // frequency-domain delay line of past input spectra
34
+ let fdl = Array.from({ length: nParts }, () => [new Float64Array(PART + 1), new Float64Array(PART + 1)])
35
+ let fdlPos = 0
36
+ let out = new Float64Array(data.length + F)
37
+ let yRe = new Float64Array(PART + 1), yIm = new Float64Array(PART + 1)
38
+ let block = new Float64Array(F)
39
+
40
+ for (let pos = 0; pos < data.length; pos += PART) {
41
+ block.fill(0)
42
+ for (let i = 0; i < PART && pos + i < data.length; i++) block[i] = data[pos + i]
43
+ let [re, im] = fft(block)
44
+ fdl[fdlPos][0].set(re.subarray(0, PART + 1))
45
+ fdl[fdlPos][1].set(im.subarray(0, PART + 1))
46
+
47
+ yRe.fill(0); yIm.fill(0)
48
+ for (let p = 0; p < nParts; p++) {
49
+ let [xr, xi] = fdl[(fdlPos - p + nParts) % nParts]
50
+ let [hr, hi] = H[p]
51
+ for (let k = 0; k <= PART; k++) {
52
+ yRe[k] += xr[k] * hr[k] - xi[k] * hi[k]
53
+ yIm[k] += xr[k] * hi[k] + xi[k] * hr[k]
54
+ }
55
+ }
56
+ let y = ifft(yRe, yIm)
57
+ for (let i = 0; i < F && pos + i < out.length; i++) out[pos + i] += y[i]
58
+ fdlPos = (fdlPos + 1) % nParts
59
+ }
60
+ return out.subarray(0, data.length)
61
+ }
62
+
63
+ /**
64
+ * @param {Float32Array} data — mono PCM, processed in place
65
+ * @param {object} opts — { ir: Float32Array, mix=1, normalize=false,
66
+ * method: 'auto'|'direct'|'fft' }
67
+ * normalize scales the IR to unit energy so output level ≈ input level
68
+ */
69
+ export default function convolve (data, { ir, mix = 1, normalize = false, method = 'auto' } = {}) {
70
+ if (!ir || !ir.length) throw new RangeError('convolve: opts.ir required')
71
+ let h = ir
72
+ if (normalize) {
73
+ let e = 0
74
+ for (let j = 0; j < ir.length; j++) e += ir[j] * ir[j]
75
+ let g = e > 0 ? 1 / Math.sqrt(e) : 1
76
+ h = Float32Array.from(ir, v => v * g)
77
+ }
78
+ let useFft = method === 'fft' || (method === 'auto' && h.length > DIRECT_MAX)
79
+ let wet = useFft ? partitioned(data, h) : direct(data, h)
80
+ for (let i = 0; i < data.length; i++) data[i] = data[i] * (1 - mix) + wet[i] * mix
81
+ return data
82
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@audio/reverb-convolution",
3
+ "version": "1.0.0",
4
+ "description": "Convolution reverb — impulse-response convolution (rooms, plates, cabinets)",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "convolution.js",
8
+ "exports": {
9
+ ".": "./convolution.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": [
13
+ "convolution.js"
14
+ ],
15
+ "keywords": [
16
+ "audio",
17
+ "dsp",
18
+ "reverb",
19
+ "convolution"
20
+ ],
21
+ "license": "MIT",
22
+ "author": "Dmitry Iv. <dfcreative@gmail.com>",
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "dependencies": {
30
+ "fourier-transform": "^2.2.0"
31
+ }
32
+ }