code wiki / _hdl_build / nx_nv1_lpc.nx
nx_nv1_lpc.nx source
↩ module page · 244 lines · 7988 B
1// nx_nv1_lpc.nx -- NishiLossless v1 LPC rung: FLAC-class lossless PCM
2// compression INSIDE the NLC1 container (the named next rung in
3// knowledge/specs/2026-06-10-nishilossless-nv1-container.md).
4//
5// New chunk kind 0x4C 'L' = compressed Int16 PCM block. Payload:
6// u8 mode 0..3 = fixed predictor order; 255 = verbatim escape
7// u8 k Rice parameter (0..30; 0 when verbatim)
8// u32 nsamples decoded Int16 sample count (little-endian)
9// mode==255 : nsamples*2 bytes raw LE Int16
10// mode 0..3 : mode*2 bytes warmup raw LE Int16, then the Rice bitstream
11// (LSB-first within each byte), zero-padded to a byte edge.
12//
13// Fixed predictors (FLAC's fixed mode, exact integer math = zero loss):
14// o0: pred = 0 o1: pred = x[i-1]
15// o2: pred = 2x[i-1] - x[i-2]
16// o3: pred = 3x[i-1] - 3x[i-2] + x[i-3]
17// Residual r = x[i] - pred, zigzag u = (r<0 ? -2r-1 : 2r), Rice(u,k) =
18// (u>>k) one-bits, a zero bit, then the k low bits of u LSB-first.
19//
20// The ENCODER costs every (order, k<16) pair EXACTLY plus the verbatim
21// escape and writes whichever is smallest -- so the payload is NEVER
22// larger than 6 + 2*nsamples (noise degrades to verbatim, not expansion).
23// The DECODER is a validator too: every malformation returns a named
24// negative (tamper-evidence doctrine), including out-of-range
25// reconstruction (-7) so a tampered stream cannot decode silently.
26//
27// Scratch is sys_mmap'd per encode call (no munmap helper exists; the
28// team-side callers are gates and one-shot transcoders).
29// license_tier: ORIGINAL
30
31import "nx_nv1.nx"
32
33func nv1l_rd_i16(b: *u8, off: i64) -> i64 {
34 var v: i64 = b[off] & 0xff
35 v = v + ((b[off + 1] & 0xff) * 256)
36 if v >= 32768 { v = v - 65536 }
37 return v
38}
39
40func nv1l_wr_i16(b: *u8, idx: i64, v: i64) -> i64 {
41 var u: i64 = v
42 if u < 0 { u = u + 65536 }
43 b[idx * 2] = (u & 255) as u8
44 b[idx * 2 + 1] = ((u / 256) & 255) as u8
45 return 0
46}
47
48// set one bit (buffer region must be pre-zeroed); returns new bit cursor
49func nv1l_bit_put(b: *u8, pos: i64, bit: i64) -> i64 {
50 if bit == 1 {
51 let by: i64 = pos >> 3
52 b[by] = ((b[by] & 0xff) | (1 << (pos & 7))) as u8
53 }
54 return pos + 1
55}
56
57// read one bit; -1 on overrun past lim (bit count)
58func nv1l_bit_get(b: *u8, cur: *i64, lim: i64) -> i64 {
59 let p: i64 = cur[0]
60 if p >= lim { return 0 - 1 }
61 cur[0] = p + 1
62 return ((b[p >> 3] & 0xff) >> (p & 7)) & 1
63}
64
65func nv1l_rice_put(b: *u8, pos: i64, u: i64, k: i64) -> i64 {
66 var p: i64 = pos
67 var q: i64 = u >> k
68 while q > 0 { p = nv1l_bit_put(b, p, 1); q = q - 1 }
69 p = nv1l_bit_put(b, p, 0)
70 var j: i64 = 0
71 while j < k {
72 p = nv1l_bit_put(b, p, (u >> j) & 1)
73 j = j + 1
74 }
75 return p
76}
77
78// returns the unsigned Rice value, or -1 on bitstream overrun
79func nv1l_rice_get(b: *u8, cur: *i64, lim: i64, k: i64) -> i64 {
80 var q: i64 = 0
81 var bit: i64 = nv1l_bit_get(b, cur, lim)
82 while bit == 1 {
83 q = q + 1
84 bit = nv1l_bit_get(b, cur, lim)
85 }
86 if bit < 0 { return 0 - 1 }
87 var v: i64 = q << k
88 var j: i64 = 0
89 while j < k {
90 let x: i64 = nv1l_bit_get(b, cur, lim)
91 if x < 0 { return 0 - 1 }
92 v = v + (x << j)
93 j = j + 1
94 }
95 return v
96}
97
98// zigzagged residuals of fixed order o into u; returns count n - o
99func nv1l_residuals(x: *i64, n: i64, o: i64, u: *i64) -> i64 {
100 var i: i64 = o
101 while i < n {
102 var pred: i64 = 0
103 if o == 1 { pred = x[i - 1] }
104 if o == 2 { pred = 2 * x[i - 1] - x[i - 2] }
105 if o == 3 { pred = 3 * x[i - 1] - 3 * x[i - 2] + x[i - 3] }
106 let r: i64 = x[i] - pred
107 var z: i64 = 2 * r
108 if r < 0 { z = 0 - z - 1 }
109 u[i - o] = z
110 i = i + 1
111 }
112 return n - o
113}
114
115func nv1l_cost_bits(u: *i64, m: i64, k: i64) -> i64 {
116 var s: i64 = 0
117 var i: i64 = 0
118 while i < m {
119 s = s + (u[i] >> k) + 1 + k
120 i = i + 1
121 }
122 return s
123}
124
125// best Rice k (0..15) for residual set; kout[0]=k, returns total bits
126func nv1l_best_k(u: *i64, m: i64, kout: *i64) -> i64 {
127 var bk: i64 = 0
128 var bb: i64 = nv1l_cost_bits(u, m, 0)
129 var k: i64 = 1
130 while k < 16 {
131 let c: i64 = nv1l_cost_bits(u, m, k)
132 if c < bb { bb = c; bk = k }
133 k = k + 1
134 }
135 kout[0] = bk
136 return bb
137}
138
139// Encode nsamples LE Int16 from pcm into an L-chunk payload at out.
140// Returns payload length, or: -10 outcap < worst case (6 + 2*nsamples),
141// -11 negative nsamples. Payload is never larger than the verbatim escape.
142func nv1l_encode(pcm: *u8, nsamples: i64, out: *u8, outcap: i64) -> i64 {
143 if nsamples < 0 { return 0 - 11 }
144 if outcap < 6 + 2 * nsamples { return 0 - 10 }
145 if nsamples == 0 {
146 out[0] = 0 as u8
147 out[1] = 0 as u8
148 nv1_wr_u32(out, 2, 0)
149 return 6
150 }
151 let x: *i64 = sys_mmap(nsamples * 8 + 64) as *i64
152 let u: *i64 = sys_mmap(nsamples * 8 + 64) as *i64
153 let kbox: *i64 = sys_mmap(64) as *i64
154 var i: i64 = 0
155 while i < nsamples { x[i] = nv1l_rd_i16(pcm, i * 2); i = i + 1 }
156 var bmode: i64 = 255
157 var bk: i64 = 0
158 var bsize: i64 = 6 + 2 * nsamples
159 var o: i64 = 0
160 while o < 4 {
161 if o < nsamples {
162 let m: i64 = nv1l_residuals(x, nsamples, o, u)
163 let bits: i64 = nv1l_best_k(u, m, kbox)
164 let sz: i64 = 6 + 2 * o + (bits + 7) / 8
165 if sz < bsize { bsize = sz; bmode = o; bk = kbox[0] }
166 }
167 o = o + 1
168 }
169 out[0] = bmode as u8
170 out[1] = bk as u8
171 nv1_wr_u32(out, 2, nsamples)
172 if bmode == 255 {
173 i = 0
174 while i < 2 * nsamples { out[6 + i] = pcm[i]; i = i + 1 }
175 return 6 + 2 * nsamples
176 }
177 i = 0
178 while i < 2 * bmode { out[6 + i] = pcm[i]; i = i + 1 }
179 let m2: i64 = nv1l_residuals(x, nsamples, bmode, u)
180 let base: i64 = 6 + 2 * bmode
181 i = base
182 while i < bsize { out[i] = 0 as u8; i = i + 1 }
183 var pos: i64 = base * 8
184 i = 0
185 while i < m2 { pos = nv1l_rice_put(out, pos, u[i], bk); i = i + 1 }
186 return bsize
187}
188
189// Decode an L-chunk payload into LE Int16 at out. Returns sample count or:
190// -1 payload too short -2 bad mode -3 bad k -4 outcap overrun
191// -5 bitstream truncated/overrun -6 warmup malformed (order >= nsamples
192// or verbatim shorter than claimed) -7 reconstructed sample outside
193// Int16 range (tampered stream)
194func nv1l_decode(pl: *u8, plen: i64, out: *u8, outcap: i64) -> i64 {
195 if plen < 6 { return 0 - 1 }
196 let mode: i64 = pl[0] & 0xff
197 let k: i64 = pl[1] & 0xff
198 let ns: i64 = nv1_rd_u32(pl, 2)
199 var modeok: i64 = 0
200 if mode < 4 { modeok = 1 }
201 if mode == 255 { modeok = 1 }
202 if modeok == 0 { return 0 - 2 }
203 if k > 30 { return 0 - 3 }
204 if ns * 2 > outcap { return 0 - 4 }
205 if mode == 255 {
206 if plen < 6 + 2 * ns { return 0 - 6 }
207 var i: i64 = 0
208 while i < 2 * ns { out[i] = pl[6 + i]; i = i + 1 }
209 return ns
210 }
211 if ns == 0 { return 0 }
212 if mode >= ns { return 0 - 6 }
213 if plen < 6 + 2 * mode { return 0 - 6 }
214 var p1: i64 = 0
215 var p2: i64 = 0
216 var p3: i64 = 0
217 var i: i64 = 0
218 while i < mode {
219 let v: i64 = nv1l_rd_i16(pl, 6 + 2 * i)
220 nv1l_wr_i16(out, i, v)
221 p3 = p2; p2 = p1; p1 = v
222 i = i + 1
223 }
224 let cur: *i64 = sys_mmap(64) as *i64
225 cur[0] = (6 + 2 * mode) * 8
226 let lim: i64 = plen * 8
227 while i < ns {
228 let uv: i64 = nv1l_rice_get(pl, cur, lim, k)
229 if uv < 0 { return 0 - 5 }
230 var r: i64 = uv / 2
231 if (uv & 1) == 1 { r = 0 - ((uv + 1) / 2) }
232 var pred: i64 = 0
233 if mode == 1 { pred = p1 }
234 if mode == 2 { pred = 2 * p1 - p2 }
235 if mode == 3 { pred = 3 * p1 - 3 * p2 + p3 }
236 let vv: i64 = pred + r
237 if vv > 32767 { return 0 - 7 }
238 if vv < 0 - 32768 { return 0 - 7 }
239 nv1l_wr_i16(out, i, vv)
240 p3 = p2; p2 = p1; p1 = vv
241 i = i + 1
242 }
243 return ns
244}