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}