@audio/neural-denoise 0.1.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/LICENSE +57 -0
- package/NOTICE +43 -0
- package/README.md +213 -0
- package/audio.d.ts +28 -0
- package/audio.js +132 -0
- package/deepfilter.js +209 -0
- package/denoise.js +74 -0
- package/fft.js +135 -0
- package/gemv.wat +38 -0
- package/index.d.ts +57 -0
- package/package.json +79 -0
- package/rnnoise.bin +0 -0
- package/rnnoise.js +615 -0
- package/worklet.js +63 -0
package/rnnoise.js
ADDED
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
// RNNoise: J.-M. Valin, "A Hybrid DSP/Deep Learning Approach to Real-Time Full-Band Speech
|
|
2
|
+
// Enhancement", MMSP 2018 (arXiv:1709.08243). A network of 1.8M weights predicts 32 ERB-band gains
|
|
3
|
+
// and a voice probability per 10 ms frame at 48 kHz; DSP does the rest: 32 bands, pitch search, a
|
|
4
|
+
// pitch filter restoring voiced harmonics between the bands, gain smoothing.
|
|
5
|
+
//
|
|
6
|
+
// Copyright (c) 2007-2017, 2024 Jean-Marc Valin; 2023 Amazon; 2017-2019 Mozilla; 2005-2017 Xiph.Org
|
|
7
|
+
// Foundation; 2008-2011 Octasic Inc.; 2007-2008 CSIRO; 2018 Gregor Richards. BSD-3-Clause: the
|
|
8
|
+
// conditions and the disclaimer are in LICENSE.
|
|
9
|
+
//
|
|
10
|
+
// Changes, 2026, audiojs: translated to JavaScript from xiph/rnnoise at 70f1d25 (src/denoise.c, rnn.c,
|
|
11
|
+
// nnet.c, nnet_arch.h, vec.h, pitch.c, celt_lpc.c, parse_lpcnet_weights.c); the portable C path
|
|
12
|
+
// (vec.h without SIMD) op for op in float32, so the output is bit-exact to that build compiled
|
|
13
|
+
// without FMA contraction (-ffp-contract=off). One file; state in typed arrays, no allocation per
|
|
14
|
+
// frame; weights parsed from upstream's blob (rnnoise_model_from_buffer, parse_weights, linear_init
|
|
15
|
+
// with the same checks). The int8 products run in a 419-byte WebAssembly SIMD kernel (gemv.wat) where
|
|
16
|
+
// available, else in JS: integer sums, identical either way. Not ported: x86 SU biases and unsigned
|
|
17
|
+
// activations (vec_avx.h), training.
|
|
18
|
+
//
|
|
19
|
+
// Float semantics: each C float op is Math.fround(a op b). For +, −, ×, ÷ and sqrt of float32
|
|
20
|
+
// operands the double result rounded to float32 equals the float32 operation (S. A. Figueroa,
|
|
21
|
+
// "When is double rounding innocuous?", SIGNUM Newsletter 30(3), 1995: 53 ≥ 2·24 + 2). Where the C
|
|
22
|
+
// promotes to double (double literals such as 1e-3, libm log10 and sqrt) the JS stays in double and
|
|
23
|
+
// rounds on the store, as the C does.
|
|
24
|
+
|
|
25
|
+
import { WINDOW, rfft, irfft } from './fft.js'
|
|
26
|
+
|
|
27
|
+
const f = Math.fround
|
|
28
|
+
|
|
29
|
+
// C float literals (the f suffix), rounded once as the compiler does
|
|
30
|
+
const F = { c06: f(.6), c07: f(.7), c085: f(.85), c09: f(.9), c03: f(.3), c04: f(.4), c05: f(.5), c08: f(.8), c008: f(.008),
|
|
31
|
+
c10001: f(1.0001), c0001: f(.001), c1e12: f(1e-12), hpA0: f(-1.99599), hpA1: f(0.99600),
|
|
32
|
+
N0: f(952.52801514), N1: f(96.39235687), N2: f(0.60863042), D0: f(952.72399902), D1: f(413.36801147), D2: f(11.88600922) }
|
|
33
|
+
|
|
34
|
+
export const FRAME = 480
|
|
35
|
+
// The attenuation limit, dB, that denoise(), the worklet and the audio atom apply unless given one (0:
|
|
36
|
+
// none, upstream's output). Unlimited, this model removes the voice on 12 of the 824 VoiceBank+DEMAND
|
|
37
|
+
// test files (STOI down by more than 0.2); limited to 20 dB, on none (README, Accuracy).
|
|
38
|
+
export const LIMIT = 20
|
|
39
|
+
const WIN = 960, FREQ = 481, NB = 32, NF = 65
|
|
40
|
+
const PMIN = 60, PMAX = 768, PFRAME = 960, PBUF = PMAX + PFRAME
|
|
41
|
+
|
|
42
|
+
// denoise.c eband20ms: ERB-spaced band edges in 50 Hz bins (0 Hz to 20 kHz)
|
|
43
|
+
const EBAND = [0, 2, 4, 6, 8, 10, 12, 15, 18, 21, 24, 28, 32, 36, 41, 47, 53, 60, 68, 77, 87, 98, 110, 124, 140, 157, 176, 198, 223, 251, 282, 317, 356, 400]
|
|
44
|
+
|
|
45
|
+
// ------------------------------------------------ tables (dump_rnnoise_tables.c)
|
|
46
|
+
|
|
47
|
+
const DCT = new Float32Array(NB * NB)
|
|
48
|
+
for (let i = 0; i < NB; i++) for (let j = 0; j < NB; j++) {
|
|
49
|
+
DCT[i * NB + j] = Math.cos((i + .5) * j * Math.PI / NB)
|
|
50
|
+
if (j === 0) DCT[i * NB + j] = DCT[i * NB + j] * Math.sqrt(.5)
|
|
51
|
+
}
|
|
52
|
+
const DCT_NORM = Math.sqrt(2 / 22)
|
|
53
|
+
|
|
54
|
+
// ------------------------------------------------ weights (parse_lpcnet_weights.c)
|
|
55
|
+
|
|
56
|
+
const HEAD = 64, TYPES = { 0: Float32Array, 1: Int32Array, 3: Int8Array }
|
|
57
|
+
|
|
58
|
+
// upstream's weight blob: 64-byte records { 'DNNw', version, type, size, block_size, name[44] }
|
|
59
|
+
function parseBlob(bytes) {
|
|
60
|
+
let u8 = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)
|
|
61
|
+
let dv = new DataView(u8.buffer, u8.byteOffset, u8.byteLength), arrays = new Map(), o = 0
|
|
62
|
+
while (o < u8.length) {
|
|
63
|
+
if (u8.length - o < HEAD) throw new Error('rnnoise: truncated weight record')
|
|
64
|
+
let type = dv.getInt32(o + 8, true), size = dv.getInt32(o + 12, true), block = dv.getInt32(o + 16, true)
|
|
65
|
+
let name = '' // ASCII, NUL-terminated; no TextDecoder: AudioWorkletGlobalScope has none
|
|
66
|
+
for (let i = o + 20; i < o + 64 && u8[i]; i++) name += String.fromCharCode(u8[i])
|
|
67
|
+
if (block < size || block > u8.length - o - HEAD || size < 0 || u8[o + 63] !== 0) throw new Error(`rnnoise: bad weight record '${name}'`)
|
|
68
|
+
let T = TYPES[type]
|
|
69
|
+
if (!T) throw new Error(`rnnoise: unknown weight type ${type} for '${name}'`)
|
|
70
|
+
// a copy (ArrayBuffer.slice): the bytes may sit at any offset of a shared buffer (a Node Buffer's pool)
|
|
71
|
+
let data = new T(u8.buffer.slice(u8.byteOffset + o + HEAD, u8.byteOffset + o + HEAD + size))
|
|
72
|
+
arrays.set(name, data)
|
|
73
|
+
o += HEAD + block
|
|
74
|
+
}
|
|
75
|
+
return arrays
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// linear_init: same presence and size checks; float weights win over int8 when both exist (compute_linear)
|
|
79
|
+
function linear(arrays, name, nIn, nOut, { int8 = false, sparse = false, diag = false } = {}) {
|
|
80
|
+
let need = (key, len) => {
|
|
81
|
+
let a = arrays.get(key)
|
|
82
|
+
if (!a || a.length !== len) throw new Error(`rnnoise: weight '${key}' missing or not ${len} long`)
|
|
83
|
+
return a
|
|
84
|
+
}
|
|
85
|
+
let opt = (key, len) => {
|
|
86
|
+
let a = arrays.get(key)
|
|
87
|
+
if (a && a.length !== len) throw new Error(`rnnoise: weight '${key}' is ${a.length} long, want ${len}`)
|
|
88
|
+
return a || null
|
|
89
|
+
}
|
|
90
|
+
let L = { nIn, nOut, bias: need(name + '_bias', nOut), idx: null, weights: null, floats: null, scale: null, diag: null }
|
|
91
|
+
let len = nIn * nOut
|
|
92
|
+
if (sparse) {
|
|
93
|
+
let idx = need(name + '_weights_idx', arrays.get(name + '_weights_idx')?.length ?? -1), blocks = 0, p = 0, out = nOut
|
|
94
|
+
while (p < idx.length) {
|
|
95
|
+
let n = idx[p++]
|
|
96
|
+
if (idx.length - p < n) throw new Error(`rnnoise: '${name}_weights_idx' overruns`)
|
|
97
|
+
for (let i = 0; i < n; i++) { let pos = idx[p++]; if (pos + 3 >= nIn || (pos & 3)) throw new Error(`rnnoise: '${name}_weights_idx' column ${pos} out of range`) }
|
|
98
|
+
blocks += n; out -= 8
|
|
99
|
+
}
|
|
100
|
+
if (out !== 0) throw new Error(`rnnoise: '${name}_weights_idx' covers the wrong number of rows`)
|
|
101
|
+
L.idx = idx; len = 32 * blocks
|
|
102
|
+
}
|
|
103
|
+
if (int8) L.weights = need(name + '_weights_int8', len)
|
|
104
|
+
L.floats = opt(name + '_weights_float', len)
|
|
105
|
+
if (!int8 && !L.floats) throw new Error(`rnnoise: weight '${name}_weights_float' missing`)
|
|
106
|
+
if (int8) L.scale = need(name + '_scale', nOut)
|
|
107
|
+
if (diag) L.diag = need(name + '_weights_diag', nOut)
|
|
108
|
+
opt(name + '_subias', nOut)
|
|
109
|
+
return L
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// gemv.wat, compiled by wat2wasm (419 bytes): the int8 products of sparse_cgemv8x4 with WebAssembly SIMD
|
|
113
|
+
// (i16 widening, i32x4.dot_i16x8_s). The same integer sums as the JS loop, 8.5 times faster; the JS loop
|
|
114
|
+
// runs where WebAssembly SIMD does not (Safari before 16.4, Node before 16.4).
|
|
115
|
+
const GEMV = new Uint8Array([
|
|
116
|
+
0,97,115,109,1,0,0,0,1,9,1,96,5,127,127,127,127,127,0,3,2,1,0,5,3,1,0,1,7,17,2,6,109,101,109,111,114,121,2,0,
|
|
117
|
+
4,103,101,109,118,0,0,10,241,2,1,238,2,2,2,127,6,123,3,64,32,1,40,2,0,33,6,32,1,65,4,106,33,1,253,12,0,0,0,0,
|
|
118
|
+
0,0,0,0,0,0,0,0,0,0,0,0,33,9,253,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,33,10,253,12,0,0,0,0,
|
|
119
|
+
0,0,0,0,0,0,0,0,0,0,0,0,33,11,253,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,33,12,2,64,3,64,32,6,
|
|
120
|
+
69,13,1,32,2,32,1,40,2,0,106,40,2,0,253,17,253,135,1,33,7,32,0,253,0,4,0,33,8,32,9,32,8,253,135,1,32,7,253,186,
|
|
121
|
+
1,253,174,1,33,9,32,10,32,8,253,136,1,32,7,253,186,1,253,174,1,33,10,32,0,253,0,4,16,33,8,32,11,32,8,253,135,1,32,7,
|
|
122
|
+
253,186,1,253,174,1,33,11,32,12,32,8,253,136,1,32,7,253,186,1,253,174,1,33,12,32,0,65,32,106,33,0,32,1,65,4,106,33,1,32,
|
|
123
|
+
6,65,1,107,33,6,12,0,11,11,32,3,32,9,32,10,253,13,0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27,32,9,32,10,253,13,
|
|
124
|
+
4,5,6,7,12,13,14,15,20,21,22,23,28,29,30,31,253,174,1,253,11,4,0,32,3,32,11,32,12,253,13,0,1,2,3,8,9,10,11,16,
|
|
125
|
+
17,18,19,24,25,26,27,32,11,32,12,253,13,4,5,6,7,12,13,14,15,20,21,22,23,28,29,30,31,253,174,1,253,11,4,16,32,3,65,32,
|
|
126
|
+
106,33,3,32,5,65,8,106,33,5,32,5,32,4,73,13,0,11,11,
|
|
127
|
+
])
|
|
128
|
+
|
|
129
|
+
// the int8 layers' weights and block indices in one wasm memory: x (int8) at 0, sums (int32) at 512, then the layers
|
|
130
|
+
function attachSimd(net) {
|
|
131
|
+
let mod
|
|
132
|
+
try { mod = new WebAssembly.Module(GEMV) } catch { return }
|
|
133
|
+
let layers = [net.conv2, net.gru1.input, net.gru1.recurrent, net.gru2.input, net.gru2.recurrent, net.gru3.input, net.gru3.recurrent].filter(L => L.weights && !L.floats)
|
|
134
|
+
let idxs = layers.map(L => L.idx ?? denseIdx(L.nIn, L.nOut)), size = 5120
|
|
135
|
+
for (let i = 0; i < layers.length; i++) size += idxs[i].length * 4 + layers[i].weights.length + 16
|
|
136
|
+
let inst = new WebAssembly.Instance(mod), mem = inst.exports.memory
|
|
137
|
+
mem.grow(Math.ceil(size / 65536))
|
|
138
|
+
let k = { gemv: inst.exports.gemv, x: new Int8Array(mem.buffer, 0, 512), sums: new Int32Array(mem.buffer, 512, 1152) }, o = 5120
|
|
139
|
+
layers.forEach((L, i) => {
|
|
140
|
+
new Int32Array(mem.buffer, o, idxs[i].length).set(idxs[i])
|
|
141
|
+
let idx = o; o = (o + idxs[i].length * 4 + 15) & ~15
|
|
142
|
+
new Int8Array(mem.buffer, o, L.weights.length).set(L.weights)
|
|
143
|
+
L.simd = { k, idx, w: o }; o += L.weights.length
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// the block index of a dense 8×4-blocked matrix: every row block uses every column block
|
|
148
|
+
function denseIdx(nIn, nOut) {
|
|
149
|
+
let idx = []
|
|
150
|
+
for (let i = 0; i < nOut; i += 8) { idx.push(nIn >> 2); for (let j = 0; j < nIn; j += 4) idx.push(j) }
|
|
151
|
+
return Int32Array.from(idx)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// init_rnnoise (rnnoise_data.c): conv 65·3 → 128, conv 128·3 → 384, three GRUs of 384, dense 1536 → 32 + 1
|
|
155
|
+
export function model(bytes, { simd = true } = {}) {
|
|
156
|
+
let a = parseBlob(bytes)
|
|
157
|
+
let gru = k => ({
|
|
158
|
+
input: linear(a, `gru${k}_input`, 384, 1152, { int8: true, sparse: true }),
|
|
159
|
+
recurrent: linear(a, `gru${k}_recurrent`, 384, 1152, { int8: true, sparse: true, diag: true }),
|
|
160
|
+
})
|
|
161
|
+
let net = {
|
|
162
|
+
conv1: linear(a, 'conv1', 195, 128),
|
|
163
|
+
conv2: linear(a, 'conv2', 384, 384, { int8: true }),
|
|
164
|
+
gru1: gru(1), gru2: gru(2), gru3: gru(3),
|
|
165
|
+
dense: linear(a, 'dense_out', 1536, 32),
|
|
166
|
+
vad: linear(a, 'vad_dense', 1536, 1),
|
|
167
|
+
}
|
|
168
|
+
if (simd && typeof WebAssembly === 'object') attachSimd(net)
|
|
169
|
+
return net
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ------------------------------------------------ network (nnet.c, nnet_arch.h, vec.h)
|
|
173
|
+
|
|
174
|
+
const XQ = new Int8Array(1536)
|
|
175
|
+
|
|
176
|
+
// compute_linear_c: out = W·in + bias (+ diag ⊙ in, GRU recurrent only)
|
|
177
|
+
function computeLinear(L, out, x) {
|
|
178
|
+
let N = L.nOut, M = L.nIn
|
|
179
|
+
if (L.floats) {
|
|
180
|
+
if (L.idx) sparseSgemv(out, L.floats, L.idx, N, x)
|
|
181
|
+
else sgemv(out, L.floats, N, M, x)
|
|
182
|
+
} else if (L.simd) {
|
|
183
|
+
let { k, idx, w } = L.simd, s = L.scale
|
|
184
|
+
for (let i = 0; i < M; i++) k.x[i] = Math.floor(.5 + f(127 * x[i]))
|
|
185
|
+
k.gemv(w, idx, 0, 512, N)
|
|
186
|
+
for (let i = 0; i < N; i++) out[i] = f(k.sums[i] * s[i])
|
|
187
|
+
} else {
|
|
188
|
+
// cgemv8x4 / sparse_cgemv8x4: activations quantized to int8, floor(.5 + 127x); the dot
|
|
189
|
+
// products are integers under 2^24, exact in the C's float accumulator as in doubles here
|
|
190
|
+
for (let i = 0; i < M; i++) XQ[i] = Math.floor(.5 + f(127 * x[i]))
|
|
191
|
+
let w = L.weights, s = L.scale, idx = L.idx, p = 0, q = 0
|
|
192
|
+
for (let i = 0; i < N; i += 8) {
|
|
193
|
+
let y0 = 0, y1 = 0, y2 = 0, y3 = 0, y4 = 0, y5 = 0, y6 = 0, y7 = 0
|
|
194
|
+
let n = idx ? idx[q++] : M >> 2
|
|
195
|
+
for (let j = 0; j < n; j++, p += 32) {
|
|
196
|
+
let pos = idx ? idx[q++] : j << 2
|
|
197
|
+
let x0 = XQ[pos], x1 = XQ[pos + 1], x2 = XQ[pos + 2], x3 = XQ[pos + 3]
|
|
198
|
+
y0 += w[p] * x0 + w[p + 1] * x1 + w[p + 2] * x2 + w[p + 3] * x3
|
|
199
|
+
y1 += w[p + 4] * x0 + w[p + 5] * x1 + w[p + 6] * x2 + w[p + 7] * x3
|
|
200
|
+
y2 += w[p + 8] * x0 + w[p + 9] * x1 + w[p + 10] * x2 + w[p + 11] * x3
|
|
201
|
+
y3 += w[p + 12] * x0 + w[p + 13] * x1 + w[p + 14] * x2 + w[p + 15] * x3
|
|
202
|
+
y4 += w[p + 16] * x0 + w[p + 17] * x1 + w[p + 18] * x2 + w[p + 19] * x3
|
|
203
|
+
y5 += w[p + 20] * x0 + w[p + 21] * x1 + w[p + 22] * x2 + w[p + 23] * x3
|
|
204
|
+
y6 += w[p + 24] * x0 + w[p + 25] * x1 + w[p + 26] * x2 + w[p + 27] * x3
|
|
205
|
+
y7 += w[p + 28] * x0 + w[p + 29] * x1 + w[p + 30] * x2 + w[p + 31] * x3
|
|
206
|
+
}
|
|
207
|
+
out[i] = f(y0 * s[i]); out[i + 1] = f(y1 * s[i + 1]); out[i + 2] = f(y2 * s[i + 2]); out[i + 3] = f(y3 * s[i + 3])
|
|
208
|
+
out[i + 4] = f(y4 * s[i + 4]); out[i + 5] = f(y5 * s[i + 5]); out[i + 6] = f(y6 * s[i + 6]); out[i + 7] = f(y7 * s[i + 7])
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
let b = L.bias
|
|
212
|
+
for (let i = 0; i < N; i++) out[i] = f(out[i] + b[i])
|
|
213
|
+
if (L.diag) {
|
|
214
|
+
let d = L.diag
|
|
215
|
+
for (let i = 0; i < M; i++) {
|
|
216
|
+
out[i] = f(out[i] + f(d[i] * x[i]))
|
|
217
|
+
out[i + M] = f(out[i + M] + f(d[i + M] * x[i]))
|
|
218
|
+
out[i + 2 * M] = f(out[i + 2 * M] + f(d[i + 2 * M] * x[i]))
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// sgemv (sgemv16x1 / sgemv8x1 / plain loop): column-major, accumulated in input order
|
|
224
|
+
function sgemv(out, w, N, M, x) {
|
|
225
|
+
out.fill(0, 0, N)
|
|
226
|
+
if (N % 8 === 0) {
|
|
227
|
+
let B = N % 16 === 0 ? 16 : 8
|
|
228
|
+
for (let i = 0; i < N; i += B)
|
|
229
|
+
for (let j = 0; j < M; j++) {
|
|
230
|
+
let xj = x[j], o = j * N + i
|
|
231
|
+
for (let k = 0; k < B; k++) out[i + k] = f(out[i + k] + f(w[o + k] * xj))
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else for (let i = 0; i < N; i++) {
|
|
235
|
+
let y = 0
|
|
236
|
+
for (let j = 0; j < M; j++) y = f(y + f(w[j * N + i] * x[j]))
|
|
237
|
+
out[i] = y
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// sparse_sgemv8x4: only reached with a blob carrying float copies of the sparse int8 matrices
|
|
242
|
+
function sparseSgemv(out, w, idx, N, x) {
|
|
243
|
+
out.fill(0, 0, N)
|
|
244
|
+
let p = 0, q = 0
|
|
245
|
+
for (let i = 0; i < N; i += 8) {
|
|
246
|
+
let n = idx[q++]
|
|
247
|
+
for (let j = 0; j < n; j++) {
|
|
248
|
+
let pos = idx[q++]
|
|
249
|
+
for (let c = 0; c < 4; c++, p += 8) {
|
|
250
|
+
let xj = x[pos + c]
|
|
251
|
+
for (let k = 0; k < 8; k++) out[i + k] = f(out[i + k] + f(w[p + k] * xj))
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// vec.h tanh_approx: (N0 + N1x² + N2x⁴)x / (D0 + D1x² + D2x⁴), clamped to ±1
|
|
258
|
+
function tanh(x) {
|
|
259
|
+
let x2 = f(x * x)
|
|
260
|
+
let num = f(f(f(f(F.N2 * x2) + F.N1) * x2) + F.N0)
|
|
261
|
+
let den = f(f(f(f(F.D2 * x2) + F.D1) * x2) + F.D0)
|
|
262
|
+
num = f(f(num * x) / den)
|
|
263
|
+
return num > 1 ? 1 : num < -1 ? -1 : num
|
|
264
|
+
}
|
|
265
|
+
const sigmoid = x => f(.5 + f(.5 * tanh(f(.5 * x))))
|
|
266
|
+
|
|
267
|
+
function convLayer(L, out, mem, x, nIn, act, tmp) {
|
|
268
|
+
let keep = L.nIn - nIn
|
|
269
|
+
tmp.set(mem.subarray(0, keep)); tmp.set(x.subarray(0, nIn), keep)
|
|
270
|
+
computeLinear(L, out, tmp)
|
|
271
|
+
for (let i = 0; i < L.nOut; i++) out[i] = act(out[i])
|
|
272
|
+
mem.set(tmp.subarray(nIn, nIn + keep))
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function gruLayer(G, state, x, zrh, recur) {
|
|
276
|
+
let N = 384
|
|
277
|
+
computeLinear(G.input, zrh, x)
|
|
278
|
+
computeLinear(G.recurrent, recur, state)
|
|
279
|
+
for (let i = 0; i < 2 * N; i++) zrh[i] = sigmoid(f(zrh[i] + recur[i]))
|
|
280
|
+
for (let i = 0; i < N; i++) {
|
|
281
|
+
let h = tanh(f(zrh[2 * N + i] + f(recur[2 * N + i] * zrh[N + i]))), z = zrh[i]
|
|
282
|
+
state[i] = f(f(z * state[i]) + f(f(1 - z) * h))
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ------------------------------------------------ DSP (denoise.c, pitch.c, celt_lpc.c)
|
|
287
|
+
|
|
288
|
+
const SUM = new Float32Array(NB + 2)
|
|
289
|
+
function bandEnergy(E, Xr, Xi, Pr, Pi) {
|
|
290
|
+
let sum = SUM.fill(0)
|
|
291
|
+
for (let i = 0; i < NB + 1; i++) {
|
|
292
|
+
let n = EBAND[i + 1] - EBAND[i]
|
|
293
|
+
for (let j = 0; j < n; j++) {
|
|
294
|
+
let fr = f(j / n), k = EBAND[i] + j
|
|
295
|
+
let t = Pr ? f(f(Xr[k] * Pr[k]) + f(Xi[k] * Pi[k])) : f(f(Xr[k] * Xr[k]) + f(Xi[k] * Xi[k]))
|
|
296
|
+
sum[i] = f(sum[i] + f(f(1 - fr) * t))
|
|
297
|
+
sum[i + 1] = f(sum[i + 1] + f(fr * t))
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
sum[1] = f(f(f(sum[0] + sum[1]) * 2) / 3)
|
|
301
|
+
sum[NB] = f(f(f(sum[NB] + sum[NB + 1]) * 2) / 3)
|
|
302
|
+
for (let i = 0; i < NB; i++) E[i] = sum[i + 1]
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// interp_band_gain; bins 400–480 (above 20 kHz) keep what g held: zeros in every caller
|
|
306
|
+
function interpGain(g, b) {
|
|
307
|
+
for (let i = 1; i < NB; i++) {
|
|
308
|
+
let n = EBAND[i + 1] - EBAND[i]
|
|
309
|
+
for (let j = 0; j < n; j++) { let fr = f(j / n); g[EBAND[i] + j] = f(f(f(1 - fr) * b[i - 1]) + f(fr * b[i])) }
|
|
310
|
+
}
|
|
311
|
+
for (let j = 0; j < EBAND[1]; j++) g[j] = b[0]
|
|
312
|
+
for (let j = EBAND[NB]; j < EBAND[NB + 1]; j++) g[j] = b[NB - 1]
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function dct(out, o, x) {
|
|
316
|
+
for (let i = 0; i < NB; i++) {
|
|
317
|
+
let s = 0
|
|
318
|
+
for (let j = 0; j < NB; j++) s = f(s + f(x[j] * DCT[j * NB + i]))
|
|
319
|
+
out[o + i] = s * DCT_NORM
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function innerProd(x, xo, y, yo, n) {
|
|
324
|
+
let s = 0
|
|
325
|
+
for (let i = 0; i < n; i++) s = f(s + f(x[xo + i] * y[yo + i]))
|
|
326
|
+
return s
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// xcorr_kernel: four lags at once, the C's rotation of y_0…y_3 kept for the same rounding order
|
|
330
|
+
function xcorrKernel(x, xo, y, yo, s, len) {
|
|
331
|
+
let y0 = y[yo++], y1 = y[yo++], y2 = y[yo++], y3 = 0, s0 = s[0], s1 = s[1], s2 = s[2], s3 = s[3], j = 0, t
|
|
332
|
+
for (; j < len - 3; j += 4) {
|
|
333
|
+
t = x[xo++]; y3 = y[yo++]
|
|
334
|
+
s0 = f(s0 + f(t * y0)); s1 = f(s1 + f(t * y1)); s2 = f(s2 + f(t * y2)); s3 = f(s3 + f(t * y3))
|
|
335
|
+
t = x[xo++]; y0 = y[yo++]
|
|
336
|
+
s0 = f(s0 + f(t * y1)); s1 = f(s1 + f(t * y2)); s2 = f(s2 + f(t * y3)); s3 = f(s3 + f(t * y0))
|
|
337
|
+
t = x[xo++]; y1 = y[yo++]
|
|
338
|
+
s0 = f(s0 + f(t * y2)); s1 = f(s1 + f(t * y3)); s2 = f(s2 + f(t * y0)); s3 = f(s3 + f(t * y1))
|
|
339
|
+
t = x[xo++]; y2 = y[yo++]
|
|
340
|
+
s0 = f(s0 + f(t * y3)); s1 = f(s1 + f(t * y0)); s2 = f(s2 + f(t * y1)); s3 = f(s3 + f(t * y2))
|
|
341
|
+
}
|
|
342
|
+
if (j++ < len) { t = x[xo++]; y3 = y[yo++]; s0 = f(s0 + f(t * y0)); s1 = f(s1 + f(t * y1)); s2 = f(s2 + f(t * y2)); s3 = f(s3 + f(t * y3)) }
|
|
343
|
+
if (j++ < len) { t = x[xo++]; y0 = y[yo++]; s0 = f(s0 + f(t * y1)); s1 = f(s1 + f(t * y2)); s2 = f(s2 + f(t * y3)); s3 = f(s3 + f(t * y0)) }
|
|
344
|
+
if (j < len) { t = x[xo++]; y1 = y[yo++]; s0 = f(s0 + f(t * y2)); s1 = f(s1 + f(t * y3)); s2 = f(s2 + f(t * y0)); s3 = f(s3 + f(t * y1)) }
|
|
345
|
+
s[0] = s0; s[1] = s1; s[2] = s2; s[3] = s3
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const SUM4 = new Float32Array(4)
|
|
349
|
+
function pitchXcorr(x, xo, y, yo, xcorr, len, maxPitch) {
|
|
350
|
+
let i = 0
|
|
351
|
+
for (; i < maxPitch - 3; i += 4) {
|
|
352
|
+
SUM4.fill(0)
|
|
353
|
+
xcorrKernel(x, xo, y, yo + i, SUM4, len)
|
|
354
|
+
xcorr[i] = SUM4[0]; xcorr[i + 1] = SUM4[1]; xcorr[i + 2] = SUM4[2]; xcorr[i + 3] = SUM4[3]
|
|
355
|
+
}
|
|
356
|
+
for (; i < maxPitch; i++) xcorr[i] = innerProd(x, xo, y, yo + i, len)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// rnn_lpc: Levinson–Durbin, order 4 here, stopping at 30 dB prediction gain
|
|
360
|
+
function lpc(out, ac, p) {
|
|
361
|
+
let err = ac[0]
|
|
362
|
+
out.fill(0, 0, p)
|
|
363
|
+
if (ac[0] === 0) return
|
|
364
|
+
for (let i = 0; i < p; i++) {
|
|
365
|
+
let rr = 0
|
|
366
|
+
for (let j = 0; j < i; j++) rr = f(rr + f(out[j] * ac[i - j]))
|
|
367
|
+
rr = f(rr + ac[i + 1])
|
|
368
|
+
let r = f(-rr / err)
|
|
369
|
+
out[i] = r
|
|
370
|
+
for (let j = 0; j < (i + 1) >> 1; j++) {
|
|
371
|
+
let t1 = out[j], t2 = out[i - 1 - j]
|
|
372
|
+
out[j] = f(t1 + f(r * t2)); out[i - 1 - j] = f(t2 + f(r * t1))
|
|
373
|
+
}
|
|
374
|
+
err = f(err - f(f(r * r) * err))
|
|
375
|
+
if (err < f(F.c0001 * ac[0])) break
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const AC = new Float32Array(5), LPC = new Float32Array(4)
|
|
380
|
+
|
|
381
|
+
// rnn_pitch_downsample (one channel): 2:1 by [.25 .5 .25], then a 4th-order LPC whitening with a zero
|
|
382
|
+
function pitchDownsample(x, xlp, len) {
|
|
383
|
+
let n = len >> 1
|
|
384
|
+
for (let i = 1; i < n; i++) xlp[i] = f(.5 * f(f(.5 * f(x[2 * i - 1] + x[2 * i + 1])) + x[2 * i]))
|
|
385
|
+
xlp[0] = f(.5 * f(f(.5 * x[1]) + x[0]))
|
|
386
|
+
let ac = AC
|
|
387
|
+
pitchXcorr(xlp, 0, xlp, 0, ac, n - 4, 5)
|
|
388
|
+
for (let k = 0; k <= 4; k++) {
|
|
389
|
+
let d = 0
|
|
390
|
+
for (let i = k + n - 4; i < n; i++) d = f(d + f(xlp[i] * xlp[i - k]))
|
|
391
|
+
ac[k] = f(ac[k] + d)
|
|
392
|
+
}
|
|
393
|
+
ac[0] = f(ac[0] * F.c10001)
|
|
394
|
+
for (let i = 1; i <= 4; i++) ac[i] = f(ac[i] - f(f(ac[i] * f(F.c008 * i)) * f(F.c008 * i)))
|
|
395
|
+
let a = LPC, tmp = 1
|
|
396
|
+
lpc(a, ac, 4)
|
|
397
|
+
for (let i = 0; i < 4; i++) { tmp = f(F.c09 * tmp); a[i] = f(a[i] * tmp) }
|
|
398
|
+
let c1 = F.c08
|
|
399
|
+
let n0 = f(a[0] + c1), n1 = f(a[1] + f(c1 * a[0])), n2 = f(a[2] + f(c1 * a[1])), n3 = f(a[3] + f(c1 * a[2])), n4 = f(c1 * a[3])
|
|
400
|
+
let m0 = 0, m1 = 0, m2 = 0, m3 = 0, m4 = 0
|
|
401
|
+
for (let i = 0; i < n; i++) {
|
|
402
|
+
let xi = xlp[i]
|
|
403
|
+
let s = f(xi + f(n0 * m0)); s = f(s + f(n1 * m1)); s = f(s + f(n2 * m2)); s = f(s + f(n3 * m3)); s = f(s + f(n4 * m4))
|
|
404
|
+
m4 = m3; m3 = m2; m2 = m1; m1 = m0; m0 = xi
|
|
405
|
+
xlp[i] = s
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function findBestPitch(xcorr, y, yo, len, maxPitch, best) {
|
|
410
|
+
let Syy = 1, bn0 = -1, bn1 = -1, bd0 = 0, bd1 = 0
|
|
411
|
+
best[0] = 0; best[1] = 1
|
|
412
|
+
for (let j = 0; j < len; j++) Syy = f(Syy + f(y[yo + j] * y[yo + j]))
|
|
413
|
+
for (let i = 0; i < maxPitch; i++) {
|
|
414
|
+
if (xcorr[i] > 0) {
|
|
415
|
+
let c = f(xcorr[i] * F.c1e12), num = f(c * c)
|
|
416
|
+
if (f(num * bd1) > f(bn1 * Syy)) {
|
|
417
|
+
if (f(num * bd0) > f(bn0 * Syy)) { bn1 = bn0; bd1 = bd0; best[1] = best[0]; bn0 = num; bd0 = Syy; best[0] = i }
|
|
418
|
+
else { bn1 = num; bd1 = Syy; best[1] = i }
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
Syy = f(Syy + f(f(y[yo + i + len] * y[yo + i + len]) - f(y[yo + i] * y[yo + i])))
|
|
422
|
+
Syy = 1 > Syy ? 1 : Syy
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// rnn_pitch_search: coarse at 4:1, refined at 2:1 around the two best, pseudo-interpolated
|
|
427
|
+
const X4 = new Float32Array(PFRAME >> 2), Y4 = new Float32Array((PFRAME + PMAX) >> 2), XCORR = new Float32Array(PMAX >> 1), BEST = new Int32Array(2)
|
|
428
|
+
function pitchSearch(xlp, xo, y, len, maxPitch) {
|
|
429
|
+
let lag = len + maxPitch, x4 = X4, y4 = Y4, xcorr = XCORR.fill(0), best = BEST
|
|
430
|
+
for (let j = 0; j < len >> 2; j++) x4[j] = xlp[xo + 2 * j]
|
|
431
|
+
for (let j = 0; j < lag >> 2; j++) y4[j] = y[2 * j]
|
|
432
|
+
pitchXcorr(x4, 0, y4, 0, xcorr, len >> 2, maxPitch >> 2)
|
|
433
|
+
findBestPitch(xcorr, y4, 0, len >> 2, maxPitch >> 2, best)
|
|
434
|
+
for (let i = 0; i < maxPitch >> 1; i++) {
|
|
435
|
+
xcorr[i] = 0
|
|
436
|
+
if (Math.abs(i - 2 * best[0]) > 2 && Math.abs(i - 2 * best[1]) > 2) continue
|
|
437
|
+
let s = innerProd(xlp, xo, y, i, len >> 1)
|
|
438
|
+
xcorr[i] = -1 > s ? -1 : s
|
|
439
|
+
}
|
|
440
|
+
findBestPitch(xcorr, y, 0, len >> 1, maxPitch >> 1, best)
|
|
441
|
+
let offset = 0
|
|
442
|
+
if (best[0] > 0 && best[0] < (maxPitch >> 1) - 1) {
|
|
443
|
+
let a = xcorr[best[0] - 1], b = xcorr[best[0]], c = xcorr[best[0] + 1]
|
|
444
|
+
if (f(c - a) > f(F.c07 * f(b - a))) offset = 1
|
|
445
|
+
else if (f(a - c) > f(F.c07 * f(b - c))) offset = -1
|
|
446
|
+
}
|
|
447
|
+
return 2 * best[0] - offset
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const SECOND_CHECK = [0, 0, 3, 2, 3, 2, 5, 2, 3, 2, 3, 2, 5, 2, 3, 2]
|
|
451
|
+
const pitchGain = (xy, xx, yy) => f(xy / Math.sqrt(f(1 + f(xx * yy))))
|
|
452
|
+
|
|
453
|
+
// rnn_remove_doubling: prefer T/k when its correlation holds up; the gain goes to PG[0]
|
|
454
|
+
const YYL = new Float32Array((PMAX >> 1) + 1), PG = new Float32Array(1)
|
|
455
|
+
function removeDoubling(x, T0, prevPeriod, prevGain) {
|
|
456
|
+
let maxp = PMAX >> 1, minp = PMIN >> 1, N = PFRAME >> 1, o = maxp
|
|
457
|
+
T0 = (T0 / 2) | 0; prevPeriod = (prevPeriod / 2) | 0
|
|
458
|
+
if (T0 >= maxp) T0 = maxp - 1
|
|
459
|
+
let T = T0, xx = 0, xy = 0
|
|
460
|
+
for (let i = 0; i < N; i++) { xx = f(xx + f(x[o + i] * x[o + i])); xy = f(xy + f(x[o + i] * x[o + i - T0])) }
|
|
461
|
+
let yyl = YYL, yy = xx
|
|
462
|
+
yyl[0] = xx
|
|
463
|
+
for (let i = 1; i <= maxp; i++) {
|
|
464
|
+
yy = f(f(yy + f(x[o - i] * x[o - i])) - f(x[o + N - i] * x[o + N - i]))
|
|
465
|
+
yyl[i] = 0 > yy ? 0 : yy
|
|
466
|
+
}
|
|
467
|
+
yy = yyl[T0]
|
|
468
|
+
let bestXy = xy, bestYy = yy, g0 = pitchGain(xy, xx, yy), g = g0
|
|
469
|
+
for (let k = 2; k <= 15; k++) {
|
|
470
|
+
let T1 = ((2 * T0 + k) / (2 * k)) | 0
|
|
471
|
+
if (T1 < minp) break
|
|
472
|
+
let T1b = k === 2 ? (T1 + T0 > maxp ? T0 : T0 + T1) : ((2 * SECOND_CHECK[k] * T0 + k) / (2 * k)) | 0
|
|
473
|
+
let a = 0, b = 0
|
|
474
|
+
for (let i = 0; i < N; i++) { a = f(a + f(x[o + i] * x[o + i - T1])); b = f(b + f(x[o + i] * x[o + i - T1b])) }
|
|
475
|
+
let xy1 = f(.5 * f(a + b)), yy1 = f(.5 * f(yyl[T1] + yyl[T1b])), g1 = pitchGain(xy1, xx, yy1)
|
|
476
|
+
let cont = Math.abs(T1 - prevPeriod) <= 1 ? prevGain : Math.abs(T1 - prevPeriod) <= 2 && 5 * k * k < T0 ? f(.5 * prevGain) : 0
|
|
477
|
+
let th = f(f(F.c07 * g0) - cont); th = F.c03 > th ? F.c03 : th
|
|
478
|
+
if (T1 < 3 * minp) { th = f(f(F.c085 * g0) - cont); th = F.c04 > th ? F.c04 : th }
|
|
479
|
+
else if (T1 < 2 * minp) { th = f(f(F.c09 * g0) - cont); th = F.c05 > th ? F.c05 : th }
|
|
480
|
+
if (g1 > th) { bestXy = xy1; bestYy = yy1; T = T1; g = g1 }
|
|
481
|
+
}
|
|
482
|
+
bestXy = 0 > bestXy ? 0 : bestXy
|
|
483
|
+
let pg = bestYy <= bestXy ? 1 : f(bestXy / f(bestYy + 1))
|
|
484
|
+
let c0 = innerProd(x, o, x, o - (T - 1), N), c1 = innerProd(x, o, x, o - T, N), c2 = innerProd(x, o, x, o - (T + 1), N)
|
|
485
|
+
let offset = f(c2 - c0) > f(F.c07 * f(c1 - c0)) ? 1 : f(c0 - c2) > f(F.c07 * f(c1 - c2)) ? -1 : 0
|
|
486
|
+
if (pg > g) pg = g
|
|
487
|
+
T0 = 2 * T + offset
|
|
488
|
+
PG[0] = pg
|
|
489
|
+
return T0 < PMIN ? PMIN : T0
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// ------------------------------------------------ denoiser state (DenoiseState)
|
|
493
|
+
|
|
494
|
+
// create(weights) → { process(input480, output480) → VAD probability }: one 10 ms frame at 48 kHz,
|
|
495
|
+
// samples at int16 scale (±32768), as rnnoise_process_frame. Output lags input by 960 samples.
|
|
496
|
+
export function create(net) {
|
|
497
|
+
if (!net || !net.gru1) net = model(net)
|
|
498
|
+
let analysisMem = new Float32Array(FRAME), synthesisMem = new Float32Array(FRAME)
|
|
499
|
+
let pitchBuf = new Float32Array(PBUF), memHp = new Float64Array(2), lastg = new Float32Array(NB)
|
|
500
|
+
let lastPeriod = 0, lastGain = 0
|
|
501
|
+
let dXr = new Float32Array(FREQ), dXi = new Float32Array(FREQ), dPr = new Float32Array(FREQ), dPi = new Float32Array(FREQ)
|
|
502
|
+
let dEx = new Float32Array(NB), dEp = new Float32Array(NB), dExp = new Float32Array(NB)
|
|
503
|
+
// RNNState
|
|
504
|
+
let conv1 = new Float32Array(130), conv2 = new Float32Array(256)
|
|
505
|
+
let h1 = new Float32Array(384), h2 = new Float32Array(384), h3 = new Float32Array(384)
|
|
506
|
+
// scratch
|
|
507
|
+
let x = new Float32Array(FRAME), buf = new Float32Array(WIN)
|
|
508
|
+
let Xr = new Float32Array(FREQ), Xi = new Float32Array(FREQ), Pr = new Float32Array(FREQ), Pi = new Float32Array(FREQ)
|
|
509
|
+
let Ex = new Float32Array(NB), Ep = new Float32Array(NB), Exp = new Float32Array(NB), Ly = new Float32Array(NB)
|
|
510
|
+
let feat = new Float32Array(NF), g = new Float32Array(NB), gf = new Float32Array(FREQ)
|
|
511
|
+
let xlp = new Float32Array(PBUF >> 1), vad = new Float32Array(1)
|
|
512
|
+
let t1 = new Float32Array(128), tmp = new Float32Array(1536), cat = new Float32Array(1536)
|
|
513
|
+
let zrh = new Float32Array(1152), recur = new Float32Array(1152)
|
|
514
|
+
|
|
515
|
+
// apply_window: the 960-point Vorbis window, symmetric
|
|
516
|
+
function window(v) {
|
|
517
|
+
for (let i = 0; i < WIN; i++) v[i] = f(v[i] * WINDOW[i])
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// rnn_compute_frame_features → true when the frame is silent (E < 0.04)
|
|
521
|
+
function features(x) {
|
|
522
|
+
buf.set(analysisMem); buf.set(x, FRAME); analysisMem.set(x)
|
|
523
|
+
window(buf); rfft(buf, Xr, Xi); bandEnergy(Ex, Xr, Xi)
|
|
524
|
+
pitchBuf.copyWithin(0, FRAME); pitchBuf.set(x, PBUF - FRAME)
|
|
525
|
+
pitchDownsample(pitchBuf, xlp, PBUF)
|
|
526
|
+
let idx = removeDoubling(xlp, PMAX - pitchSearch(xlp, PMAX >> 1, xlp, PFRAME, PMAX - 3 * PMIN), lastPeriod, lastGain)
|
|
527
|
+
lastPeriod = idx; lastGain = PG[0]
|
|
528
|
+
for (let i = 0; i < WIN; i++) buf[i] = pitchBuf[PBUF - WIN - idx + i]
|
|
529
|
+
window(buf); rfft(buf, Pr, Pi)
|
|
530
|
+
bandEnergy(Ep, Pr, Pi); bandEnergy(Exp, Xr, Xi, Pr, Pi)
|
|
531
|
+
for (let i = 0; i < NB; i++) Exp[i] = Exp[i] / Math.sqrt(.001 + f(Ex[i] * Ep[i]))
|
|
532
|
+
dct(feat, NB, Exp)
|
|
533
|
+
feat[2 * NB] = .01 * (idx - 300)
|
|
534
|
+
let logMax = -2, follow = -2, E = 0
|
|
535
|
+
for (let i = 0; i < NB; i++) {
|
|
536
|
+
let ly = f(Math.log10(1e-2 + Ex[i])), a = follow - 1.5, b = f(logMax - 7)
|
|
537
|
+
ly = a > ly ? a : ly
|
|
538
|
+
Ly[i] = ly = f(b > ly ? b : ly)
|
|
539
|
+
logMax = logMax > ly ? logMax : ly
|
|
540
|
+
follow = f(a > ly ? a : ly)
|
|
541
|
+
E = f(E + Ex[i])
|
|
542
|
+
}
|
|
543
|
+
if (E < .04) { feat.fill(0); return true }
|
|
544
|
+
dct(feat, 0, Ly)
|
|
545
|
+
feat[0] = f(feat[0] - 12); feat[1] = f(feat[1] - 4)
|
|
546
|
+
return false
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function network() {
|
|
550
|
+
convLayer(net.conv1, t1, conv1, feat, NF, tanh, tmp)
|
|
551
|
+
convLayer(net.conv2, cat, conv2, t1, 128, tanh, tmp)
|
|
552
|
+
gruLayer(net.gru1, h1, cat, zrh, recur)
|
|
553
|
+
gruLayer(net.gru2, h2, h1, zrh, recur)
|
|
554
|
+
gruLayer(net.gru3, h3, h2, zrh, recur)
|
|
555
|
+
cat.set(h1, 384); cat.set(h2, 768); cat.set(h3, 1152)
|
|
556
|
+
computeLinear(net.dense, g, cat)
|
|
557
|
+
for (let i = 0; i < NB; i++) g[i] = sigmoid(g[i])
|
|
558
|
+
computeLinear(net.vad, vad, cat)
|
|
559
|
+
return sigmoid(vad[0])
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// rnn_pitch_filter on the delayed frame: add back the pitch-periodic part the gains would remove
|
|
563
|
+
let r = new Float32Array(NB), rf = new Float32Array(FREQ), newE = new Float32Array(NB), norm = new Float32Array(NB), nf = new Float32Array(FREQ)
|
|
564
|
+
function pitchFilter() {
|
|
565
|
+
rf.fill(0); nf.fill(0)
|
|
566
|
+
for (let i = 0; i < NB; i++) {
|
|
567
|
+
let e = dExp[i], gi = g[i], v
|
|
568
|
+
if (e > gi) v = 1
|
|
569
|
+
else v = f(f(e * e) * f(1 - f(gi * gi))) / (.001 + f(f(gi * gi) * f(1 - f(e * e))))
|
|
570
|
+
v = f(v)
|
|
571
|
+
v = 0 > v ? 0 : v; v = 1 < v ? 1 : v
|
|
572
|
+
r[i] = f(Math.sqrt(v)) * Math.sqrt(dEx[i] / (1e-8 + dEp[i]))
|
|
573
|
+
}
|
|
574
|
+
interpGain(rf, r)
|
|
575
|
+
for (let i = 0; i < FREQ; i++) { dXr[i] = f(dXr[i] + f(rf[i] * dPr[i])); dXi[i] = f(dXi[i] + f(rf[i] * dPi[i])) }
|
|
576
|
+
bandEnergy(newE, dXr, dXi)
|
|
577
|
+
for (let i = 0; i < NB; i++) norm[i] = Math.sqrt(dEx[i] / (1e-8 + newE[i]))
|
|
578
|
+
interpGain(nf, norm)
|
|
579
|
+
for (let i = 0; i < FREQ; i++) { dXr[i] = f(dXr[i] * nf[i]); dXi[i] = f(dXi[i] * nf[i]) }
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// rnnoise_process_frame
|
|
583
|
+
function process(input, output) {
|
|
584
|
+
// rnn_biquad: DC-blocking high-pass, state updated in double, stored as float
|
|
585
|
+
let m0 = memHp[0], m1 = memHp[1]
|
|
586
|
+
for (let i = 0; i < FRAME; i++) {
|
|
587
|
+
let xi = f(input[i]), yi = f(xi + m0)
|
|
588
|
+
m0 = f(m1 + (-2 * xi - F.hpA0 * yi))
|
|
589
|
+
m1 = f(1 * xi - F.hpA1 * yi)
|
|
590
|
+
x[i] = yi
|
|
591
|
+
}
|
|
592
|
+
memHp[0] = m0; memHp[1] = m1
|
|
593
|
+
let p = 0
|
|
594
|
+
if (!features(x)) {
|
|
595
|
+
p = network()
|
|
596
|
+
pitchFilter()
|
|
597
|
+
for (let i = 0; i < NB; i++) {
|
|
598
|
+
let gi = g[i] > f(F.c06 * lastg[i]) ? g[i] : f(F.c06 * lastg[i])
|
|
599
|
+
g[i] = gi
|
|
600
|
+
let v = gi * (dEx[i] + 1e-3) / (Ex[i] + 1e-3)
|
|
601
|
+
lastg[i] = 1 < v ? 1 : v
|
|
602
|
+
}
|
|
603
|
+
gf.fill(0); interpGain(gf, g)
|
|
604
|
+
for (let i = 0; i < FREQ; i++) { dXr[i] = f(dXr[i] * gf[i]); dXi[i] = f(dXi[i] * gf[i]) }
|
|
605
|
+
}
|
|
606
|
+
irfft(dXr, dXi, buf)
|
|
607
|
+
window(buf)
|
|
608
|
+
for (let i = 0; i < FRAME; i++) output[i] = f(buf[i] + synthesisMem[i])
|
|
609
|
+
synthesisMem.set(buf.subarray(FRAME))
|
|
610
|
+
dXr.set(Xr); dXi.set(Xi); dPr.set(Pr); dPi.set(Pi); dEx.set(Ex); dEp.set(Ep); dExp.set(Exp)
|
|
611
|
+
return p
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return { process, latency: 2 * FRAME }
|
|
615
|
+
}
|
package/worklet.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// AudioWorkletProcessor 'neural-denoise': RNNoise on each channel of a 48 kHz AudioContext.
|
|
2
|
+
//
|
|
3
|
+
// await ctx.audioWorklet.addModule(new URL('@audio/neural-denoise/worklet', import.meta.url))
|
|
4
|
+
// let node = new AudioWorkletNode(ctx, 'neural-denoise', { processorOptions: { weights: await weights() } })
|
|
5
|
+
//
|
|
6
|
+
// Render quanta (128 samples) are collected into 10 ms frames (480); the output queue starts with
|
|
7
|
+
// 480 − gcd(quantum, 480) samples of silence, the least that never runs dry, so the node's latency is
|
|
8
|
+
// constant: that plus RNNoise's 960 samples (1408 samples, 29.3 ms, at the 128-sample quantum).
|
|
9
|
+
// processorOptions.limit (dB) caps the attenuation by mixing the input back in: 20 unless given, 0 for
|
|
10
|
+
// none (upstream's output).
|
|
11
|
+
|
|
12
|
+
import { model, create, FRAME, LIMIT } from './rnnoise.js'
|
|
13
|
+
|
|
14
|
+
const gcd = (a, b) => b ? gcd(b, a % b) : a
|
|
15
|
+
|
|
16
|
+
class NeuralDenoise extends AudioWorkletProcessor {
|
|
17
|
+
constructor({ processorOptions: { weights, limit } = {} } = {}) {
|
|
18
|
+
super()
|
|
19
|
+
if (sampleRate !== 48000) throw new Error(`neural-denoise: RNNoise runs at 48 kHz, this context runs at ${sampleRate} Hz; create it with { sampleRate: 48000 }`)
|
|
20
|
+
if (!weights) throw new Error('neural-denoise: pass processorOptions.weights (await weights())')
|
|
21
|
+
this.net = model(weights)
|
|
22
|
+
limit ??= LIMIT
|
|
23
|
+
this.lim = limit ? 10 ** (-Math.abs(limit) / 20) : 0
|
|
24
|
+
this.chans = []
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
channel(c, quantum) {
|
|
28
|
+
let prime = FRAME - gcd(quantum, FRAME), size = FRAME + prime + quantum
|
|
29
|
+
return this.chans[c] ??= {
|
|
30
|
+
st: create(this.net), frame: new Float32Array(FRAME), out: new Float32Array(FRAME), fill: 0,
|
|
31
|
+
// output ring: queued samples between `read` and `read + queued`
|
|
32
|
+
ring: new Float32Array(size), read: 0, queued: prime,
|
|
33
|
+
// dry ring for the attenuation limit, delayed by the node's latency
|
|
34
|
+
dry: new Float32Array(2 * FRAME + prime + quantum), dw: 2 * FRAME + prime, dr: 0,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
process(inputs, outputs) {
|
|
39
|
+
let input = inputs[0], output = outputs[0]
|
|
40
|
+
for (let c = 0; c < output.length; c++) {
|
|
41
|
+
let x = input[c] ?? input[0], y = output[c], n = y.length, s = this.channel(c, n)
|
|
42
|
+
for (let i = 0; i < n; i++) {
|
|
43
|
+
let v = x ? x[i] : 0
|
|
44
|
+
s.frame[s.fill++] = v * 32768
|
|
45
|
+
if (this.lim) { s.dry[s.dw] = v; s.dw = (s.dw + 1) % s.dry.length }
|
|
46
|
+
if (s.fill === FRAME) {
|
|
47
|
+
s.st.process(s.frame, s.out)
|
|
48
|
+
for (let k = 0, w = (s.read + s.queued) % s.ring.length; k < FRAME; k++, w = (w + 1) % s.ring.length) s.ring[w] = s.out[k] / 32768
|
|
49
|
+
s.queued += FRAME; s.fill = 0
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (let i = 0; i < n; i++) {
|
|
53
|
+
let v = s.queued > 0 ? s.ring[s.read] : 0
|
|
54
|
+
if (s.queued > 0) { s.read = (s.read + 1) % s.ring.length; s.queued-- }
|
|
55
|
+
if (this.lim) { v = v * (1 - this.lim) + s.dry[s.dr] * this.lim; s.dr = (s.dr + 1) % s.dry.length }
|
|
56
|
+
y[i] = v
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return true
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
registerProcessor('neural-denoise', NeuralDenoise)
|