code wiki / (root) / nx_lpc_levinson.nx

nx_lpc_levinson.nx source

↩ module page · 302 lines · 12408 B

1// nx_lpc_levinson.nx -- Levinson-Durbin recursion in Q30 fixed-point. 2// Arc 1 Phase A.2 (after nx_lpc_autocorr). 3// 4// Given autocorrelation R[0..p] (i64 from nx_lpc_autocorr), computes: 5// - reflection coefficients (PARCOR) k[1..p] in Q30 (|k_i| < 1 for stable) 6// - LPC predictor coefficients a[1..p] in Q30 7// - prediction error E[p] in i64 (= R[0] * Π(1 - k_i²)) 8// 9// Re-derived from Levinson-Durbin / Yule-Walker. Patent-clean math. 10// Per [[feedback-bits-up-exceed-never-match]] — no Opus, no MLow, 11// no proprietary codec dependency, integer-only. 12// 13// Algorithm (per any DSP textbook, e.g. Rabiner & Schafer 1978 §8.3): 14// E = R[0] 15// for i = 1 to p: 16// num = -R[i] - Σ_{j=1..i-1} a[j] * R[i-j] (with proper scaling) 17// k_i = num / E 18// new_a[i] = k_i 19// for j = 1 to i-1: new_a[j] = a[j] + k_i * a[i-j] 20// E = E * (1 - k_i²) 21// a = new_a 22// 23// Fixed-point convention: 24// - R[*] in raw i64 (autocorrelation sums) 25// - E in raw i64 (always positive, decreasing each iteration) 26// - k[*] in Q30: real_k = stored_k / 2^30, |real_k| < 1 ⇒ |stored_k| < 2^30 27// - a[*] in Q30: same convention; final coefs bounded by O(2^p) typically 28// 29// Returns 0 on success, -1 if R[0] <= 0 (degenerate / silence) or 30// p > 30 (would overflow Q30 in a coefs). 31 32import "nx_syscalls_x86_64.nx" 33const NX_MAGIC_1073741823: i64 = 1073741823 34const NX_MAGIC_1024: i64 = 1024 35 36const NX_LPC_Q: i64 = 30 37const NX_LPC_ONE_Q30: i64 = 1073741824 // 1 << 30 38 39// Helper: i64 Q30 multiplication. (a * b) >> 30 with i64 intermediate. 40// Caller's responsibility to ensure |a| * |b| fits in i64 before shift, 41// which is guaranteed when both are Q30 values with |a|, |b| < 2^31. 42func _q30_mul(a: i64, b: i64) -> i64 { 43 return (a * b) >> NX_LPC_Q 44} 45 46// Helper: i64 division a / b, where b > 0, result is signed. 47// NishiLang's `/` should do truncated-toward-zero integer division for i64. 48func _i64_div(a: i64, b: i64) -> i64 { 49 return a / b 50} 51 52// nx_lpc_levinson -- p must be in [1, 30]. 53// 54// r_in: i64 LE array R[0..p] ((p+1) * 8 bytes) 55// p_order: 1..30 56// k_out: i64 LE array k[0..p-1] (Q30) -- k_out[i-1] = k_i 57// a_out: i64 LE array a[0..p-1] (Q30) -- a_out[i-1] = a_i 58// e_out: single i64 LE = E[p] (raw, same units as R[0]) 59// 60// Returns 0 on success. 61func nx_lpc_levinson(r_in: *u8, p_order: i64, 62 k_out: *u8, a_out: *u8, e_out: *u8) -> i64 { 63 if p_order < 1 { return -1 } 64 if p_order > 30 { return -1 } 65 66 // R[0] must be > 0. Otherwise the signal is silent / degenerate. 67 let r0_off: i64 = 0 68 var r0: i64 = 0 69 var bi: i64 = 0 70 while bi < 8 { 71 let bb: i64 = r_in[r0_off + bi] 72 r0 = r0 | (bb << (bi * 8)) 73 bi = bi + 1 74 } 75 if r0 <= 0 { return -1 } 76 77 // Normalize R[0..p] so R[0] fits in [2^28, 2^31]. Without this, 78 // large-magnitude inputs (real speech R[0] ~ 2^33+) cause acc<<30 79 // to overflow + the e_cur>>30 fallback path divides by zero once 80 // e_cur shrinks below 2^30. Apply the same shift uniformly to 81 // ALL R[k] -- k and a coefficients are dimensionless ratios so 82 // they are unaffected. Only E[p] picks up the scale; we restore 83 // it by left-shifting at the end. 84 var norm_shift: i64 = 0 85 var r0_test: i64 = r0 86 while r0_test > 0x80000000 { // > 2^31, shift down 87 norm_shift = norm_shift + 1 88 r0_test = r0_test >> 1 89 } 90 // Apply norm_shift to ALL R[k] in place by writing back to r_in 91 // (allowed: caller's R buffer is consumed during the recursion). 92 if norm_shift > 0 { 93 var nk: i64 = 0 94 while nk <= p_order { 95 let nk_off: i64 = nk * 8 96 var rk: i64 = 0 97 var nbi: i64 = 0 98 while nbi < 8 { 99 rk = rk | ((r_in[nk_off + nbi]) << (nbi * 8)) 100 nbi = nbi + 1 101 } 102 let rk_shifted: i64 = rk >> norm_shift 103 r_in[nk_off + 0] = rk_shifted & 0xff 104 r_in[nk_off + 1] = (rk_shifted >> 8) & 0xff 105 r_in[nk_off + 2] = (rk_shifted >> 16) & 0xff 106 r_in[nk_off + 3] = (rk_shifted >> 24) & 0xff 107 r_in[nk_off + 4] = (rk_shifted >> 32) & 0xff 108 r_in[nk_off + 5] = (rk_shifted >> 40) & 0xff 109 r_in[nk_off + 6] = (rk_shifted >> 48) & 0xff 110 r_in[nk_off + 7] = (rk_shifted >> 56) & 0xff 111 nk = nk + 1 112 } 113 r0 = r0 >> norm_shift 114 } 115 116 // Working buffers: a[1..p] and new_a[1..p] in Q30. 117 // Use sys_mmap for transient storage (32 * 8 = 256 bytes each). 118 let a_cur: *u8 = sys_mmap(256) 119 let a_new: *u8 = sys_mmap(256) 120 // Zero both. 121 var zi: i64 = 0 122 while zi < 32 { 123 let off: i64 = zi * 8 124 a_cur[off] = 0; a_cur[off+1] = 0; a_cur[off+2] = 0; a_cur[off+3] = 0 125 a_cur[off+4] = 0; a_cur[off+5] = 0; a_cur[off+6] = 0; a_cur[off+7] = 0 126 a_new[off] = 0; a_new[off+1] = 0; a_new[off+2] = 0; a_new[off+3] = 0 127 a_new[off+4] = 0; a_new[off+5] = 0; a_new[off+6] = 0; a_new[off+7] = 0 128 zi = zi + 1 129 } 130 131 var e_cur: i64 = r0 132 133 // Main recursion: i = 1..p 134 var i: i64 = 1 135 while i <= p_order { 136 // num = -R[i] - Σ_{j=1..i-1} a[j] * R[i-j] 137 // R values are raw i64; a values are Q30. So a[j] * R[i-j] is 138 // Q30 * raw = needs >> 30 to bring back to raw scale. 139 let r_i_off: i64 = i * 8 140 var r_i: i64 = 0 141 var ri_bi: i64 = 0 142 while ri_bi < 8 { 143 r_i = r_i | ((r_in[r_i_off + ri_bi]) << (ri_bi * 8)) 144 ri_bi = ri_bi + 1 145 } 146 // Sign-extend r_i if top bit set (already i64 LE, so it's fine) 147 148 var acc: i64 = 0 - r_i 149 var j: i64 = 1 150 while j < i { 151 let aj_off: i64 = (j - 1) * 8 152 var a_j: i64 = 0 153 var aj_bi: i64 = 0 154 while aj_bi < 8 { 155 a_j = a_j | ((a_cur[aj_off + aj_bi]) << (aj_bi * 8)) 156 aj_bi = aj_bi + 1 157 } 158 let r_imj_off: i64 = (i - j) * 8 159 var r_imj: i64 = 0 160 var rimj_bi: i64 = 0 161 while rimj_bi < 8 { 162 r_imj = r_imj | ((r_in[r_imj_off + rimj_bi]) << (rimj_bi * 8)) 163 rimj_bi = rimj_bi + 1 164 } 165 // a[j] * R[i-j]: Q30 * raw, sum into acc (raw scale). 166 // Bring a[j] to raw by >> 30 after multiply. 167 acc = acc - ((a_j * r_imj) >> NX_LPC_Q) 168 j = j + 1 169 } 170 // k_i = acc / E_{i-1}, in Q30: (acc << 30) / E 171 // To avoid overflow: |acc| << 30 may exceed i64. Use mul-shift trick: 172 // k_i (Q30) = (acc / E) << 30 + ((acc % E) << 30) / E -- still tricky 173 // Simpler: divide first, then shift, accepting precision loss for now. 174 // OR: shift acc up by 30 if |acc| < 2^33 (which it is for normal signals). 175 // For test patterns we expect |acc| << 2^33, so the shift-first path is fine. 176 // S-class Q30 division: compute (acc << 30) / e_cur EXACTLY 177 // without i128, using the identity 178 // (acc << 30) / e = (acc / e) << 30 + ((acc % e) << 30) / e 179 // First term: (acc / e) is small after normalization (e_cur was 180 // at most 2^31 initially, shrinks each iteration; acc bounded 181 // by similar magnitude; quotient fits well in i32 typically). 182 // Left-shift by 30 fits in i64 when |acc/e| < 2^33. 183 // Second term: (acc % e) is in [0, |e|); (acc % e) << 30 fits 184 // in i64 when |e| < 2^33 (always true post-normalization). 185 // Sign handled by splitting on acc's sign and using positive 186 // |acc| in the formula. 187 var k_i: i64 = 0 188 var pos_acc: i64 = acc 189 var neg: i64 = 0 190 if acc < 0 { pos_acc = 0 - acc; neg = 1 } 191 let q_high: i64 = (pos_acc / e_cur) << NX_LPC_Q 192 let q_low: i64 = ((pos_acc % e_cur) << NX_LPC_Q) / e_cur 193 k_i = q_high + q_low 194 if neg == 1 { k_i = 0 - k_i } 195 // Clamp |k_i| < 2^30 for stable LPC (|reflection coef| < 1). 196 // Outside this range, system is unstable; clamp to ±(1 << 30 - 1). 197 if k_i > NX_MAGIC_1073741823 { k_i = NX_MAGIC_1073741823 } 198 if k_i < -NX_MAGIC_1073741823 { k_i = -NX_MAGIC_1073741823 } 199 200 // Store k[i-1] = k_i (Q30) to k_out. 201 let ki_off: i64 = (i - 1) * 8 202 var v: i64 = k_i 203 if v < 0 { v = v + 0x10000000000000000 } 204 // store low 8 bytes of v as LE; signed top bit comes out correct 205 k_out[ki_off + 0] = k_i & 0xff 206 k_out[ki_off + 1] = (k_i >> 8) & 0xff 207 k_out[ki_off + 2] = (k_i >> 16) & 0xff 208 k_out[ki_off + 3] = (k_i >> 24) & 0xff 209 k_out[ki_off + 4] = (k_i >> 32) & 0xff 210 k_out[ki_off + 5] = (k_i >> 40) & 0xff 211 k_out[ki_off + 6] = (k_i >> 48) & 0xff 212 k_out[ki_off + 7] = (k_i >> 56) & 0xff 213 214 // new_a[i] = k_i 215 let new_ai_off: i64 = (i - 1) * 8 216 a_new[new_ai_off + 0] = k_i & 0xff 217 a_new[new_ai_off + 1] = (k_i >> 8) & 0xff 218 a_new[new_ai_off + 2] = (k_i >> 16) & 0xff 219 a_new[new_ai_off + 3] = (k_i >> 24) & 0xff 220 a_new[new_ai_off + 4] = (k_i >> 32) & 0xff 221 a_new[new_ai_off + 5] = (k_i >> 40) & 0xff 222 a_new[new_ai_off + 6] = (k_i >> 48) & 0xff 223 a_new[new_ai_off + 7] = (k_i >> 56) & 0xff 224 225 // For j = 1..i-1: new_a[j] = a[j] + k_i * a[i-j] 226 var jj: i64 = 1 227 while jj < i { 228 let aj_off: i64 = (jj - 1) * 8 229 var a_j: i64 = 0 230 var aj_b: i64 = 0 231 while aj_b < 8 { 232 a_j = a_j | ((a_cur[aj_off + aj_b]) << (aj_b * 8)) 233 aj_b = aj_b + 1 234 } 235 let aimj_off: i64 = (i - jj - 1) * 8 236 var a_imj: i64 = 0 237 var aimj_b: i64 = 0 238 while aimj_b < 8 { 239 a_imj = a_imj | ((a_cur[aimj_off + aimj_b]) << (aimj_b * 8)) 240 aimj_b = aimj_b + 1 241 } 242 let new_aj: i64 = a_j + _q30_mul(k_i, a_imj) 243 let new_aj_off: i64 = (jj - 1) * 8 244 a_new[new_aj_off + 0] = new_aj & 0xff 245 a_new[new_aj_off + 1] = (new_aj >> 8) & 0xff 246 a_new[new_aj_off + 2] = (new_aj >> 16) & 0xff 247 a_new[new_aj_off + 3] = (new_aj >> 24) & 0xff 248 a_new[new_aj_off + 4] = (new_aj >> 32) & 0xff 249 a_new[new_aj_off + 5] = (new_aj >> 40) & 0xff 250 a_new[new_aj_off + 6] = (new_aj >> 48) & 0xff 251 a_new[new_aj_off + 7] = (new_aj >> 56) & 0xff 252 jj = jj + 1 253 } 254 255 // E = E * (1 - k_i²) 256 let k2: i64 = (k_i * k_i) >> NX_LPC_Q // Q30 257 let one_minus_k2: i64 = NX_LPC_ONE_Q30 - k2 258 // S-class graceful early-termination: when |k_i| → 1 (perfect AR 259 // fit at current order), the signal has no further LPC structure. 260 // Stop the recursion; remaining k[i+1..p] and a[i+1..p] stay 0 261 // (already zeroed in init). Per [[feedback-build-intelligence-never-strip-features]]: 262 // detect and inform, never silently produce garbage. 263 if one_minus_k2 < NX_MAGIC_1024 { 264 // Snapshot current a into a_out for orders 1..i (already done 265 // each iteration via a_new -> a_cur copy below), set e_out, 266 // skip remaining iterations. 267 // Copy a_new into a_cur for the final loop's copy-out. 268 var sp: i64 = 0 269 while sp < p_order * 8 { a_cur[sp] = a_new[sp]; sp = sp + 1 } 270 // Force loop exit by setting i past p_order 271 i = p_order + 1 272 } else { 273 e_cur = _q30_mul(e_cur, one_minus_k2) 274 if e_cur < 1 { e_cur = 1 } // numerical safety 275 } 276 277 // Swap a_cur and a_new (copy a_new -> a_cur) 278 var cp: i64 = 0 279 while cp < p_order * 8 { 280 a_cur[cp] = a_new[cp] 281 cp = cp + 1 282 } 283 i = i + 1 284 } 285 286 // Store a_cur to a_out (p entries, p*8 bytes). 287 var co: i64 = 0 288 while co < p_order * 8 { 289 a_out[co] = a_cur[co] 290 co = co + 1 291 } 292 // Store e_out = e_cur (i64 LE). 293 e_out[0] = e_cur & 0xff 294 e_out[1] = (e_cur >> 8) & 0xff 295 e_out[2] = (e_cur >> 16) & 0xff 296 e_out[3] = (e_cur >> 24) & 0xff 297 e_out[4] = (e_cur >> 32) & 0xff 298 e_out[5] = (e_cur >> 40) & 0xff 299 e_out[6] = (e_cur >> 48) & 0xff 300 e_out[7] = (e_cur >> 56) & 0xff 301 return 0 302}