use crate::silk_excitation::SilkFrameSize;
use crate::silk_frame::SignalType;
use crate::silk_lpc_synth::{subframe_samples, LPC_SYNTH_MAX_ORDER};
use crate::silk_lsf_stage2::{D_LPC_NB_MB, D_LPC_WB};
use crate::silk_ltp::LTP_FILTER_TAPS;
use crate::toc::Bandwidth;
use crate::Error;
pub const LTP_MAX_PITCH_LAG: usize = 288;
pub const LTP_OUT_HISTORY_MAX: usize = LTP_MAX_PITCH_LAG + LPC_SYNTH_MAX_ORDER + 2;
pub const LTP_LPC_HISTORY_MAX: usize = 3 * 80 + LPC_SYNTH_MAX_ORDER;
pub const LTP_SCALE_FRESH_Q14: u16 = 16384;
#[derive(Debug, Clone)]
pub struct LtpSynthState {
bandwidth: Bandwidth,
d_lpc: usize,
out_history: Vec<f32>,
lpc_history: Vec<f32>,
subframe_index: u8,
}
impl LtpSynthState {
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 {
bandwidth,
d_lpc,
out_history: vec![0.0; LTP_OUT_HISTORY_MAX],
lpc_history: vec![0.0; LTP_LPC_HISTORY_MAX],
subframe_index: 0,
})
}
pub fn bandwidth(&self) -> Bandwidth {
self.bandwidth
}
pub fn d_lpc(&self) -> usize {
self.d_lpc
}
pub fn subframe_index(&self) -> u8 {
self.subframe_index
}
pub fn out_history(&self) -> &[f32] {
&self.out_history
}
pub fn lpc_history(&self) -> &[f32] {
&self.lpc_history
}
pub fn reset(&mut self) {
for v in self.out_history.iter_mut() {
*v = 0.0;
}
for v in self.lpc_history.iter_mut() {
*v = 0.0;
}
self.subframe_index = 0;
}
pub fn start_frame(&mut self) {
self.subframe_index = 0;
}
fn push_subframe(&mut self, out_clamped: &[f32], lpc_unclamped: &[f32]) {
let n = out_clamped.len();
debug_assert_eq!(n, lpc_unclamped.len());
if n < self.out_history.len() {
self.out_history.copy_within(n.., 0);
}
if n < self.lpc_history.len() {
self.lpc_history.copy_within(n.., 0);
}
let out_tail = self.out_history.len() - n;
self.out_history[out_tail..].copy_from_slice(out_clamped);
let lpc_tail = self.lpc_history.len() - n;
self.lpc_history[lpc_tail..].copy_from_slice(lpc_unclamped);
self.subframe_index = self.subframe_index.saturating_add(1);
}
}
#[derive(Debug, Clone, Copy)]
pub struct LtpSynthSubframe<'a> {
pub bandwidth: Bandwidth,
pub signal_type: SignalType,
pub frame_size: SilkFrameSize,
pub subframe_index: u8,
pub gain_q16: u32,
pub pitch_lag: i32,
pub b_q7: [i8; LTP_FILTER_TAPS],
pub ltp_scaling_q14: u16,
pub a_q12: &'a [i16],
pub lsf_interp_used: bool,
}
pub fn ltp_synthesis_subframe(
state: &LtpSynthState,
cfg: LtpSynthSubframe<'_>,
e_q23: &[i32],
res_out: &mut [f32],
) -> Result<(), Error> {
let n = subframe_samples(cfg.bandwidth)?;
if e_q23.len() != n || res_out.len() != n {
return Err(Error::MalformedPacket);
}
if cfg.a_q12.len() != state.d_lpc {
return Err(Error::MalformedPacket);
}
if state.bandwidth != cfg.bandwidth {
return Err(Error::MalformedPacket);
}
let num_subframes = match cfg.frame_size {
SilkFrameSize::TenMs => 2u8,
SilkFrameSize::TwentyMs => 4u8,
};
if cfg.subframe_index >= num_subframes {
return Err(Error::MalformedPacket);
}
if cfg.signal_type != SignalType::Voiced {
for i in 0..n {
res_out[i] = (e_q23[i] as f32) / 8_388_608.0; }
return Ok(());
}
let s = cfg.subframe_index as usize;
let pitch_lag = cfg.pitch_lag;
if pitch_lag <= 0 {
return Err(Error::MalformedPacket);
}
let d_lpc = state.d_lpc;
let (out_end_offset_from_j, ltp_scale_q14): (i32, u16) = {
let n_i = n as i32;
let s_i = s as i32;
let split_branch =
matches!(cfg.frame_size, SilkFrameSize::TwentyMs) && (s >= 2) && cfg.lsf_interp_used;
if split_branch {
(-(s_i - 2) * n_i, LTP_SCALE_FRESH_Q14)
} else {
(-s_i * n_i, cfg.ltp_scaling_q14)
}
};
let region_a_start_off = -(pitch_lag + 2); let out_end_off = out_end_offset_from_j;
let earliest_off = region_a_start_off.min(out_end_off);
let lookback = (-earliest_off) as usize;
let total = lookback + n;
let mut res_buf = vec![0.0f32; total];
let mut a_f = [0.0f32; LPC_SYNTH_MAX_ORDER];
for (slot, &q12) in a_f.iter_mut().zip(cfg.a_q12.iter()).take(d_lpc) {
*slot = (q12 as f32) / 4096.0;
}
let gain_q16 = cfg.gain_q16 as f32;
let inv_gain = 65536.0 / gain_q16;
let ltp_scale_factor = 4.0 * (ltp_scale_q14 as f32) / gain_q16;
let out_hist = state.out_history();
let out_hist_len = out_hist.len();
let fetch_out = |off: i32| -> f32 {
let idx = (out_hist_len as i32) + off;
if (0..out_hist_len as i32).contains(&idx) {
out_hist[idx as usize]
} else {
0.0
}
};
let lpc_hist = state.lpc_history();
let lpc_hist_len = lpc_hist.len();
let fetch_lpc = |off: i32| -> f32 {
let idx = (lpc_hist_len as i32) + off;
if (0..lpc_hist_len as i32).contains(&idx) {
lpc_hist[idx as usize]
} else {
0.0
}
};
for off in region_a_start_off..out_end_off {
let out_i = fetch_out(off);
let mut sum = 0.0f32;
for (k, &af) in a_f.iter().enumerate().take(d_lpc) {
sum += fetch_out(off - (k as i32) - 1) * af;
}
let predicted = (out_i - sum).clamp(-1.0, 1.0);
let res_idx = (off - earliest_off) as usize;
res_buf[res_idx] = ltp_scale_factor * predicted;
}
for off in out_end_off..0 {
let lpc_i = fetch_lpc(off);
let mut sum = 0.0f32;
for (k, &af) in a_f.iter().enumerate().take(d_lpc) {
sum += fetch_lpc(off - (k as i32) - 1) * af;
}
let res_idx = (off - earliest_off) as usize;
res_buf[res_idx] = inv_gain * (lpc_i - sum);
}
let mut b_f = [0.0f32; LTP_FILTER_TAPS];
for (slot, &q7) in b_f.iter_mut().zip(cfg.b_q7.iter()) {
*slot = (q7 as f32) / 128.0;
}
for (local_i, (e_sample, out_sample)) in e_q23.iter().zip(res_out.iter_mut()).enumerate() {
let buf_i = lookback + local_i;
let mut sum = (*e_sample as f32) / 8_388_608.0;
for (k, &bf) in b_f.iter().enumerate() {
let src = (buf_i as i32) - pitch_lag + 2 - (k as i32);
if src >= 0 && (src as usize) < total {
sum += res_buf[src as usize] * bf;
}
}
res_buf[buf_i] = sum;
*out_sample = sum;
}
Ok(())
}
pub fn ltp_synth_commit_subframe(
state: &mut LtpSynthState,
out_clamped: &[f32],
lpc_unclamped: &[f32],
) -> Result<(), Error> {
let n = subframe_samples(state.bandwidth)?;
if out_clamped.len() != n || lpc_unclamped.len() != n {
return Err(Error::MalformedPacket);
}
state.push_subframe(out_clamped, lpc_unclamped);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn make_a_zero(d_lpc: usize) -> Vec<i16> {
vec![0i16; d_lpc]
}
fn make_b_zero() -> [i8; LTP_FILTER_TAPS] {
[0; LTP_FILTER_TAPS]
}
#[test]
fn constants_match_spec_paragraphs() {
assert_eq!(LTP_MAX_PITCH_LAG, 288);
assert_eq!(LTP_OUT_HISTORY_MAX, 306);
assert_eq!(LTP_LPC_HISTORY_MAX, 256);
assert_eq!(LTP_SCALE_FRESH_Q14, 16384);
}
#[test]
fn state_new_routes_d_lpc() {
assert_eq!(LtpSynthState::new(Bandwidth::Nb).unwrap().d_lpc(), 10);
assert_eq!(LtpSynthState::new(Bandwidth::Mb).unwrap().d_lpc(), 10);
assert_eq!(LtpSynthState::new(Bandwidth::Wb).unwrap().d_lpc(), 16);
assert!(LtpSynthState::new(Bandwidth::Swb).is_err());
assert!(LtpSynthState::new(Bandwidth::Fb).is_err());
}
#[test]
fn state_starts_zero_resets_zero() {
let mut s = LtpSynthState::new(Bandwidth::Wb).unwrap();
assert!(s.out_history().iter().all(|&x| x == 0.0));
assert!(s.lpc_history().iter().all(|&x| x == 0.0));
assert_eq!(s.subframe_index(), 0);
let dummy_out = vec![0.5f32; 80];
let dummy_lpc = vec![0.7f32; 80];
ltp_synth_commit_subframe(&mut s, &dummy_out, &dummy_lpc).unwrap();
assert!(s.out_history().iter().any(|&x| x != 0.0));
assert!(s.lpc_history().iter().any(|&x| x != 0.0));
assert_eq!(s.subframe_index(), 1);
s.reset();
assert!(s.out_history().iter().all(|&x| x == 0.0));
assert!(s.lpc_history().iter().all(|&x| x == 0.0));
assert_eq!(s.subframe_index(), 0);
}
#[test]
fn start_frame_clears_subframe_index_but_keeps_history() {
let mut s = LtpSynthState::new(Bandwidth::Nb).unwrap();
let dummy_out = vec![0.25f32; 40];
let dummy_lpc = vec![0.125f32; 40];
ltp_synth_commit_subframe(&mut s, &dummy_out, &dummy_lpc).unwrap();
assert_eq!(s.subframe_index(), 1);
assert!(s.out_history().iter().any(|&x| x != 0.0));
s.start_frame();
assert_eq!(s.subframe_index(), 0);
assert!(s.out_history().iter().any(|&x| x != 0.0));
assert!(s.lpc_history().iter().any(|&x| x != 0.0));
}
#[test]
fn push_subframe_keeps_most_recent_at_tail() {
let mut s = LtpSynthState::new(Bandwidth::Nb).unwrap();
let n = 40;
let mut out_a = vec![0.0f32; n];
let mut lpc_a = vec![0.0f32; n];
for i in 0..n {
out_a[i] = (i as f32) * 0.01;
lpc_a[i] = (i as f32) * 0.02;
}
ltp_synth_commit_subframe(&mut s, &out_a, &lpc_a).unwrap();
let oh = s.out_history();
let lh = s.lpc_history();
for i in 0..n {
assert!((oh[oh.len() - n + i] - out_a[i]).abs() < 1e-7);
assert!((lh[lh.len() - n + i] - lpc_a[i]).abs() < 1e-7);
}
let mut out_b = vec![0.0f32; n];
let mut lpc_b = vec![0.0f32; n];
for i in 0..n {
out_b[i] = 1.0 + (i as f32) * 0.01;
lpc_b[i] = 2.0 + (i as f32) * 0.02;
}
ltp_synth_commit_subframe(&mut s, &out_b, &lpc_b).unwrap();
let oh = s.out_history();
let lh = s.lpc_history();
for i in 0..n {
assert!((oh[oh.len() - n + i] - out_b[i]).abs() < 1e-7);
assert!((lh[lh.len() - n + i] - lpc_b[i]).abs() < 1e-7);
}
for i in 0..n {
assert!((oh[oh.len() - 2 * n + i] - out_a[i]).abs() < 1e-7);
assert!((lh[lh.len() - 2 * n + i] - lpc_a[i]).abs() < 1e-7);
}
}
#[test]
fn unvoiced_residual_is_excitation_scaled() {
let bandwidth = Bandwidth::Wb;
let n = subframe_samples(bandwidth).unwrap();
let state = LtpSynthState::new(bandwidth).unwrap();
let mut e_q23 = vec![0i32; n];
for (i, slot) in e_q23.iter_mut().enumerate() {
*slot = (i as i32 - 40) * 100_000;
}
let a = make_a_zero(state.d_lpc());
let cfg = LtpSynthSubframe {
bandwidth,
signal_type: SignalType::Unvoiced,
frame_size: SilkFrameSize::TwentyMs,
subframe_index: 0,
gain_q16: 65536,
pitch_lag: 100,
b_q7: make_b_zero(),
ltp_scaling_q14: 15565,
a_q12: &a,
lsf_interp_used: false,
};
let mut res = vec![0.0f32; n];
ltp_synthesis_subframe(&state, cfg, &e_q23, &mut res).unwrap();
for i in 0..n {
let expect = (e_q23[i] as f32) / 8_388_608.0;
assert!(
(res[i] - expect).abs() < 1e-9,
"i={i}: res={} expect={expect}",
res[i]
);
}
}
#[test]
fn inactive_treated_as_unvoiced_for_residual_path() {
let bandwidth = Bandwidth::Nb;
let n = subframe_samples(bandwidth).unwrap();
let state = LtpSynthState::new(bandwidth).unwrap();
let mut e_q23 = vec![0i32; n];
e_q23[0] = 8_388_608; e_q23[1] = -8_388_608; let a = make_a_zero(state.d_lpc());
let cfg = LtpSynthSubframe {
bandwidth,
signal_type: SignalType::Inactive,
frame_size: SilkFrameSize::TenMs,
subframe_index: 0,
gain_q16: 65536,
pitch_lag: 50,
b_q7: make_b_zero(),
ltp_scaling_q14: 15565,
a_q12: &a,
lsf_interp_used: false,
};
let mut res = vec![0.0f32; n];
ltp_synthesis_subframe(&state, cfg, &e_q23, &mut res).unwrap();
assert!((res[0] - 1.0).abs() < 1e-7);
assert!((res[1] - -1.0).abs() < 1e-7);
for &v in &res[2..] {
assert_eq!(v, 0.0);
}
}
#[test]
fn rejects_mismatched_lengths() {
let bandwidth = Bandwidth::Nb;
let state = LtpSynthState::new(bandwidth).unwrap();
let a = make_a_zero(state.d_lpc());
let cfg = LtpSynthSubframe {
bandwidth,
signal_type: SignalType::Unvoiced,
frame_size: SilkFrameSize::TwentyMs,
subframe_index: 0,
gain_q16: 65536,
pitch_lag: 50,
b_q7: make_b_zero(),
ltp_scaling_q14: 15565,
a_q12: &a,
lsf_interp_used: false,
};
let e = vec![0i32; 39];
let mut r = vec![0.0f32; 40];
assert!(ltp_synthesis_subframe(&state, cfg, &e, &mut r).is_err());
let e = vec![0i32; 40];
let mut r = vec![0.0f32; 39];
assert!(ltp_synthesis_subframe(&state, cfg, &e, &mut r).is_err());
}
#[test]
fn rejects_bad_a_q12_len() {
let bandwidth = Bandwidth::Wb;
let n = subframe_samples(bandwidth).unwrap();
let state = LtpSynthState::new(bandwidth).unwrap();
let a = make_a_zero(10);
let cfg = LtpSynthSubframe {
bandwidth,
signal_type: SignalType::Voiced,
frame_size: SilkFrameSize::TwentyMs,
subframe_index: 0,
gain_q16: 65536,
pitch_lag: 100,
b_q7: make_b_zero(),
ltp_scaling_q14: 15565,
a_q12: &a,
lsf_interp_used: false,
};
let e = vec![0i32; n];
let mut r = vec![0.0f32; n];
assert!(ltp_synthesis_subframe(&state, cfg, &e, &mut r).is_err());
}
#[test]
fn rejects_mismatched_state_bandwidth() {
let n = subframe_samples(Bandwidth::Nb).unwrap();
let state = LtpSynthState::new(Bandwidth::Wb).unwrap();
let a = make_a_zero(16);
let cfg = LtpSynthSubframe {
bandwidth: Bandwidth::Nb,
signal_type: SignalType::Voiced,
frame_size: SilkFrameSize::TwentyMs,
subframe_index: 0,
gain_q16: 65536,
pitch_lag: 50,
b_q7: make_b_zero(),
ltp_scaling_q14: 15565,
a_q12: &a,
lsf_interp_used: false,
};
let e = vec![0i32; n];
let mut r = vec![0.0f32; n];
assert!(ltp_synthesis_subframe(&state, cfg, &e, &mut r).is_err());
}
#[test]
fn rejects_swb_fb_bandwidth() {
for bw in [Bandwidth::Swb, Bandwidth::Fb] {
assert!(LtpSynthState::new(bw).is_err());
}
}
#[test]
fn rejects_bad_subframe_index() {
let bandwidth = Bandwidth::Nb;
let n = subframe_samples(bandwidth).unwrap();
let state = LtpSynthState::new(bandwidth).unwrap();
let a = make_a_zero(state.d_lpc());
let cfg = LtpSynthSubframe {
bandwidth,
signal_type: SignalType::Voiced,
frame_size: SilkFrameSize::TenMs,
subframe_index: 2, gain_q16: 65536,
pitch_lag: 50,
b_q7: make_b_zero(),
ltp_scaling_q14: 15565,
a_q12: &a,
lsf_interp_used: false,
};
let e = vec![0i32; n];
let mut r = vec![0.0f32; n];
assert!(ltp_synthesis_subframe(&state, cfg, &e, &mut r).is_err());
}
#[test]
fn rejects_nonpositive_pitch_lag_voiced() {
let bandwidth = Bandwidth::Wb;
let n = subframe_samples(bandwidth).unwrap();
let state = LtpSynthState::new(bandwidth).unwrap();
let a = make_a_zero(state.d_lpc());
let cfg = LtpSynthSubframe {
bandwidth,
signal_type: SignalType::Voiced,
frame_size: SilkFrameSize::TwentyMs,
subframe_index: 0,
gain_q16: 65536,
pitch_lag: 0,
b_q7: make_b_zero(),
ltp_scaling_q14: 15565,
a_q12: &a,
lsf_interp_used: false,
};
let e = vec![0i32; n];
let mut r = vec![0.0f32; n];
assert!(ltp_synthesis_subframe(&state, cfg, &e, &mut r).is_err());
}
#[test]
fn voiced_zero_history_zero_excitation_zero_b_yields_zero() {
let bandwidth = Bandwidth::Mb;
let n = subframe_samples(bandwidth).unwrap();
let state = LtpSynthState::new(bandwidth).unwrap();
let a = make_a_zero(state.d_lpc());
let cfg = LtpSynthSubframe {
bandwidth,
signal_type: SignalType::Voiced,
frame_size: SilkFrameSize::TwentyMs,
subframe_index: 0,
gain_q16: 65536,
pitch_lag: 100,
b_q7: make_b_zero(),
ltp_scaling_q14: 15565,
a_q12: &a,
lsf_interp_used: false,
};
let e = vec![0i32; n];
let mut r = vec![0.0f32; n];
ltp_synthesis_subframe(&state, cfg, &e, &mut r).unwrap();
for (i, v) in r.iter().enumerate() {
assert_eq!(*v, 0.0, "i={i}: nonzero {v}");
}
}
#[test]
fn voiced_zero_b_passes_scaled_excitation() {
let bandwidth = Bandwidth::Wb;
let n = subframe_samples(bandwidth).unwrap();
let mut state = LtpSynthState::new(bandwidth).unwrap();
let mut out_h = vec![0.0f32; n];
let mut lpc_h = vec![0.0f32; n];
for i in 0..n {
out_h[i] = ((i as f32) - 40.0) * 0.013;
lpc_h[i] = ((i as f32) - 40.0) * 0.011;
}
ltp_synth_commit_subframe(&mut state, &out_h, &lpc_h).unwrap();
let mut a = vec![0i16; state.d_lpc()];
a[0] = 1234;
a[3] = -456;
let cfg = LtpSynthSubframe {
bandwidth,
signal_type: SignalType::Voiced,
frame_size: SilkFrameSize::TwentyMs,
subframe_index: 1, gain_q16: 100_000,
pitch_lag: 50,
b_q7: [0; LTP_FILTER_TAPS],
ltp_scaling_q14: 15565,
a_q12: &a,
lsf_interp_used: false,
};
let mut e = vec![0i32; n];
for (i, slot) in e.iter_mut().enumerate() {
*slot = ((i as i32) - 40) * 50_000;
}
let mut r = vec![0.0f32; n];
ltp_synthesis_subframe(&state, cfg, &e, &mut r).unwrap();
for (i, (&ri, &ei)) in r.iter().zip(e.iter()).enumerate() {
let expect = (ei as f32) / 8_388_608.0;
assert!(
(ri - expect).abs() < 1e-7,
"i={i}: r={} expect={expect}",
ri
);
}
}
#[test]
fn voiced_b0_only_uses_pitch_lag_lookback() {
let bandwidth = Bandwidth::Nb;
let n = subframe_samples(bandwidth).unwrap();
let mut state = LtpSynthState::new(bandwidth).unwrap();
let v = 0.42f32;
let mut out_h = vec![0.0f32; n];
out_h[n - 14] = v;
let lpc_h = vec![0.0f32; n];
ltp_synth_commit_subframe(&mut state, &out_h, &lpc_h).unwrap();
let a = make_a_zero(state.d_lpc());
let cfg = LtpSynthSubframe {
bandwidth,
signal_type: SignalType::Voiced,
frame_size: SilkFrameSize::TwentyMs,
subframe_index: 0,
gain_q16: 65536,
pitch_lag: 16,
b_q7: [64, 0, 0, 0, 0],
ltp_scaling_q14: 15565,
a_q12: &a,
lsf_interp_used: false,
};
let e = vec![0i32; n];
let mut r = vec![0.0f32; n];
ltp_synthesis_subframe(&state, cfg, &e, &mut r).unwrap();
let scale = 4.0 * 15565.0 / 65536.0;
let expect = 0.5 * scale * v;
assert!(
(r[0] - expect).abs() < 1e-6,
"r[0]={} expect={expect}",
r[0]
);
}
#[test]
fn voiced_region_b_lpc_path_no_out_history() {
let bandwidth = Bandwidth::Wb;
let n = subframe_samples(bandwidth).unwrap();
let mut state = LtpSynthState::new(bandwidth).unwrap();
let mut lpc_h = vec![0.0f32; n];
let v = 0.33f32;
lpc_h[n - 16] = v;
let out_h = vec![0.0f32; n];
ltp_synth_commit_subframe(&mut state, &out_h, &lpc_h).unwrap();
let a = make_a_zero(state.d_lpc());
let cfg = LtpSynthSubframe {
bandwidth,
signal_type: SignalType::Voiced,
frame_size: SilkFrameSize::TwentyMs,
subframe_index: 1, gain_q16: 65536,
pitch_lag: 16,
b_q7: [0, 0, 64, 0, 0],
ltp_scaling_q14: 15565,
a_q12: &a,
lsf_interp_used: false,
};
let e = vec![0i32; n];
let mut r = vec![0.0f32; n];
ltp_synthesis_subframe(&state, cfg, &e, &mut r).unwrap();
let expect = 0.5 * v;
assert!(
(r[0] - expect).abs() < 1e-6,
"r[0]={} expect={expect}",
r[0]
);
}
#[test]
fn voiced_lsf_interp_split_uses_fresh_scale_and_shorter_lookback() {
let bandwidth = Bandwidth::Nb;
let n = subframe_samples(bandwidth).unwrap();
let mut state = LtpSynthState::new(bandwidth).unwrap();
for _ in 0..3 {
let mut out_h = vec![0.0f32; n];
out_h[n - 14] = 0.0;
ltp_synth_commit_subframe(&mut state, &out_h, &vec![0.0f32; n]).unwrap();
}
let v = 0.5f32;
let mut out_h = vec![0.0f32; n];
out_h[n - 14] = v;
ltp_synth_commit_subframe(&mut state, &out_h, &vec![0.0f32; n]).unwrap();
state.start_frame();
let a = make_a_zero(state.d_lpc());
let cfg = LtpSynthSubframe {
bandwidth,
signal_type: SignalType::Voiced,
frame_size: SilkFrameSize::TwentyMs,
subframe_index: 2,
gain_q16: 65536,
pitch_lag: 16,
b_q7: [64, 0, 0, 0, 0],
ltp_scaling_q14: 15565, a_q12: &a,
lsf_interp_used: true,
};
let e = vec![0i32; n];
let mut r = vec![0.0f32; n];
ltp_synthesis_subframe(&state, cfg, &e, &mut r).unwrap();
let expect = 0.5 * v;
assert!(
(r[0] - expect).abs() < 1e-6,
"r[0]={} expect={expect}",
r[0]
);
}
#[test]
fn voiced_decode_is_deterministic_for_same_inputs() {
let bandwidth = Bandwidth::Wb;
let n = subframe_samples(bandwidth).unwrap();
let state0 = {
let mut s = LtpSynthState::new(bandwidth).unwrap();
let mut out_h = vec![0.0f32; n];
let mut lpc_h = vec![0.0f32; n];
for i in 0..n {
out_h[i] = ((i as f32) * 0.017).sin() * 0.4;
lpc_h[i] = ((i as f32) * 0.013).cos() * 0.3;
}
ltp_synth_commit_subframe(&mut s, &out_h, &lpc_h).unwrap();
s
};
let mut a = vec![0i16; 16];
for (k, slot) in a.iter_mut().enumerate() {
*slot = (200 - (k as i16) * 25).clamp(-2047, 2047);
}
let mut e = vec![0i32; n];
for (i, slot) in e.iter_mut().enumerate() {
*slot = ((i as i32) - 40) * 12_345;
}
let cfg = LtpSynthSubframe {
bandwidth,
signal_type: SignalType::Voiced,
frame_size: SilkFrameSize::TwentyMs,
subframe_index: 1,
gain_q16: 200_000,
pitch_lag: 90,
b_q7: [13, 22, 39, 23, 12],
ltp_scaling_q14: 12288,
a_q12: &a,
lsf_interp_used: false,
};
let mut r1 = vec![0.0f32; n];
let mut r2 = vec![0.0f32; n];
ltp_synthesis_subframe(&state0, cfg, &e, &mut r1).unwrap();
ltp_synthesis_subframe(&state0, cfg, &e, &mut r2).unwrap();
assert_eq!(r1, r2);
}
#[test]
fn voiced_sweep_no_panic_finite() {
let buffers: [&[u8]; 3] = [
&[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77],
&[0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88],
&[0x5A, 0xA5, 0x3C, 0xC3, 0x0F, 0xF0, 0x69, 0x96],
];
for buf_seed in buffers {
for bw in [Bandwidth::Nb, Bandwidth::Mb, Bandwidth::Wb] {
let n = subframe_samples(bw).unwrap();
let d_lpc = if matches!(bw, Bandwidth::Wb) { 16 } else { 10 };
for fs in [SilkFrameSize::TenMs, SilkFrameSize::TwentyMs] {
let num_subframes = match fs {
SilkFrameSize::TenMs => 2u8,
SilkFrameSize::TwentyMs => 4u8,
};
let mut state = LtpSynthState::new(bw).unwrap();
let mut out_h = vec![0.0f32; n];
let mut lpc_h = vec![0.0f32; n];
for i in 0..n {
let s = buf_seed[i % buf_seed.len()] as f32;
out_h[i] = (s - 128.0) / 256.0;
lpc_h[i] = (s - 128.0) / 200.0;
}
ltp_synth_commit_subframe(&mut state, &out_h, &lpc_h).unwrap();
let mut a = vec![0i16; d_lpc];
for (k, slot) in a.iter_mut().enumerate() {
*slot = (k as i16) * 50 - 200;
}
let mut e = vec![0i32; n];
for (i, slot) in e.iter_mut().enumerate() {
*slot = ((buf_seed[i % buf_seed.len()] as i32) - 128) * 60_000;
}
for s in 0..num_subframes {
let cfg = LtpSynthSubframe {
bandwidth: bw,
signal_type: SignalType::Voiced,
frame_size: fs,
subframe_index: s,
gain_q16: 100_000 + (s as u32) * 20_000,
pitch_lag: 32 + (s as i32) * 8,
b_q7: [4, 6, 24, 7, 5],
ltp_scaling_q14: 15565,
a_q12: &a,
lsf_interp_used: s >= 2 && matches!(fs, SilkFrameSize::TwentyMs),
};
let mut r = vec![0.0f32; n];
ltp_synthesis_subframe(&state, cfg, &e, &mut r).unwrap();
for (i, v) in r.iter().enumerate() {
assert!(v.is_finite(), "non-finite res at i={i}: {v}");
}
let out_clamped: Vec<f32> = r.iter().map(|v| v.clamp(-1.0, 1.0)).collect();
let lpc_unclamped: Vec<f32> = r.clone();
ltp_synth_commit_subframe(&mut state, &out_clamped, &lpc_unclamped)
.unwrap();
}
}
}
}
}
#[test]
fn commit_rejects_bad_lengths() {
let mut s = LtpSynthState::new(Bandwidth::Nb).unwrap();
let out = vec![0.0f32; 39]; let lpc = vec![0.0f32; 40];
assert!(ltp_synth_commit_subframe(&mut s, &out, &lpc).is_err());
let out = vec![0.0f32; 40];
let lpc = vec![0.0f32; 39];
assert!(ltp_synth_commit_subframe(&mut s, &out, &lpc).is_err());
}
}