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}