code wiki / (root) / nx_audio_enhance.nx

nx_audio_enhance.nx source

↩ module page · 74 lines · 3345 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 8import "nx_vecmath.nx" 9const AE_FULL: i64 = 32767 10 11// integer sqrt (Newton) for RMS without an FPU 12func ae_isqrt(v: i64) -> i64 { return vm_isqrt(v) } 13// RMS level of n samples 14func ae_rms(s: *i64, n: i64) -> i64 { 15 if n <= 0 { return 0 } 16 var sum: i64 = 0 17 var i: i64 = 0 18 while i < n { sum = sum + s[i] * s[i]; i = i + 1 } 19 return ae_isqrt(sum / n) 20} 21// mean-square energy of a frame [base, base+FL) 22func ae_frame_energy(s: *i64, base: i64, FL: i64) -> i64 { 23 var sum: i64 = 0 24 var i: i64 = 0 25 while i < FL { let v: i64 = s[base + i]; sum = sum + v * v; i = i + 1 } 26 return sum / FL 27} 28// AGC: scale the signal toward target_rms, capped at max_gain (x100) so a bad mic is boosted but background 29// noise isn't blown up; hard-limited to +/-FULL so it never clips. returns the applied gain (x100). 30func ae_agc(s: *i64, n: i64, target_rms: i64, max_gain100: i64) -> i64 { 31 let rms: i64 = ae_rms(s, n) 32 if rms < 1 { return 100 } // silence -> leave it 33 var g100: i64 = target_rms * 100 / rms 34 if g100 > max_gain100 { g100 = max_gain100 } 35 if g100 < 10 { g100 = 10 } 36 var i: i64 = 0 37 while i < n { 38 var v: i64 = s[i] * g100 / 100 39 if v > AE_FULL { v = AE_FULL } 40 if v < 0 - AE_FULL { v = 0 - AE_FULL } 41 s[i] = v 42 i = i + 1 43 } 44 return g100 45} 46// adaptive noise gate: attenuate frames whose energy is well BELOW the average (background hiss between speech). 47// MEAN-relative (not floor-relative) so a uniform/steady signal is never gated -- only frames < mean/div are noise. 48// returns the number of frames gated. 49func ae_noise_gate(s: *i64, n: i64, FL: i64, div: i64, att100: i64) -> i64 { 50 let nf: i64 = n / FL 51 if nf <= 0 { return 0 } 52 var sum: i64 = 0 53 var f: i64 = 0 54 while f < nf { sum = sum + ae_frame_energy(s, f*FL, FL); f = f + 1 } 55 let mean: i64 = sum / nf 56 var gated: i64 = 0 57 f = 0 58 while f < nf { 59 let e: i64 = ae_frame_energy(s, f*FL, FL) 60 if e * div < mean { // frame well below average -> background noise -> attenuate 61 var i: i64 = f * FL 62 let endi: i64 = i + FL 63 while i < endi { s[i] = s[i] * att100 / 100; i = i + 1 } 64 gated = gated + 1 65 } 66 f = f + 1 67 } 68 return gated 69} 70// full enhancement: gate the background first, then AGC-boost the (now cleaner) speech. 71func ae_enhance(s: *i64, n: i64, FL: i64, target_rms: i64, max_gain100: i64) -> i64 { 72 ae_noise_gate(s, n, FL, 4, 12) // frames below mean/4 energy -> background noise -> attenuate to 12% 73 return ae_agc(s, n, target_rms, max_gain100) 74}