@audio/denoise-detect 0.1.3 → 0.1.5
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/README.md +39 -0
- package/denoise.js +28 -11
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @audio/denoise-detect [](https://www.npmjs.com/package/@audio/denoise-detect) [](https://github.com/krishnized/license)
|
|
2
|
+
|
|
3
|
+
denoise — content-aware auto-selector that classifies the dominant noise type
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
npm install @audio/denoise-detect
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import denoise from '@audio/denoise-detect'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Content-aware auto-selector. Runs a single STFT classification sweep over the input and dispatches to the most suitable method.
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
denoise(data) // → cleaned Float32Array
|
|
17
|
+
denoise(data, { returnPlan: true }) // → { out, plan }
|
|
18
|
+
denoise(data, { force: 'wiener' }) // skip classifier
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
| Param | Default | |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `fs` | `44100` | Sample rate |
|
|
24
|
+
| `force` | — | One of `'dehum' \| 'declick' \| 'dewind' \| 'deesser' \| 'dereverb' \| 'omlsa' \| 'wiener'` |
|
|
25
|
+
| `returnPlan` | `false` | Return `{ out, plan }` with classifier scores + chosen method |
|
|
26
|
+
|
|
27
|
+
**Routing (in priority order):**
|
|
28
|
+
1. tonal hum (Goertzel — ≥2 of first 3 harmonics show 50× line/off-line ratio at 50 or 60 Hz)
|
|
29
|
+
2. impulses (excess kurtosis of AR residual > 12)
|
|
30
|
+
3. sibilance (high/mid band power ratio > 8)
|
|
31
|
+
4. LF rumble (low/mid band power ratio > 3)
|
|
32
|
+
5. non-stationary noise (frame-energy CV > 0.6) → omlsa
|
|
33
|
+
6. otherwise → wiener
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
Part of [@audio/denoise](https://github.com/audiojs/denoise) — the denoise family umbrella. This README is generated from the umbrella docs.
|
|
38
|
+
|
|
39
|
+
MIT © [audiojs](https://github.com/audiojs)
|
package/denoise.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// - click — high kurtosis of AR residual → declick
|
|
7
7
|
// - hi — 5–9 kHz / mid energy ratio → deesser
|
|
8
8
|
// - lf — LF/mid energy ratio → dewind
|
|
9
|
-
// -
|
|
9
|
+
// - stationarity — frame-energy floor CV: stable → wiener, wandering → omlsa
|
|
10
10
|
// - otherwise → wiener (transparent broadband)
|
|
11
11
|
//
|
|
12
12
|
// dereverb has no reliable single-pass signature, so auto-mode never selects it —
|
|
@@ -50,18 +50,19 @@ export function classify(data, fs = 44100) {
|
|
|
50
50
|
let frameVar = [] // for stationarity
|
|
51
51
|
|
|
52
52
|
stftAnalyse(data, mag => {
|
|
53
|
-
let lf = 0, mf = 0, hi = 0
|
|
53
|
+
let lf = 0, mf = 0, hi = 0, tot = 0
|
|
54
54
|
for (let k = 0; k <= half; k++) {
|
|
55
55
|
let p = mag[k] * mag[k]
|
|
56
56
|
bins[k] += p
|
|
57
|
+
tot += p
|
|
57
58
|
let f = k * fs / N
|
|
58
59
|
if (f < 200) lf += p
|
|
59
60
|
else if (f < 2000) mf += p
|
|
60
61
|
else if (f >= 5000 && f < 9000) hi += p // sibilance band, kept specific (not 2–9 kHz)
|
|
61
62
|
}
|
|
62
63
|
lfSum += lf; mfSum += mf; hiSum += hi
|
|
63
|
-
frameVar.push(
|
|
64
|
-
frames++
|
|
64
|
+
frameVar.push(tot) // full-spectrum energy — band-restricted
|
|
65
|
+
frames++ // sums under-average the floor statistic
|
|
65
66
|
}, { frameSize: N, hopSize: hop })
|
|
66
67
|
if (!frames) return { method: 'wiener', scores: {} }
|
|
67
68
|
|
|
@@ -118,27 +119,43 @@ export function classify(data, fs = 44100) {
|
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
// Noise stationarity: CV of the frame-energy FLOOR (rolling minimum over ~0.75 s).
|
|
123
|
+
// Speech dynamics ride above the floor, so the floor tracks the *noise bed*:
|
|
124
|
+
// stationary noise → stable floor (CV ≈ 0.06 measured on speech+white), babble /
|
|
125
|
+
// wandering beds → drifting floor (CV ≈ 0.5). Raw frame-energy CV can't make this
|
|
126
|
+
// call — speech's own variance trips it regardless of the noise.
|
|
127
|
+
let floorCV = 0
|
|
128
|
+
{
|
|
129
|
+
let D = 64, step = 16, floors = []
|
|
130
|
+
for (let i = D; i < frames; i += step) {
|
|
131
|
+
let mn = Infinity
|
|
132
|
+
for (let j = i - D; j < i; j++) if (frameVar[j] < mn) mn = frameVar[j]
|
|
133
|
+
floors.push(mn)
|
|
134
|
+
}
|
|
135
|
+
if (floors.length >= 3) {
|
|
136
|
+
let m = 0; for (let f of floors) m += f; m /= floors.length
|
|
137
|
+
let v = 0; for (let f of floors) v += (f - m) ** 2; v /= floors.length
|
|
138
|
+
floorCV = m > 0 ? Math.sqrt(v) / m : 0
|
|
139
|
+
}
|
|
140
|
+
}
|
|
125
141
|
|
|
126
142
|
let scores = {
|
|
127
143
|
hum: humBest, humFreq,
|
|
128
144
|
click: clickScore,
|
|
129
145
|
lf: lfRatio,
|
|
130
146
|
hi: hiRatio,
|
|
131
|
-
|
|
147
|
+
stationarity: floorCV // low = stationary noise bed
|
|
132
148
|
}
|
|
133
149
|
|
|
134
|
-
// Priority: tonal hum > impulses > sibilance > rumble >
|
|
150
|
+
// Priority: tonal hum > impulses > sibilance > rumble > stationary → wiener,
|
|
151
|
+
// non-stationary → omlsa (IMCRA keeps adapting where a frozen profile can't).
|
|
135
152
|
// humBest is a hit count: ≥2 of the first 3 harmonics show 20× peak-to-median sharpness.
|
|
136
153
|
let method = 'wiener'
|
|
137
154
|
if (humBest >= 2) method = 'dehum'
|
|
138
155
|
else if (clickScore > 12) method = 'declick'
|
|
139
156
|
else if (hiRatio > 8) method = 'deesser' // white noise scores ~3.9 by bandwidth alone
|
|
140
157
|
else if (lfRatio > 3) method = 'dewind'
|
|
141
|
-
else if (
|
|
158
|
+
else if (floorCV > 0.3) method = 'omlsa' // white ~0.06 · rumble ~0.2 · babble ~0.5
|
|
142
159
|
|
|
143
160
|
return { method, scores, humFreq }
|
|
144
161
|
}
|