code wiki / (root) / nx_voice_codec_v2.nx

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}