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}