use crate::core_types::{LPC_ORDER, NUM_LSF};
use crate::math::{cosf, acosf, sort_f32};
pub fn autocorrelation(signal: &[f32], order: usize) -> [f32; LPC_ORDER + 1] {
let mut r = [0.0f32; LPC_ORDER + 1];
let n = signal.len();
for lag in 0..=order.min(LPC_ORDER) {
let mut sum = 0.0f32;
for i in 0..(n - lag) {
sum += signal[i] * signal[i + lag];
}
r[lag] = sum;
}
r
}
pub fn levinson_durbin(r: &[f32; LPC_ORDER + 1], order: usize) -> ([f32; LPC_ORDER], f32) {
let order = order.min(LPC_ORDER);
let mut a = [0.0f32; LPC_ORDER];
let mut a_prev = [0.0f32; LPC_ORDER];
if r[0] <= 0.0 {
return (a, 0.0);
}
let mut err = r[0];
for i in 0..order {
let mut sum = 0.0f32;
for j in 0..i {
sum += a_prev[j] * r[i - j];
}
let k = -(r[i + 1] + sum) / err;
a[i] = k;
for j in 0..i {
a[j] = a_prev[j] + k * a_prev[i - 1 - j];
}
err *= 1.0 - k * k;
if err <= 0.0 {
break;
}
a_prev[..=i].copy_from_slice(&a[..=i]);
}
(a, err)
}
pub fn lpc_to_lsf(a: &[f32; LPC_ORDER]) -> Option<[f32; NUM_LSF]> {
let p = LPC_ORDER;
let mut p_poly = [0.0f32; LPC_ORDER / 2 + 1];
let mut q_poly = [0.0f32; LPC_ORDER / 2 + 1];
let half = p / 2;
let mut p_full = [0.0f32; LPC_ORDER + 2];
let mut q_full = [0.0f32; LPC_ORDER + 2];
p_full[0] = 1.0;
q_full[0] = 1.0;
for i in 0..p {
p_full[i + 1] = a[i] + a[p - 1 - i];
q_full[i + 1] = a[i] - a[p - 1 - i];
}
p_full[p + 1] = 1.0; q_full[p + 1] = -1.0;
for i in 0..=p {
p_full[i + 1] += p_full[i];
}
for i in 0..=p {
q_full[i + 1] -= q_full[i];
}
for i in 0..=half {
p_poly[i] = p_full[half - i + 1];
q_poly[i] = q_full[half - i + 1];
}
let mut lsf = [0.0f32; NUM_LSF];
let mut found = 0usize;
let steps = 512;
let mut prev_p = eval_cheby(&p_poly, half, 1.0);
let mut prev_q = eval_cheby(&q_poly, half, 1.0);
for step in 1..=steps {
let w = core::f32::consts::PI * step as f32 / steps as f32;
let cos_w = cosf(w);
let cur_p = eval_cheby(&p_poly, half, cos_w);
let cur_q = eval_cheby(&q_poly, half, cos_w);
let prev_w = core::f32::consts::PI * (step - 1) as f32 / steps as f32;
if prev_p * cur_p <= 0.0 && found < NUM_LSF {
lsf[found] = refine_root(&p_poly, half, prev_w, w);
found += 1;
}
if prev_q * cur_q <= 0.0 && found < NUM_LSF {
lsf[found] = refine_root(&q_poly, half, prev_w, w);
found += 1;
}
prev_p = cur_p;
prev_q = cur_q;
}
if found < NUM_LSF {
return None; }
sort_f32(&mut lsf[..NUM_LSF]);
Some(lsf)
}
pub fn lsf_to_lpc(lsf: &[f32; NUM_LSF]) -> [f32; LPC_ORDER] {
let p = LPC_ORDER;
let mut a = [0.0f32; LPC_ORDER];
let half = p / 2;
let mut p_coeffs = [0.0f32; LPC_ORDER / 2 + 1];
let mut q_coeffs = [0.0f32; LPC_ORDER / 2 + 1];
p_coeffs[0] = 1.0;
q_coeffs[0] = 1.0;
for i in 0..half {
let cos_p = -cosf(lsf[2 * i]);
let cos_q = -cosf(lsf[2 * i + 1]);
let mut j = i + 1;
while j > 0 {
p_coeffs[j] += cos_p * 2.0 * p_coeffs[j.saturating_sub(1)];
q_coeffs[j] += cos_q * 2.0 * q_coeffs[j.saturating_sub(1)];
if j >= 2 {
p_coeffs[j] += p_coeffs[j - 2];
q_coeffs[j] += q_coeffs[j - 2];
}
j -= 1;
}
p_coeffs[0] += cos_p * 2.0; q_coeffs[0] += cos_q * 2.0;
}
let mut pp = [0.0f32; LPC_ORDER + 2]; let mut qq = [0.0f32; LPC_ORDER + 2]; pp[0] = 1.0;
qq[0] = 1.0;
let mut pp_order = 0usize;
let mut qq_order = 0usize;
for i in 0..half {
let wp = lsf[2 * i];
let wq = lsf[2 * i + 1];
let cp = -2.0 * cosf(wp);
for j in (0..=pp_order).rev() {
let t2 = if j >= 2 { pp[j - 2] } else { 0.0 };
let t1 = if j >= 1 { pp[j - 1] } else { 0.0 };
pp[j + 2] = pp[j] + cp * (if j + 1 <= pp_order + 2 { t1 } else { 0.0 });
}
pp_order += 2;
qq_order += 2;
let _ = (cp, wq); }
let a_reconstructed = lsf_to_lpc_clean(lsf);
a_reconstructed
}
fn lsf_to_lpc_clean(lsf: &[f32; NUM_LSF]) -> [f32; LPC_ORDER] {
let half = LPC_ORDER / 2;
let mut p = [0.0f32; LPC_ORDER + 2];
let mut q = [0.0f32; LPC_ORDER + 2];
p[0] = 1.0;
q[0] = 1.0;
let mut p_ord = 0usize;
let mut q_ord = 0usize;
for i in 0..half {
let cos_p = 2.0 * cosf(lsf[2 * i]);
let cos_q = 2.0 * cosf(lsf[2 * i + 1]);
p_ord += 2;
for j in (2..=p_ord).rev() {
p[j] = p[j] - cos_p * p[j - 1] + p[j - 2];
}
if p_ord >= 1 {
p[1] = p[1] - cos_p * p[0];
}
q_ord += 2;
for j in (2..=q_ord).rev() {
q[j] = q[j] - cos_q * q[j - 1] + q[j - 2];
}
if q_ord >= 1 {
q[1] = q[1] - cos_q * q[0];
}
}
p_ord += 1;
for j in (1..=p_ord).rev() {
p[j] = p[j] + p[j - 1];
}
q_ord += 1;
for j in (1..=q_ord).rev() {
q[j] = q[j] - q[j - 1];
}
let mut a = [0.0f32; LPC_ORDER];
for i in 0..LPC_ORDER {
a[i] = 0.5 * (p[i + 1] + q[i + 1]);
}
a
}
fn eval_cheby(coeffs: &[f32], order: usize, cos_w: f32) -> f32 {
let mut sum = 0.0f32;
let w = acosf(cos_w); for i in 0..=order {
sum += coeffs[i] * cosf(i as f32 * w);
}
sum
}
fn refine_root(coeffs: &[f32], order: usize, w_lo: f32, w_hi: f32) -> f32 {
let mut lo = w_lo;
let mut hi = w_hi;
let v_lo = eval_cheby(coeffs, order, cosf(lo));
for _ in 0..16 {
let mid = 0.5 * (lo + hi);
let v_mid = eval_cheby(coeffs, order, cosf(mid));
if v_lo * v_mid <= 0.0 {
hi = mid;
} else {
lo = mid;
}
}
0.5 * (lo + hi)
}
pub fn hamming_window(signal: &mut [f32]) {
let n = signal.len();
for i in 0..n {
let w = 0.54 - 0.46 * cosf(2.0 * core::f32::consts::PI * i as f32 / (n as f32 - 1.0));
signal[i] *= w;
}
}
pub fn pre_emphasis(signal: &mut [f32], coeff: f32) {
for i in (1..signal.len()).rev() {
signal[i] -= coeff * signal[i - 1];
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_autocorrelation_dc() {
let sig = [1.0f32; 80];
let r = autocorrelation(&sig, LPC_ORDER);
assert!((r[0] - 80.0).abs() < 1e-4);
assert!((r[1] - 79.0).abs() < 1e-4);
}
#[test]
fn test_levinson_flat_spectrum() {
let mut r = [0.0f32; LPC_ORDER + 1];
r[0] = 1.0;
let (a, err) = levinson_durbin(&r, LPC_ORDER);
for coeff in &a {
assert!(coeff.abs() < 1e-6, "expected ~0, got {}", coeff);
}
assert!((err - 1.0).abs() < 1e-6);
}
#[test]
fn test_lpc_lsf_roundtrip() {
let mut a = [0.0f32; LPC_ORDER];
a[0] = 0.9;
a[1] = -0.5;
a[2] = 0.3;
a[3] = -0.2;
a[4] = 0.1;
if let Some(lsf) = lpc_to_lsf(&a) {
for i in 0..NUM_LSF {
assert!(lsf[i] > 0.0, "LSF[{}] = {} not > 0", i, lsf[i]);
assert!(lsf[i] < core::f32::consts::PI, "LSF[{}] too large", i);
}
for i in 1..NUM_LSF {
assert!(lsf[i] > lsf[i - 1], "LSFs not strictly increasing at {}", i);
}
let a2 = lsf_to_lpc(&lsf);
for i in 0..LPC_ORDER {
assert!(
(a[i] - a2[i]).abs() < 0.05,
"LPC roundtrip mismatch at [{}]: {} vs {}",
i, a[i], a2[i]
);
}
}
}
#[test]
fn test_hamming_window_endpoints() {
let mut sig = [1.0f32; 64];
hamming_window(&mut sig);
assert!((sig[0] - 0.08).abs() < 0.01);
assert!((sig[32] - 1.0).abs() < 0.02);
}
#[test]
fn test_pre_emphasis() {
let mut sig = [1.0f32; 8];
pre_emphasis(&mut sig, 0.97);
assert!((sig[0] - 1.0).abs() < 1e-6);
assert!((sig[1] - 0.03).abs() < 1e-4);
}
}