code wiki / _hdl_build / nx_aec_dtd_gate.nx
nx_aec_dtd_gate.nx source
↩ module page · 68 lines · 4484 B
1// nx_aec_dtd_gate.nx -- proves the Geigel DOUBLE-TALK DETECTOR (aec_run_dtd) beats plain NLMS during double-talk.
2// MEASURED head-to-head: same FAST step (mu~0.75, the one that diverged in nx_aec_gate test 2), same realistic signal
3// (far-end talks ALONE first so the filter converges, THEN near-end joins = double-talk). The DTD freezes adaptation
4// while the near-end speaks, so the converged filter is preserved; plain NLMS mis-adapts and lets echo leak through.
5import "nx_syscalls.nx"
6import "nx_gate_emit_lib.nx"
7import "nx_aec.nx"
8
9func energy(arr: *i64, from: i64, to: i64) -> i64 { var s: i64=0; var i: i64=from; while i<to { s = s + arr[i]*arr[i]; i=i+1 } return s }
10// residual ECHO energy = energy of (out - near) over [from,to) (out should equal near once echo is cancelled)
11func resid_echo(out: *i64, near: *i64, from: i64, to: i64) -> i64 {
12 var s: i64=0; var i: i64=from; while i<to { let d: i64 = out[i]-near[i]; s = s + d*d; i=i+1 } return s
13}
14
15func main() -> i64 {
16 g_puts("nx_aec DTD gate (Geigel double-talk detector, MEASURED head-to-head)\n" as *u8)
17 var pass: i64 = 0; var total: i64 = 0
18 let n: i64 = 400; let L: i64 = 8; let D: i64 = 3; let HALF: i64 = 200 // near-end joins at sample 200
19 let FAST: i64 = 8192 // mu~0.25 -- the sweet spot: cleanest convergence + best DTD separation
20 // (mu0.5/0.75 overshoot -> smear taps -> the canceller itself works worse)
21 let ref: *i64 = sys_mmap(n*8) as *i64
22 let mic: *i64 = sys_mmap(n*8) as *i64
23 let near: *i64 = sys_mmap(n*8) as *i64
24 let out: *i64 = sys_mmap(n*8) as *i64
25 let w: *i64 = sys_mmap(L*8) as *i64
26
27 // far-end reference (broadband) all the way; near-end SILENT for first half, then speaks (realistic double-talk onset)
28 var i: i64=0
29 while i<n {
30 ref[i] = ((i*37) % 200) - 100
31 var nr: i64 = 0
32 if i >= HALF { nr = ((i*61) % 180) - 90 } // near-end voice LOUDER than the echo (realistic: your mic > far echo)
33 near[i] = nr
34 var e: i64 = 0; if i-D>=0 { e = ref[i-D] / 2 } // echo path: half-amplitude, delay 3
35 mic[i] = nr + e
36 i=i+1
37 }
38 let near_e: i64 = energy(near, 300, 400) // near-voice energy in the double-talk window
39
40 // A) plain NLMS, FAST step (no double-talk protection)
41 i=0; while i<L { w[i]=0; i=i+1 }
42 aec_run(mic, ref, n, L, FAST, w, out)
43 let resid_plain: i64 = resid_echo(out, near, 300, 400)
44
45 // B) NLMS + Geigel DTD, SAME fast step (freeze adaptation when |mic| > 0.75*max|ref|)
46 i=0; while i<L { w[i]=0; i=i+1 }
47 aec_run_dtd(mic, ref, n, L, FAST, w, out, 192, 64) // 192/256 = 0.75 threshold, 64-sample hangover
48 let resid_dtd: i64 = resid_echo(out, near, 300, 400)
49
50 // DEBUG: dump converged filter taps + a few out/near samples to find the root cause
51 g_puts(" [debug] DTD filter taps w[0..L]: " as *u8)
52 i=0; while i<L { let wv: i64 = w[i]; if wv<0 { g_puts("-" as *u8); g_pn(0-wv) } else { g_pn(wv) } g_puts(" " as *u8); i=i+1 }
53 g_puts("(tap3 should be ~16384=0.5)\n" as *u8)
54 g_puts(" [debug] out vs near @300..305: " as *u8)
55 i=300; while i<306 { g_puts("(" as *u8); let ov: i64=out[i]; if ov<0 { g_puts("-" as *u8); g_pn(0-ov) } else { g_pn(ov) } g_puts("/" as *u8); let nv: i64=near[i]; if nv<0 { g_puts("-" as *u8); g_pn(0-nv) } else { g_pn(nv) } g_puts(") " as *u8); i=i+1 }
56 g_puts("\n" as *u8)
57
58 g_puts(" [measure] double-talk window: near-voice energy=" as *u8); g_pn(near_e); g_puts("\n" as *u8)
59 g_puts(" [measure] residual echo PLAIN-NLMS(fast)=" as *u8); g_pn(resid_plain); g_puts(" +Geigel-DTD(fast)=" as *u8); g_pn(resid_dtd); g_puts("\n" as *u8)
60
61 pass = pass + g_check("DTD beats plain NLMS during double-talk (less residual echo)" as *u8, resid_dtd < resid_plain); total=total+1
62 pass = pass + g_check("DTD residual echo << near voice (echo cancelled, voice preserved)" as *u8, resid_dtd * 4 < near_e); total=total+1
63 pass = pass + g_check("DTD measurably cuts double-talk residual echo (plain leaks >= 25% more)" as *u8, resid_plain * 4 > resid_dtd * 5); total=total+1
64
65 g_puts("---- aec DTD gate: passed " as *u8); g_pn(pass); g_puts(" / " as *u8); g_pn(total); g_puts(" ----\n" as *u8)
66 if pass == total { g_puts("verdict=GREEN\n" as *u8); sys_exit(0); return 0 }
67 g_puts("verdict=RED\n" as *u8); sys_exit(1); return 1
68}