code wiki / (root) / nx_denoise_spectral.nx

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}