code wiki / _hdl_build / nx_jitterbuf_gate.nx

nx_jitterbuf_gate.nx source

↩ module page · 88 lines · 4829 B

1// nx_jitterbuf_gate.nx -- proves + MEASURES the sovereign adaptive playout-delay (nx_jitterbuf): 2// the interp buffer sizes itself to MEASURED jitter so a bad-mobile link stays smooth without 3// needless latency on a good link. 4// 1) LOW jitter (regular arrivals) -> jitter ~0, playout near min (no needless latency) -- measured 5// 2) HIGH jitter (irregular arrivals) -> jitter rises, playout GROWS to cover it -- measured 6// 3) GROW FAST / SHRINK SLOW: a spike lifts playout immediately, then it decays gradually (not instantly) 7// 4) clamp (neg): playout always within [min,max] 8// Built nx_cc_sovereign -> nxasm_x86 (no gcc, no .sh). license_tier: ORIGINAL 9import "nx_syscalls.nx" 10import "nx_gate_emit_lib.nx" 11import "nx_jitterbuf.nx" 12import "nx_gate_verdict.nx" 13 14func main() -> i64 { 15 g_puts("nx_jitterbuf gate (sovereign adaptive playout delay sized to measured jitter)\n" as *u8) 16 var pass: i64 = 0; var total: i64 = 0 17 18 let SI: i64 = 100; let MIN: i64 = 100; let MAX: i64 = 1000 19 let lo: *i64 = sys_mmap(8*8) as *i64 20 let hi: *i64 = sys_mmap(8*8) as *i64 21 22 // 1) LOW jitter: perfectly regular arrivals every 100ms 23 jb_init(lo, SI, MIN, MAX) 24 var t: i64 = 1000 25 var clamp_ok: i64 = 1 26 var n: i64 = 0 27 while n < 12 { 28 let pd: i64 = jb_on_recv(lo, t) 29 if pd < MIN { clamp_ok = 0 } 30 if pd > MAX { clamp_ok = 0 } 31 t = t + 100 32 n = n + 1 33 } 34 let lo_jit: i64 = jb_jitter(lo); let lo_pd: i64 = jb_playout(lo) 35 g_puts(" [measure] LOW jitter: jitter=" as *u8); g_pn(lo_jit); g_puts("ms playout=" as *u8); g_pn(lo_pd); g_puts("ms (near min " as *u8); g_pn(MIN); g_puts(")\n" as *u8) 36 pass = pass + g_check("low jitter -> playout near min (no needless latency)" as *u8, lo_pd <= MIN + 20); total=total+1 37 38 // 2) HIGH jitter: irregular arrivals (alternating early/late around the 100ms cadence) 39 jb_init(hi, SI, MIN, MAX) 40 let arr: *i64 = sys_mmap(16*8) as *i64 41 arr[0]=1000; arr[1]=1180; arr[2]=1200; arr[3]=1420; arr[4]=1440; arr[5]=1700; arr[6]=1720 42 arr[7]=2050; arr[8]=2070; arr[9]=2400; arr[10]=2420; arr[11]=2800 43 n = 0 44 while n < 12 { 45 let pd: i64 = jb_on_recv(hi, arr[n]) 46 if pd < MIN { clamp_ok = 0 } 47 if pd > MAX { clamp_ok = 0 } 48 n = n + 1 49 } 50 let hi_jit: i64 = jb_jitter(hi); let hi_pd: i64 = jb_playout(hi) 51 g_puts(" [measure] HIGH jitter: jitter=" as *u8); g_pn(hi_jit); g_puts("ms playout=" as *u8); g_pn(hi_pd); g_puts("ms\n" as *u8) 52 var r2: i64 = 1 53 if hi_jit <= lo_jit { r2 = 0 } 54 if hi_pd <= lo_pd { r2 = 0 } // sized UP to cover the jitter 55 pass = pass + g_check("high jitter -> larger jitter estimate AND larger playout than low" as *u8, r2); total=total+1 56 57 // 3) GROW FAST / SHRINK SLOW 58 let sp: *i64 = sys_mmap(8*8) as *i64 59 jb_init(sp, SI, MIN, MAX) 60 jb_on_recv(sp, 1000); jb_on_recv(sp, 1100); jb_on_recv(sp, 1200) // steady -> playout ~min 61 let before: i64 = jb_playout(sp) 62 let after_spike: i64 = jb_on_recv(sp, 1500) // a 300ms-late arrival (gap 300 vs 100) 63 // then steady again; watch it decay GRADUALLY 64 jb_on_recv(sp, 1600); jb_on_recv(sp, 1700); jb_on_recv(sp, 1800) 65 let after_3: i64 = jb_playout(sp) 66 jb_on_recv(sp, 1900); jb_on_recv(sp, 2000); jb_on_recv(sp, 2100); jb_on_recv(sp, 2200); jb_on_recv(sp, 2300) 67 let after_8: i64 = jb_playout(sp) 68 g_puts(" [measure] spike: before=" as *u8); g_pn(before); g_puts(" after-spike=" as *u8); g_pn(after_spike); g_puts(" +3=" as *u8); g_pn(after_3); g_puts(" +8=" as *u8); g_pn(after_8); g_puts("\n" as *u8) 69 var r3: i64 = 1 70 if after_spike <= before { r3 = 0 } // grew immediately on the spike 71 if after_3 <= before { r3 = 0 } // still elevated a few samples later (slow shrink) 72 if after_8 >= after_3 { r3 = 0 } // and trending back down 73 pass = pass + g_check("grow-fast on spike, shrink-slow as link calms" as *u8, r3); total=total+1 74 75 // 4) clamp 76 pass = pass + g_check("playout always within [min,max] (clamp neg-control)" as *u8, clamp_ok); total=total+1 77 78 g_puts("---- jitterbuf gate: passed " as *u8); g_pn(pass); g_puts(" / " as *u8); g_pn(total); g_puts(" ----\n" as *u8) 79 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 80 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 81 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 82 let ctr__dry: *i64 = gv_ctr() 83 ctr__dry[0] = pass 84 ctr__dry[1] = total 85 let rc__dry: i64 = gv_verdict("JITTERBUF-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) 86 sys_exit(rc__dry) 87 return rc__dry 88}