code wiki / (root) / nx_audio_enhance.nx

nx_audio_enhance.nx source

↩ module page · 80 lines · 3489 B

1// nx_audio_enhance.nx -- sovereign AUDIO ENHANCEMENT for the call mic path: AGC (auto gain control -> boosts a 2// quiet/bad mic to a healthy level, with a hard limiter so it never clips) + an adaptive VAD NOISE GATE (estimates 3// the noise floor, attenuates frames that are just background hiss between speech). This is the "top-of-the-line 4// audio even with a bad mic" path, pure Nishi -- the browser only ferries raw PCM; ALL DSP is here. Operates on 5// signed PCM samples in an i64 array (-32767..32767). Spectral subtraction via nx_fft = the next rung (in-speech 6// denoise); this rung delivers the two biggest bad-mic wins (level + background gating). license_tier: ORIGINAL 7 8const AE_FULL: i64 = 32767 9 10// integer sqrt (Newton) for RMS without an FPU 11func ae_isqrt(v: i64) -> i64 { 12 if v <= 0 { return 0 } 13 var x: i64 = v 14 var y: i64 = (x + 1) / 2 15 var go: i64 = 1 16 while go == 1 { if y < x { x = y; y = (x + v / x) / 2 } else { go = 0 } } 17 return x 18} 19// RMS level of n samples 20func ae_rms(s: *i64, n: i64) -> i64 { 21 if n <= 0 { return 0 } 22 var sum: i64 = 0 23 var i: i64 = 0 24 while i < n { sum = sum + s[i] * s[i]; i = i + 1 } 25 return ae_isqrt(sum / n) 26} 27// mean-square energy of a frame [base, base+FL) 28func ae_frame_energy(s: *i64, base: i64, FL: i64) -> i64 { 29 var sum: i64 = 0 30 var i: i64 = 0 31 while i < FL { let v: i64 = s[base + i]; sum = sum + v * v; i = i + 1 } 32 return sum / FL 33} 34// AGC: scale the signal toward target_rms, capped at max_gain (x100) so a bad mic is boosted but background 35// noise isn't blown up; hard-limited to +/-FULL so it never clips. returns the applied gain (x100). 36func ae_agc(s: *i64, n: i64, target_rms: i64, max_gain100: i64) -> i64 { 37 let rms: i64 = ae_rms(s, n) 38 if rms < 1 { return 100 } // silence -> leave it 39 var g100: i64 = target_rms * 100 / rms 40 if g100 > max_gain100 { g100 = max_gain100 } 41 if g100 < 10 { g100 = 10 } 42 var i: i64 = 0 43 while i < n { 44 var v: i64 = s[i] * g100 / 100 45 if v > AE_FULL { v = AE_FULL } 46 if v < 0 - AE_FULL { v = 0 - AE_FULL } 47 s[i] = v 48 i = i + 1 49 } 50 return g100 51} 52// adaptive noise gate: attenuate frames whose energy is well BELOW the average (background hiss between speech). 53// MEAN-relative (not floor-relative) so a uniform/steady signal is never gated -- only frames < mean/div are noise. 54// returns the number of frames gated. 55func ae_noise_gate(s: *i64, n: i64, FL: i64, div: i64, att100: i64) -> i64 { 56 let nf: i64 = n / FL 57 if nf <= 0 { return 0 } 58 var sum: i64 = 0 59 var f: i64 = 0 60 while f < nf { sum = sum + ae_frame_energy(s, f*FL, FL); f = f + 1 } 61 let mean: i64 = sum / nf 62 var gated: i64 = 0 63 f = 0 64 while f < nf { 65 let e: i64 = ae_frame_energy(s, f*FL, FL) 66 if e * div < mean { // frame well below average -> background noise -> attenuate 67 var i: i64 = f * FL 68 let endi: i64 = i + FL 69 while i < endi { s[i] = s[i] * att100 / 100; i = i + 1 } 70 gated = gated + 1 71 } 72 f = f + 1 73 } 74 return gated 75} 76// full enhancement: gate the background first, then AGC-boost the (now cleaner) speech. 77func ae_enhance(s: *i64, n: i64, FL: i64, target_rms: i64, max_gain100: i64) -> i64 { 78 ae_noise_gate(s, n, FL, 4, 12) // frames below mean/4 energy -> background noise -> attenuate to 12% 79 return ae_agc(s, n, target_rms, max_gain100) 80}