code wiki / (root) / nx_rec_grade.nx

nx_rec_grade.nx source

↩ module page · 83 lines · 2858 B

1// nx_rec_grade.nx -- AUDIO arc, voice-recording thread: grade a recording's 2// technical quality so a bad capture is caught BEFORE it pollutes a voicebank / 3// clone. Clipping wrecks the LPC spectral envelope; too-quiet kills SNR; a DC 4// offset biases autocorrelation -- all three corrupt every downstream rung, so 5// the recorder gates its own input. 6// 7// Pure integer, NO floats, NO syscalls, NO imports (operates on a caller buffer). 8// 9// Metrics over i16-LE mono PCM s[0..n): 10// peak = max |s| 11// dc = mean(s) (offset) 12// rms_ac = sqrt( mean(s^2) - dc^2 ) (AC level, offset removed) 13// clip_cnt = #{ |s| >= REC_CLIP } (samples pinned near full-scale) 14// 15// nx_rec_grade(pcm, n, out) -> verdict 16// out[0]=peak out[1]=rms_ac out[2]=dc out[3]=clip_cnt 17// Verdict priority: CLIPPED > DC_BIAS > TOO_QUIET > TOO_LOUD > GOOD. 18// 19// license_tier: ORIGINAL 20// module: nishi-core.audio.rec_grade 21// capability: AUDIO_RECORDING_QUALITY 22 23import "nx_vecmath.nx" 24const REC_GOOD: i64 = 0 25const REC_CLIPPED: i64 = 1 26const REC_DC_BIAS: i64 = 2 27const REC_TOO_QUIET: i64 = 3 28const REC_TOO_LOUD: i64 = 4 29 30const REC_CLIP: i64 = 32000 // |s| at/above this is clipping 31const REC_CLIP_FRAC: i64 = 200 // CLIPPED if clip_cnt > n/200 (0.5%) 32const REC_QUIET_RMS: i64 = 800 // below this AC level = too quiet 33const REC_LOUD_RMS: i64 = 26000 // above this AC level = clip risk 34 35// integer sqrt (Newton, truncated) 36func _rec_isqrt(x: i64) -> i64 { return vm_isqrt(x) } 37 38func _rec_i16(pcm: *u8, i: i64) -> i64 { 39 let lo: i64 = pcm[i * 2] 40 let hi: i64 = pcm[i * 2 + 1] 41 var v: i64 = lo | (hi << 8) 42 if v >= 0x8000 { v = v - 0x10000 } 43 return v 44} 45 46func nx_rec_grade(pcm: *u8, n: i64, out: *i64) -> i64 { 47 if n < 1 { out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 0; return REC_TOO_QUIET } 48 var sum: i64 = 0 49 var sumsq: i64 = 0 50 var peak: i64 = 0 51 var clip: i64 = 0 52 var i: i64 = 0 53 while i < n { 54 let s: i64 = _rec_i16(pcm, i) 55 sum = sum + s 56 sumsq = sumsq + s * s 57 var a: i64 = s 58 if a < 0 { a = 0 - a } 59 if a > peak { peak = a } 60 if a >= REC_CLIP { clip = clip + 1 } 61 i = i + 1 62 } 63 let dc: i64 = sum / n 64 let meansq: i64 = sumsq / n 65 var var_ac: i64 = meansq - dc * dc 66 if var_ac < 0 { var_ac = 0 } 67 let rms_ac: i64 = _rec_isqrt(var_ac) 68 69 out[0] = peak 70 out[1] = rms_ac 71 out[2] = dc 72 out[3] = clip 73 74 var adc: i64 = dc 75 if adc < 0 { adc = 0 - adc } 76 77 // priority order 78 if clip * REC_CLIP_FRAC > n { return REC_CLIPPED } 79 if adc * 2 > rms_ac { return REC_DC_BIAS } // offset comparable to signal swing 80 if rms_ac < REC_QUIET_RMS { return REC_TOO_QUIET } 81 if rms_ac > REC_LOUD_RMS { return REC_TOO_LOUD } 82 return REC_GOOD 83}