nx_denoise_spectral.nx source
↩ module page · 35 lines · 1872 B
1// nx_denoise_spectral.nx -- sovereign SPECTRAL-SUBTRACTION denoise (the RNNoise-class direction: remove broadband
2// noise DURING speech, not just between -- the gap vs Discord's Krisp). FFT the frame, attenuate frequency bins
3// whose power sits below the noise floor (a fraction of the spectral peak), inverse-FFT back. Pure Nishi DSP
4// (composes nx_fft); the browser only ferries raw PCM. Research: RNNoise (Valin, hybrid DSP+GRU band gains),
5// DeepFilterNet. (This rung = the DSP spectral gate; the learned band-gain GRU is a later rung.) license_tier: ORIGINAL
6import "nx_fft.nx"
7
8// estimate the noise-power floor from a NOISE-ONLY frame (VAD-detected silence, like RNNoise): the loudest noise
9// bin x a margin, so real speech (which sits above the noise) survives. re/im = the noise frame (im zeroed).
10func ds_noise_floor(re: *i64, im: *i64, n: i64, margin_q8: i64) -> i64 {
11 nx_fft_forward(re, im, n)
12 let pw: *i64 = sys_mmap(n * 8) as *i64
13 nx_fft_power_spectrum(re, im, n, pw)
14 var peak: i64 = 0
15 var k: i64 = 0
16 while k < n { if pw[k] > peak { peak = pw[k] } k = k + 1 }
17 return peak * margin_q8 / 256
18}
19// attenuate spectral bins whose power is below floor_power (the measured noise floor) by att_q8/256. re/im are
20// the time-domain frame (im zeroed); after the call re holds the denoised frame. n in {2,4,8,16,32}.
21func ds_spectral_gate(re: *i64, im: *i64, n: i64, floor_power: i64, att_q8: i64) -> i64 {
22 nx_fft_forward(re, im, n)
23 let pw: *i64 = sys_mmap(n * 8) as *i64
24 nx_fft_power_spectrum(re, im, n, pw)
25 var k: i64 = 0
26 while k < n {
27 if pw[k] < floor_power { // a noise bin (below the measured floor) -> attenuate, keep phase
28 re[k] = re[k] * att_q8 / 256
29 im[k] = im[k] * att_q8 / 256
30 }
31 k = k + 1
32 }
33 nx_fft_inverse(re, im, n)
34 return 0
35}