use crate::silk_excitation::SilkFrameSize;
use crate::silk_lsf_stage2::{D_LPC_NB_MB, D_LPC_WB};
use crate::toc::Bandwidth;
use crate::Error;
pub const LPC_SYNTH_MAX_ORDER: usize = D_LPC_WB;
pub const LPC_SYNTH_MAX_SUBFRAME_SAMPLES: usize = 80;
pub fn subframe_samples(bandwidth: Bandwidth) -> Result<usize, Error> {
Ok(match bandwidth {
Bandwidth::Nb => 40,
Bandwidth::Mb => 60,
Bandwidth::Wb => 80,
_ => return Err(Error::MalformedPacket),
})
}
#[derive(Debug, Clone)]
pub struct LpcSynthState {
d_lpc: usize,
history: [f32; LPC_SYNTH_MAX_ORDER],
}
impl LpcSynthState {
pub fn new(bandwidth: Bandwidth) -> Result<Self, Error> {
let d_lpc = match bandwidth {
Bandwidth::Nb | Bandwidth::Mb => D_LPC_NB_MB,
Bandwidth::Wb => D_LPC_WB,
_ => return Err(Error::MalformedPacket),
};
Ok(Self {
d_lpc,
history: [0.0; LPC_SYNTH_MAX_ORDER],
})
}
pub fn d_lpc(&self) -> usize {
self.d_lpc
}
pub fn history(&self) -> &[f32] {
&self.history[..self.d_lpc]
}
pub fn reset(&mut self) {
self.history = [0.0; LPC_SYNTH_MAX_ORDER];
}
}
pub fn lpc_synthesis_subframe(
bandwidth: Bandwidth,
state: &mut LpcSynthState,
res: &[f32],
gain_q16: u32,
a_q12: &[i16],
out_clamped: &mut [f32],
) -> Result<Vec<f32>, Error> {
let n = subframe_samples(bandwidth)?;
if res.len() != n || out_clamped.len() != n {
return Err(Error::MalformedPacket);
}
if a_q12.len() != state.d_lpc {
return Err(Error::MalformedPacket);
}
let d_lpc = state.d_lpc;
let mut work = vec![0.0f32; d_lpc + n];
work[..d_lpc].copy_from_slice(&state.history[..d_lpc]);
let gain_scale = (gain_q16 as f32) / 65536.0;
let mut a_f = [0.0f32; LPC_SYNTH_MAX_ORDER];
for k in 0..d_lpc {
a_f[k] = (a_q12[k] as f32) / 4096.0;
}
for i in 0..n {
let mut sum = 0.0f32;
for k in 0..d_lpc {
sum += work[d_lpc + i - k - 1] * a_f[k];
}
let value = gain_scale * res[i] + sum;
work[d_lpc + i] = value;
out_clamped[i] = value.clamp(-1.0, 1.0);
}
for k in 0..d_lpc {
state.history[k] = work[d_lpc + n - d_lpc + k];
}
Ok(work[d_lpc..].to_vec())
}
pub fn lpc_synthesis_frame(
bandwidth: Bandwidth,
frame_size: SilkFrameSize,
state: &mut LpcSynthState,
res: &[f32],
gains_q16: &[u32],
a_q12_per_subframe: &[Vec<i16>],
) -> Result<Vec<f32>, Error> {
let n = subframe_samples(bandwidth)?;
let num_subframes = match frame_size {
SilkFrameSize::TenMs => 2,
SilkFrameSize::TwentyMs => 4,
};
if res.len() != n * num_subframes
|| gains_q16.len() != num_subframes
|| a_q12_per_subframe.len() != num_subframes
{
return Err(Error::MalformedPacket);
}
let mut out = vec![0.0f32; n * num_subframes];
for s in 0..num_subframes {
let j = s * n;
let res_sub = &res[j..j + n];
let a_sub = &a_q12_per_subframe[s];
let (head, tail) = out.split_at_mut(j);
let _ = head; let out_sub = &mut tail[..n];
lpc_synthesis_subframe(bandwidth, state, res_sub, gains_q16[s], a_sub, out_sub)?;
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn subframe_samples_table() {
assert_eq!(subframe_samples(Bandwidth::Nb).unwrap(), 40);
assert_eq!(subframe_samples(Bandwidth::Mb).unwrap(), 60);
assert_eq!(subframe_samples(Bandwidth::Wb).unwrap(), 80);
assert!(subframe_samples(Bandwidth::Swb).is_err());
assert!(subframe_samples(Bandwidth::Fb).is_err());
}
#[test]
fn state_dlpc_routing() {
assert_eq!(LpcSynthState::new(Bandwidth::Nb).unwrap().d_lpc(), 10);
assert_eq!(LpcSynthState::new(Bandwidth::Mb).unwrap().d_lpc(), 10);
assert_eq!(LpcSynthState::new(Bandwidth::Wb).unwrap().d_lpc(), 16);
assert!(LpcSynthState::new(Bandwidth::Swb).is_err());
assert!(LpcSynthState::new(Bandwidth::Fb).is_err());
}
#[test]
fn state_starts_zero_and_resets_zero() {
let mut s = LpcSynthState::new(Bandwidth::Wb).unwrap();
assert!(s.history().iter().all(|&x| x == 0.0));
s.history[3] = 0.5;
s.reset();
assert!(s.history().iter().all(|&x| x == 0.0));
}
#[test]
fn subframe_rejects_mismatched_res_len() {
let mut s = LpcSynthState::new(Bandwidth::Nb).unwrap();
let res = vec![0.0f32; 39]; let a = vec![0i16; 10];
let mut out = vec![0.0f32; 39];
assert!(lpc_synthesis_subframe(Bandwidth::Nb, &mut s, &res, 65536, &a, &mut out).is_err());
}
#[test]
fn subframe_rejects_mismatched_out_len() {
let mut s = LpcSynthState::new(Bandwidth::Nb).unwrap();
let res = vec![0.0f32; 40];
let a = vec![0i16; 10];
let mut out = vec![0.0f32; 39]; assert!(lpc_synthesis_subframe(Bandwidth::Nb, &mut s, &res, 65536, &a, &mut out).is_err());
}
#[test]
fn subframe_rejects_mismatched_a_len() {
let mut s = LpcSynthState::new(Bandwidth::Nb).unwrap();
let res = vec![0.0f32; 40];
let a = vec![0i16; 9]; let mut out = vec![0.0f32; 40];
assert!(lpc_synthesis_subframe(Bandwidth::Nb, &mut s, &res, 65536, &a, &mut out).is_err());
}
#[test]
fn all_zero_filter_passes_scaled_residual() {
let mut s = LpcSynthState::new(Bandwidth::Nb).unwrap();
let mut res = vec![0.0f32; 40];
for (i, r) in res.iter_mut().enumerate() {
*r = ((i as f32) - 20.0) * 0.01;
}
let a = vec![0i16; 10];
let mut out = vec![0.0f32; 40];
let gain_q16: u32 = 131072; let lpc =
lpc_synthesis_subframe(Bandwidth::Nb, &mut s, &res, gain_q16, &a, &mut out).unwrap();
for i in 0..40 {
let expect = 2.0 * res[i];
assert!(
(lpc[i] - expect).abs() < 1e-6,
"i={i}: lpc={} expect={}",
lpc[i],
expect
);
assert!((out[i] - expect.clamp(-1.0, 1.0)).abs() < 1e-6);
}
let hist = s.history().to_vec();
for k in 0..10 {
assert!((hist[k] - lpc[40 - 10 + k]).abs() < 1e-6);
}
}
#[test]
fn zero_residual_with_zero_history_yields_zero_output() {
let mut s = LpcSynthState::new(Bandwidth::Wb).unwrap();
let res = vec![0.0f32; 80];
let mut a = vec![0i16; 16];
for (k, v) in a.iter_mut().enumerate() {
*v = ((k as i16) * 37 - 200).clamp(-2048, 2047);
}
let mut out = vec![0.0f32; 80];
let lpc =
lpc_synthesis_subframe(Bandwidth::Wb, &mut s, &res, 1234567, &a, &mut out).unwrap();
assert!(lpc.iter().all(|&x| x == 0.0));
assert!(out.iter().all(|&x| x == 0.0));
assert!(s.history().iter().all(|&x| x == 0.0));
}
#[test]
fn single_tap_filter_pin_nb_q16_unity() {
let mut s = LpcSynthState::new(Bandwidth::Nb).unwrap();
let mut res = vec![0.0f32; 40];
res[0] = 1.0;
let mut a = vec![0i16; 10];
a[0] = 4096;
let mut out = vec![0.0f32; 40];
let lpc = lpc_synthesis_subframe(Bandwidth::Nb, &mut s, &res, 65536, &a, &mut out).unwrap();
for (i, v) in lpc.iter().enumerate() {
assert!((v - 1.0).abs() < 1e-6, "i={i}: lpc={v}");
}
for (i, v) in out.iter().enumerate() {
assert!((v - 1.0).abs() < 1e-6, "i={i}: out={v}");
}
}
#[test]
fn single_tap_filter_pin_wb_half_gain() {
let mut s = LpcSynthState::new(Bandwidth::Wb).unwrap();
let mut res = vec![0.0f32; 80];
res[0] = 1.0;
let mut a = vec![0i16; 16];
a[0] = 2048;
let mut out = vec![0.0f32; 80];
let lpc = lpc_synthesis_subframe(Bandwidth::Wb, &mut s, &res, 32768, &a, &mut out).unwrap();
for (i, v) in lpc.iter().enumerate() {
let expect = 0.5f32.powi((i + 1) as i32);
assert!((v - expect).abs() < 1e-6, "i={i}: lpc={v} expect={expect}");
}
let hist = s.history().to_vec();
for (k, h) in hist.iter().enumerate() {
let i_global = 80 - 16 + k;
let expect = 0.5f32.powi((i_global + 1) as i32);
assert!((h - expect).abs() < 1e-9);
}
}
#[test]
fn two_tap_filter_pin_nb_hand_traced() {
let mut s = LpcSynthState::new(Bandwidth::Nb).unwrap();
let mut res = vec![0.0f32; 40];
res[0] = 1.0;
res[1] = 2.0;
res[2] = 3.0;
let mut a = vec![0i16; 10];
a[0] = 2048; a[1] = 1024; let mut out = vec![0.0f32; 40];
let lpc = lpc_synthesis_subframe(Bandwidth::Nb, &mut s, &res, 65536, &a, &mut out).unwrap();
let expected = [1.0f32, 2.5, 4.5, 2.875, 2.5625];
for (i, ex) in expected.iter().enumerate() {
assert!(
(lpc[i] - ex).abs() < 1e-5,
"i={i}: lpc={} expect={}",
lpc[i],
ex
);
}
assert!((out[2] - 1.0).abs() < 1e-6);
assert!((out[3] - 1.0).abs() < 1e-6);
assert!((out[4] - 1.0).abs() < 1e-6);
assert!((out[0] - 1.0).abs() < 1e-6);
assert!((out[1] - 1.0).abs() < 1e-6);
}
#[test]
fn history_carries_between_subframes() {
let mut s = LpcSynthState::new(Bandwidth::Nb).unwrap();
let mut res = vec![0.0f32; 40];
res[0] = 1.0;
let mut a = vec![0i16; 10];
a[0] = 4096;
let mut out0 = vec![0.0f32; 40];
lpc_synthesis_subframe(Bandwidth::Nb, &mut s, &res, 65536, &a, &mut out0).unwrap();
for v in s.history() {
assert!((v - 1.0).abs() < 1e-6);
}
let res1 = vec![0.0f32; 40];
let mut out1 = vec![0.0f32; 40];
let lpc1 =
lpc_synthesis_subframe(Bandwidth::Nb, &mut s, &res1, 65536, &a, &mut out1).unwrap();
for (i, v) in lpc1.iter().enumerate() {
assert!((v - 1.0).abs() < 1e-6, "i={i}: lpc1={v}");
}
}
#[test]
fn reset_zeroes_history_for_decoder_reset_path() {
let mut s = LpcSynthState::new(Bandwidth::Nb).unwrap();
let mut res = vec![0.0f32; 40];
res[0] = 1.0;
let mut a = vec![0i16; 10];
a[0] = 4096;
let mut out = vec![0.0f32; 40];
lpc_synthesis_subframe(Bandwidth::Nb, &mut s, &res, 65536, &a, &mut out).unwrap();
assert!(s.history().iter().any(|&x| x != 0.0));
s.reset();
assert!(s.history().iter().all(|&x| x == 0.0));
}
#[test]
fn out_always_in_minus_one_one() {
let mut s = LpcSynthState::new(Bandwidth::Wb).unwrap();
let mut res = vec![0.0f32; 80];
for (i, r) in res.iter_mut().enumerate() {
*r = if i % 2 == 0 { 10.0 } else { -10.0 };
}
let mut a = vec![0i16; 16];
a[0] = 4000;
let mut out = vec![0.0f32; 80];
let _lpc =
lpc_synthesis_subframe(Bandwidth::Wb, &mut s, &res, 65536, &a, &mut out).unwrap();
for (i, v) in out.iter().enumerate() {
assert!((-1.0..=1.0).contains(v), "i={i}: out={v} outside [-1, 1]");
}
}
#[test]
fn history_stores_unclamped_lpc_not_out() {
let mut s = LpcSynthState::new(Bandwidth::Nb).unwrap();
let res = vec![5.0f32; 40];
let mut a = vec![0i16; 10];
a[0] = 0; let mut out = vec![0.0f32; 40];
let _ = lpc_synthesis_subframe(Bandwidth::Nb, &mut s, &res, 65536, &a, &mut out).unwrap();
for v in s.history() {
assert!(
(v - 5.0).abs() < 1e-6,
"history must be unclamped 5.0, got {v}"
);
}
for v in &out {
assert!((v - 1.0).abs() < 1e-6, "out must clamp to 1.0, got {v}");
}
}
#[test]
fn frame_wrapper_matches_per_subframe() {
let bandwidth = Bandwidth::Wb;
let frame_size = SilkFrameSize::TwentyMs;
let n = subframe_samples(bandwidth).unwrap();
let num_subframes = 4;
let mut res = vec![0.0f32; n * num_subframes];
for (i, r) in res.iter_mut().enumerate() {
*r = ((i as f32) * 0.013).sin() * 0.5;
}
let gains_q16 = vec![65536u32, 49152, 32768, 24576];
let mut a_per: Vec<Vec<i16>> = Vec::with_capacity(num_subframes);
for s in 0..num_subframes {
let mut row = vec![0i16; 16];
row[0] = (1000 + (s as i16) * 200).min(2047);
row[1] = -((s as i16) * 100);
a_per.push(row);
}
let mut state_a = LpcSynthState::new(bandwidth).unwrap();
let out_wrapper = lpc_synthesis_frame(
bandwidth,
frame_size,
&mut state_a,
&res,
&gains_q16,
&a_per,
)
.unwrap();
let mut state_b = LpcSynthState::new(bandwidth).unwrap();
let mut out_manual = vec![0.0f32; n * num_subframes];
for s in 0..num_subframes {
let j = s * n;
let res_sub = &res[j..j + n];
let (head, tail) = out_manual.split_at_mut(j);
let _ = head;
let out_sub = &mut tail[..n];
lpc_synthesis_subframe(
bandwidth,
&mut state_b,
res_sub,
gains_q16[s],
&a_per[s],
out_sub,
)
.unwrap();
}
assert_eq!(out_wrapper, out_manual);
assert_eq!(state_a.history(), state_b.history());
}
#[test]
fn frame_wrapper_rejects_bad_lengths() {
let mut s = LpcSynthState::new(Bandwidth::Nb).unwrap();
let res = vec![0.0f32; 79]; let gains = vec![65536u32, 65536];
let a: Vec<Vec<i16>> = vec![vec![0i16; 10]; 2];
assert!(lpc_synthesis_frame(
Bandwidth::Nb,
SilkFrameSize::TenMs,
&mut s,
&res,
&gains,
&a
)
.is_err());
let res = vec![0.0f32; 80];
let gains = vec![65536u32];
assert!(lpc_synthesis_frame(
Bandwidth::Nb,
SilkFrameSize::TenMs,
&mut s,
&res,
&gains,
&a
)
.is_err());
}
#[test]
fn no_panic_sweep_all_bandwidths_frame_sizes() {
for bw in [Bandwidth::Nb, Bandwidth::Mb, Bandwidth::Wb] {
for fs in [SilkFrameSize::TenMs, SilkFrameSize::TwentyMs] {
let n = subframe_samples(bw).unwrap();
let num_subframes = match fs {
SilkFrameSize::TenMs => 2,
SilkFrameSize::TwentyMs => 4,
};
let total = n * num_subframes;
let mut res = vec![0.0f32; total];
for (i, r) in res.iter_mut().enumerate() {
*r = ((i as f32) * 0.0173).cos() * 0.3;
}
let gains: Vec<u32> = (0..num_subframes)
.map(|s| 50000 + (s as u32) * 30000)
.collect();
let d_lpc = if matches!(bw, Bandwidth::Wb) { 16 } else { 10 };
let a_per: Vec<Vec<i16>> = (0..num_subframes)
.map(|s| {
let mut row = vec![0i16; d_lpc];
row[0] = 1000 + (s as i16) * 50;
if d_lpc > 1 {
row[1] = -200;
}
row
})
.collect();
let mut state = LpcSynthState::new(bw).unwrap();
let out = lpc_synthesis_frame(bw, fs, &mut state, &res, &gains, &a_per).unwrap();
assert_eq!(out.len(), total);
for v in &out {
assert!(
(-1.0..=1.0).contains(v),
"clamping post-condition violated: out={v}"
);
}
assert_eq!(state.history().len(), d_lpc);
}
}
}
}