code wiki / (root) / nx_lpc_autocorr.nx

nx_lpc_autocorr.nx source

↩ module page · 82 lines · 3273 B

1// nx_lpc_autocorr.nx -- integer-only autocorrelation R[0..p] of a 2// speech frame. Foundation primitive for the Arc 1 voice codec 3// ladder (see docs/NISHI_BITS_UP_EXCEED_INDUSTRY.md Arc 1 Phase A). 4// 5// Per [[feedback-bits-up-exceed-never-match]]: this is the math layer 6// LPC uses; re-derived from the Wiener-Hopf / Yule-Walker formulation, 7// patent-clean. Not MLow, not Lyra, not Opus. 8// 9// Spec: 10// Input s[0..N-1] is i16 PCM samples (mono, 8 kHz or 16 kHz). 11// Output R[0..p] is i64 sums of products (no scaling). 12// R[k] = Σ_{n=k..N-1} s[n] * s[n-k] for k in 0..p 13// 14// Per-call cost: O((p+1) * N) multiplications + adds. For typical 15// p=10 + N=320 (20 ms @ 16 kHz): ~3500 ops / frame = trivial CPU. 16// 17// Returns 0 on success, -1 if p > N (degenerate). 18 19import "nx_syscalls_x86_64.nx" 20const K_MAGIC_1024: i64 = 1024 21 22func nx_lpc_autocorr(s_pcm: *u8, n_samples: i64, 23 p_order: i64, out_r: *u8) -> i64 { 24 if p_order >= n_samples { return -1 } 25 var k: i64 = 0 26 while k <= p_order { 27 var sum: i64 = 0 28 var n: i64 = k 29 while n < n_samples { 30 // s[n] and s[n-k] are i16 LE at byte offset 2*n and 2*(n-k) 31 let sn_off: i64 = n * 2 32 let sk_off: i64 = (n - k) * 2 33 let sn_lo: i64 = s_pcm[sn_off] 34 let sn_hi: i64 = s_pcm[sn_off + 1] 35 var sn: i64 = sn_lo | (sn_hi << 8) 36 if sn >= 0x8000 { sn = sn - 0x10000 } 37 let sk_lo: i64 = s_pcm[sk_off] 38 let sk_hi: i64 = s_pcm[sk_off + 1] 39 var sk: i64 = sk_lo | (sk_hi << 8) 40 if sk >= 0x8000 { sk = sk - 0x10000 } 41 sum = sum + sn * sk 42 n = n + 1 43 } 44 // Store i64 LE at byte offset 8*k 45 let r_off: i64 = k * 8 46 var v: i64 = sum 47 if v < 0 { v = v + 0x10000000000000000 } // unsigned representation for byte split 48 // Hmm actually i64 negative store: just bit-cast. 49 // NishiLang doesn't have bit_cast; we manually emit LE bytes 50 // including the sign bit. Easiest: shift+mask, sign-extending OK. 51 out_r[r_off + 0] = sum & 0xff 52 out_r[r_off + 1] = (sum >> 8) & 0xff 53 out_r[r_off + 2] = (sum >> 16) & 0xff 54 out_r[r_off + 3] = (sum >> 24) & 0xff 55 out_r[r_off + 4] = (sum >> 32) & 0xff 56 out_r[r_off + 5] = (sum >> 40) & 0xff 57 out_r[r_off + 6] = (sum >> 48) & 0xff 58 out_r[r_off + 7] = (sum >> 56) & 0xff 59 k = k + 1 60 } 61 return 0 62} 63 64// KAT smoke export: feed a known-pattern PCM frame, return # of 65// autocorrelation values that match expected. Expected values are 66// computed in the test harness in pure JS for cross-checking. 67func nx_lpc_autocorr_test(scratch: *u8) -> i64 { 68 let pcm: *u8 = scratch 69 let r_out: *u8 = (scratch as i64 + K_MAGIC_1024) as *u8 70 // Fill 256 samples with a triangular pattern: s[n] = (n % 32) - 16 71 // bounded i16 values in [-16, +15]. Predictable autocorrelation. 72 var i: i64 = 0 73 while i < 256 { 74 var v: i64 = (i % 32) - 16 75 if v < 0 { v = v + 0x10000 } 76 pcm[i * 2] = v & 0xff 77 pcm[i * 2 + 1] = (v >> 8) & 0xff 78 i = i + 1 79 } 80 let rc: i64 = nx_lpc_autocorr(pcm, 256, 10, r_out) 81 return rc 82}