@audio/denoise-detect 0.1.3 → 0.1.4

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/denoise.js +28 -11
  2. package/package.json +1 -1
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
- // - nonstationarity — frame-energy CV → omlsa
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(lf + mf + hi)
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
- // Stationarity: variance-of-variances of frame energy
122
- let mean = 0; for (let v of frameVar) mean += v; mean /= frames
123
- let varE = 0; for (let v of frameVar) varE += (v - mean) ** 2; varE /= frames
124
- let cv = mean > 0 ? Math.sqrt(varE) / mean : 0 // higher = less stationary
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
- nonstationarity: cv
147
+ stationarity: floorCV // low = stationary noise bed
132
148
  }
133
149
 
134
- // Priority: tonal hum > impulses > sibilance > rumble > non-stationary > broadband.
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 (cv > 0.6) method = 'omlsa'
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audio/denoise-detect",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "denoise — content-aware auto-selector that classifies the dominant noise type",
5
5
  "type": "module",
6
6
  "sideEffects": false,