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}