nx_voice_codec_v2.nx source
↩ module page · 135 lines · 6700 B
1// nx_voice_codec_v2.nx -- NishiVoice v2: the REAL LPC-residual voice codec the v1 skeleton described but didn't build.
2// encode: PCM -> autocorr -> Levinson (a[1..p] Q30) -> analysis filter (true residual) -> quantise -> range-code.
3// decode: range-decode -> dequantise -> LPC synthesis filter -> PCM. Entirely on the x86_64 LPC lineage so autocorr +
4// levinson + synth + range coder all share one syscalls module (the v1 codec's nx_lpc/nx_syscalls path can't compose
5// with the range coder). Frame = [order:1][Q:2][n:2][a_q30: order*8][resid_len:2][entropy residual]. The coeffs are
6// carried full Q30 here (correctness first); LSF/reflection-coeff quantisation to shrink the header is the follow-on.
7// license_tier: ORIGINAL
8import "nx_lpc_autocorr.nx"
9import "nx_lpc_levinson.nx"
10import "nx_lpc_synth.nx"
11import "nx_resid_entropy.nx"
12const V2_MAGIC_32768: i64 = 32768
13const V2_MAGIC_65536: i64 = 65536
14const V2_MAGIC_4096: i64 = 4096
15
16const V2_ALPHA: i64 = 65 // residual symbol alphabet (|residual| up to 32)
17
18func _v2_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v }
19func _v2_ld16(buf: *u8, idx: i64) -> i64 { var v: i64 = buf[idx*2] | (buf[idx*2+1] << 8); if v >= V2_MAGIC_32768 { v = v - V2_MAGIC_65536 } return v }
20func _v2_st16(buf: *u8, idx: i64, v: i64) -> i64 { var x: i64 = v; if x < 0 { x = x + V2_MAGIC_65536 } buf[idx*2] = x & 0xff; buf[idx*2+1] = (x >> 8) & 0xff; return 0 }
21func _v2_ld64(buf: *u8, byteoff: i64) -> i64 { var v: i64 = 0; var b: i64 = 0; while b < 8 { v = v | (buf[byteoff + b] << (b*8)); b = b + 1 } return v }
22func _v2_st64(buf: *u8, byteoff: i64, v: i64) -> i64 { var b: i64 = 0; while b < 8 { buf[byteoff + b] = (v >> (b*8)) & 0xff; b = b + 1 } return 0 }
23func _v2_w16(buf: *u8, off: i64, v: i64) -> i64 { buf[off] = v & 0xff; buf[off+1] = (v >> 8) & 0xff; return off + 2 }
24func _v2_r16(buf: *u8, off: i64) -> i64 { return buf[off] | (buf[off+1] << 8) }
25
26// Levinson step-up: PARCOR reflection coeffs k[0..p-1] (k_i at k[i-1], Q30) -> LPC predictor a[1..p] (Q30), written to
27// a_out as p i64-LE words. Same recursion as nx_voice_clone_synth's _cs_stepup; |k|<1 keeps it stable.
28func v2_stepup(k: *i64, p: i64, a_out: *u8) -> i64 {
29 let acur: *i64 = sys_mmap(p*8) as *i64
30 let anew: *i64 = sys_mmap(p*8) as *i64
31 acur[0] = k[0]
32 var i: i64 = 2
33 while i <= p {
34 let ki: i64 = k[i-1]
35 var j: i64 = 1
36 while j <= i-1 { anew[j-1] = acur[j-1] + ((ki * acur[(i-j)-1]) >> 30); j = j + 1 }
37 anew[i-1] = ki
38 var c: i64 = 0
39 while c < i { acur[c] = anew[c]; c = c + 1 }
40 i = i + 1
41 }
42 var w: i64 = 0
43 while w < p { _v2_st64(a_out, w*8, acur[w]); w = w + 1 }
44 return 0
45}
46
47// encode one frame of i16-LE PCM (s_pcm, n samples) into out; returns total bytes, or -1 on overflow.
48func v2_encode(s_pcm: *u8, n: i64, order: i64, out: *u8, cap: i64) -> i64 {
49 let R: *u8 = sys_mmap((order+1)*8)
50 let kbuf: *u8 = sys_mmap((order+1)*8)
51 let a_q30: *u8 = sys_mmap((order+1)*8)
52 let ebuf: *u8 = sys_mmap((order+1)*8)
53 nx_lpc_autocorr(s_pcm, n, order, R)
54 nx_lpc_levinson(R, order, kbuf, a_q30, ebuf)
55
56 // quantise reflection coeffs to i16 (|k|<1 -> Q15 fits), reconstruct + step-up to the LPC coeffs BOTH sides use
57 let kq15: *i64 = sys_mmap(order*8) as *i64
58 let kq30: *i64 = sys_mmap(order*8) as *i64
59 var qi: i64 = 0
60 while qi < order { let kk: i64 = _v2_ld64(kbuf, qi*8); kq15[qi] = kk >> 15; kq30[qi] = kq15[qi] << 15; qi = qi + 1 }
61 let aq: *u8 = sys_mmap(order*8)
62 v2_stepup(kq30, order, aq)
63
64 // analysis filter: e[n] = s[n] + Sum_{k=1..p} (a[k]*s[n-k] >> 30) (per-term shift, matching nx_lpc_synth)
65 let e_i64: *i64 = sys_mmap(n*8) as *i64
66 var maxe: i64 = 0
67 var i: i64 = 0
68 while i < n {
69 var pred: i64 = 0
70 var k: i64 = 1
71 while k <= order { if i - k >= 0 { pred = pred + ((_v2_ld64(aq, (k-1)*8) * _v2_ld16(s_pcm, i-k)) >> 30) } k = k + 1 }
72 let e: i64 = _v2_ld16(s_pcm, i) + pred
73 e_i64[i] = e
74 if _v2_abs(e) > maxe { maxe = _v2_abs(e) }
75 i = i + 1
76 }
77 // quantise so the residual fits +/-32
78 var Q: i64 = (maxe + 31) / 32
79 if Q < 1 { Q = 1 }
80 let eq: *i64 = sys_mmap(n*8) as *i64
81 i = 0
82 while i < n { var q: i64 = e_i64[i] / Q; if q > 32 { q = 32 } if q < -32 { q = -32 } eq[i] = q; i = i + 1 }
83 // range-code the quantised residual
84 let freq: *i64 = sys_mmap(V2_ALPHA*8) as *i64
85 let cum: *i64 = sys_mmap((V2_ALPHA+1)*8) as *i64
86 let ft: i64 = re_build_model(V2_ALPHA, freq, cum)
87 let resid: *u8 = sys_mmap(V2_MAGIC_4096)
88 let resid_len: i64 = re_encode(eq, n, V2_ALPHA, cum, ft, resid, V2_MAGIC_4096)
89
90 // write frame: [order:1][Q:2][n:2][a_q30: order*8][resid_len:2][residual]
91 let hdr: i64 = 1 + 2 + 2 + order*2 + 2
92 if hdr + resid_len > cap { return -1 }
93 var o: i64 = 0
94 out[o] = order & 0xff; o = o + 1
95 o = _v2_w16(out, o, Q)
96 o = _v2_w16(out, o, n)
97 var c: i64 = 0
98 while c < order { var cc: i64 = kq15[c]; if cc < 0 { cc = cc + V2_MAGIC_65536 } out[o] = cc & 0xff; out[o+1] = (cc >> 8) & 0xff; o = o + 2; c = c + 1 }
99 o = _v2_w16(out, o, resid_len)
100 var j: i64 = 0
101 while j < resid_len { out[o + j] = resid[j]; j = j + 1 }
102 return o + resid_len
103}
104
105// decode one v2 frame (inb,len) into out_pcm (i16-LE); returns n samples, or -1 on error.
106func v2_decode(inb: *u8, len: i64, out_pcm: *u8, out_cap: i64) -> i64 {
107 if len < 5 { return -1 }
108 var o: i64 = 0
109 let order: i64 = inb[o]; o = o + 1
110 let Q: i64 = _v2_r16(inb, o); o = o + 2
111 let n: i64 = _v2_r16(inb, o); o = o + 2
112 if n > out_cap { return -1 }
113 // read quantised reflection coeffs (i16), reconstruct Q30, step-up to the LPC coeffs (same as encode used)
114 let kq30: *i64 = sys_mmap(order*8) as *i64
115 var dc: i64 = 0
116 while dc < order { var kv: i64 = inb[o] | (inb[o+1] << 8); if kv >= V2_MAGIC_32768 { kv = kv - V2_MAGIC_65536 } kq30[dc] = kv << 15; o = o + 2; dc = dc + 1 }
117 let aq: *u8 = sys_mmap(order*8)
118 v2_stepup(kq30, order, aq)
119 let resid_len: i64 = _v2_r16(inb, o); o = o + 2
120 let resid: *u8 = (inb as i64 + o) as *u8
121
122 // range-decode the residual (same deterministic model as encode)
123 let freq: *i64 = sys_mmap(V2_ALPHA*8) as *i64
124 let cum: *i64 = sys_mmap((V2_ALPHA+1)*8) as *i64
125 let ft: i64 = re_build_model(V2_ALPHA, freq, cum)
126 let eq: *i64 = sys_mmap(n*8) as *i64
127 re_decode(resid, resid_len, n, V2_ALPHA, cum, ft, eq)
128
129 // dequantise -> e_pcm, then LPC synthesis
130 let e_pcm: *u8 = sys_mmap(n*2)
131 var i: i64 = 0
132 while i < n { _v2_st16(e_pcm, i, eq[i] * Q); i = i + 1 }
133 nx_lpc_synth(e_pcm, n, aq, order, out_pcm)
134 return n
135}