code wiki / _hdl_build / nx_asr_frontend_gate.nx

nx_asr_frontend_gate.nx source

↩ module page · 123 lines · 6716 B

1// nx_asr_frontend_gate.nx -- proves THE DEEP rung 1 (ASR acoustic front-end), NO fake green. Synthesizes EXACT 2// integer tones from the nx_fft twiddle table (a tone at bin k = cos(2*pi*k*i/N)); the front-end must (1) recover 3// the right peak bin for two different tones, (2) place two well-separated tones in DIFFERENT filterbank bands, 4// and NEG-CONTROL (3) report ~zero energy for silence and (4) clearly non-zero energy for a tone (VAD seed). 5// Honest: this is the FRONT-END only, not recognition. expect_exit: 0 license_tier: ORIGINAL 6import "nx_syscalls.nx" 7import "nx_asr_frontend.nx" 8import "nx_gate_verdict.nx" 9 10func ag(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 11func agn(v: i64) -> i64 { 12 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } 13 var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 14 let d: *u8 = sys_mmap(24); var k: i64 = 0 15 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 16 var j: i64 = k - 1 17 while j >= 0 { sys_write(1, ((d as i64)+j) as *u8, 1); j = j - 1 } 18 return 0 19} 20// cos(2*pi*m/32) in Q14 from the 16-entry twiddle table (cos is even + period 32) 21func cos_lut(twr: *i64, m: i64) -> i64 { 22 var x: i64 = m & 31 23 if x < 16 { return twr[x] } 24 if x == 16 { return 0 - 16384 } 25 return twr[32 - x] 26} 27// synthesize an N=32 pure tone at bin k into pcm 28func syn(twr: *i64, k: i64, pcm: *i64, n: i64) -> i64 { 29 var i: i64 = 0 30 while i < n { pcm[i] = cos_lut(twr, k * i); i = i + 1 } 31 return 0 32} 33 34func main() -> i64 { 35 ag("=== nx_asr_frontend_gate: THE DEEP rung 1 -- PCM frame -> spectrum -> filterbank features ===\n" as *u8) 36 let N: i64 = 32 37 let twr: *i64 = sys_mmap(16 * 8) as *i64 38 let twi: *i64 = sys_mmap(16 * 8) as *i64 39 af_tw(twr, twi) 40 41 let pcm: *i64 = sys_mmap(N * 8) as *i64 42 let power: *i64 = sys_mmap(N * 8) as *i64 43 let nfilt: i64 = 4 44 let en: *i64 = sys_mmap(nfilt * 8) as *i64 45 46 var pass: i64 = 0 47 var fail: i64 = 0 48 49 // (1) peak-bin recovery: tone at bin 4 50 syn(twr, 4, pcm, N); nx_asr_power(pcm, N, power) 51 let pb4: i64 = nx_asr_peak_bin(power, N) 52 ag(" tone bin=4 -> peak bin=" as *u8); agn(pb4) 53 if pb4 == 4 { pass=pass+1; ag(" GREEN\n" as *u8) } else { fail=fail+1; ag(" RED\n" as *u8) } 54 55 // tone at bin 7 56 syn(twr, 7, pcm, N); nx_asr_power(pcm, N, power) 57 let pb7: i64 = nx_asr_peak_bin(power, N) 58 ag(" tone bin=7 -> peak bin=" as *u8); agn(pb7) 59 if pb7 == 7 { pass=pass+1; ag(" GREEN\n" as *u8) } else { fail=fail+1; ag(" RED\n" as *u8) } 60 61 // (2) two separated tones land in DIFFERENT filterbank bands 62 syn(twr, 2, pcm, N); nx_asr_power(pcm, N, power); nx_asr_filterbank(power, N, nfilt, en) 63 let d2: i64 = nx_asr_dom_band(en, nfilt) 64 let e2: i64 = nx_asr_total_energy(en, nfilt) 65 syn(twr, 14, pcm, N); nx_asr_power(pcm, N, power); nx_asr_filterbank(power, N, nfilt, en) 66 let d14: i64 = nx_asr_dom_band(en, nfilt) 67 ag(" tone bin=2 -> band=" as *u8); agn(d2); ag(" ; tone bin=14 -> band=" as *u8); agn(d14); ag("\n" as *u8) 68 if d2 == 0 { pass=pass+1 } else { fail=fail+1; ag(" RED band2\n" as *u8) } 69 if d14 == 3 { pass=pass+1 } else { fail=fail+1; ag(" RED band14\n" as *u8) } 70 if d2 != d14 { pass=pass+1; ag(" ROW distinct-bands: GREEN\n" as *u8) } else { fail=fail+1; ag(" ROW distinct-bands: RED\n" as *u8) } 71 72 // (3) NEG-CONTROL: silence -> ~zero energy 73 var z: i64 = 0 74 while z < N { pcm[z] = 0; z = z + 1 } 75 nx_asr_power(pcm, N, power); nx_asr_filterbank(power, N, nfilt, en) 76 let esil: i64 = nx_asr_total_energy(en, nfilt) 77 ag(" silence -> total energy=" as *u8); agn(esil) 78 if esil == 0 { pass=pass+1; ag(" ROW silence-zero: GREEN\n" as *u8) } else { fail=fail+1; ag(" ROW silence-zero: RED\n" as *u8) } 79 80 // (4) tone energy clearly non-zero (VAD seed: tone >> silence) 81 if e2 > 0 { pass=pass+1; ag(" ROW tone-has-energy: GREEN (" as *u8); agn(e2); ag(")\n" as *u8) } else { fail=fail+1; ag(" ROW tone-has-energy: RED\n" as *u8) } 82 83 // ---- MUTATION-DERIVED TOOTH: DFT CONJUGATE SYMMETRY (2026-08-01, debt 1785604588) ---- 84 // nx_gate_mutation_probe scored this pair 3/4 with a survivor at nx_asr_frontend.nx:77 -- 85 // `im[k + j + m] = u_i - t_i`, the IMAGINARY output of the FFT butterfly. The mutant corrupts that 86 // write index. It survived because every tooth above is MAGNITUDE-based: peak bin, filterbank band 87 // energies, silence-is-zero, tone-has-energy. Power is re*re + im*im and for these test tones the 88 // real part dominates, so a scrambled imaginary array still leaves the peak in the right bin and 89 // every existing row still passes. 90 // LAW: A MAGNITUDE TEST CANNOT SEE A PHASE ERROR. Checking |X| discards exactly the component the 91 // imaginary array carries, so half the transform was unverified. 92 // The invariant used is CONJUGATE SYMMETRY: for a REAL input signal the DFT satisfies 93 // X[N-k] = conj(X[k]), i.e. re[N-k] == re[k] AND im[N-k] == -im[k]. It is the right tooth here for 94 // the same reason eyeball-colour symmetry was right earlier: it needs no hard-coded expected value, 95 // so it cannot rot, and it fails the instant either half of the transform is written to the wrong 96 // index. 97 let cre: *i64 = sys_mmap(N * 8) as *i64 98 let cim: *i64 = sys_mmap(N * 8) as *i64 99 syn(twr, 5, pcm, N) // a real-valued tone; bin 5 is not used by any row above 100 var ci: i64 = 0 101 while ci < N { cre[ci] = pcm[ci]; cim[ci] = 0; ci = ci + 1 } 102 af_fwd(cre, cim, N) 103 var sym_ok: i64 = 1 104 var kk: i64 = 1 105 while kk < N / 2 { 106 if cre[N - kk] != cre[kk] { sym_ok = 0 } 107 if cim[N - kk] != (0 - cim[kk]) { sym_ok = 0 } 108 kk = kk + 1 109 } 110 if sym_ok == 1 { pass = pass + 1; ag(" ROW conj-symmetry (real input => im[N-k] == -im[k]): GREEN\n" as *u8) } 111 if sym_ok == 0 { fail = fail + 1; ag(" ROW conj-symmetry: RED -- the imaginary half of the transform is wrong\n" as *u8) } 112 113 ag("ASR-FRONTEND-GATE pass=" as *u8); agn(pass); ag(" fail=" as *u8); agn(fail) 114 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 115 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 116 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 117 let ctr__dry: *i64 = gv_ctr() 118 ctr__dry[0] = pass 119 ctr__dry[1] = pass + fail 120 let rc__dry: i64 = gv_verdict("ASR-FRONTEND-GATE" as *u8, ctr__dry, "PCM->FFT->filterbank features recover tone bin + band; silence=0; tone>0 (the front-end, not yet recognition)" as *u8) 121 sys_exit(rc__dry) 122 return rc__dry 123}