use super::tables::{MAX_FRAME_LENGTH, MAX_LPC_ORDER, MAX_NB_SUBFR};
#[derive(Debug, Clone)]
pub(super) struct SilkDecodedFrame {
pub lag: i32,
pub ltp_gains: [i32; MAX_NB_SUBFR],
pub output: Vec<i16>,
}
#[derive(Debug, Clone)]
pub(super) struct SilkPlcState {
pub pitch_lag: i32,
pub pitch_gain: [i32; MAX_NB_SUBFR],
pub lpc_coeffs: [i32; MAX_LPC_ORDER],
pub shape_gain: i32,
pub rand_seed: i32,
pub last_frame_out: Vec<i16>,
pub nb_subfr: usize,
pub subfr_length: usize,
pub fs_khz: i32,
pub nb_coefs: usize,
}
impl Default for SilkPlcState {
fn default() -> Self {
Self {
pitch_lag: 0,
pitch_gain: [0; MAX_NB_SUBFR],
lpc_coeffs: [0; MAX_LPC_ORDER],
shape_gain: 1 << 14,
rand_seed: 1,
last_frame_out: Vec::new(),
nb_subfr: 0,
subfr_length: 0,
fs_khz: 0,
nb_coefs: 0,
}
}
}
pub(super) fn plc_update(
plc: &mut SilkPlcState,
frame: &SilkDecodedFrame,
lpc_q12: &[i32],
nb_subfr: usize,
subfr_length: usize,
fs_khz: i32,
nb_coefs: usize,
) {
plc.pitch_lag = frame.lag.max(0);
plc.pitch_gain.fill(0);
for (dst, src) in plc
.pitch_gain
.iter_mut()
.zip(frame.ltp_gains.iter())
.take(nb_subfr.min(MAX_NB_SUBFR))
{
*dst = (*src).clamp(0, 1 << 16);
}
plc.lpc_coeffs.fill(0);
for (dst, src) in plc
.lpc_coeffs
.iter_mut()
.zip(lpc_q12.iter())
.take(nb_coefs.min(MAX_LPC_ORDER))
{
*dst = *src;
}
plc.nb_subfr = nb_subfr;
plc.subfr_length = subfr_length;
plc.fs_khz = fs_khz;
plc.nb_coefs = nb_coefs.min(MAX_LPC_ORDER);
plc.last_frame_out.clear();
plc.last_frame_out.extend_from_slice(&frame.output);
}
pub(super) fn plc_conceal(
plc: &mut SilkPlcState,
lpc_state: &mut [i32],
out: &mut [i16],
loss_count: u32,
) {
let frame_len = plc.subfr_length.saturating_mul(plc.nb_subfr);
let nb_coefs = plc.nb_coefs.min(MAX_LPC_ORDER).min(lpc_state.len());
if frame_len == 0 || out.len() != frame_len || nb_coefs == 0 {
out.fill(0);
return;
}
let attenuate_q16 = if loss_count == 0 {
1 << 16
} else {
((1 << 16) * (10 - loss_count.min(9)) as i32) / 10
};
let base_rand_scale_q16 = (plc.fs_khz.max(1) * 3) << 10;
let noise_gain_q16 = mul_q14_q16(plc.shape_gain, base_rand_scale_q16);
let mut synth_q14 = [0i32; MAX_LPC_ORDER + MAX_FRAME_LENGTH];
synth_q14[..nb_coefs].copy_from_slice(&lpc_state[..nb_coefs]);
for subframe_idx in 0..plc.nb_subfr {
let gain_q16 = plc.pitch_gain[subframe_idx.min(MAX_NB_SUBFR - 1)];
let pitch_offset = subframe_idx * plc.subfr_length;
for sample_idx in 0..plc.subfr_length {
let frame_idx = pitch_offset + sample_idx;
let pitch_sample = plc_pitch_sample(plc, frame_idx);
let mut excitation = mul_q16(pitch_sample, gain_q16);
plc.rand_seed = plc
.rand_seed
.wrapping_mul(196_314_165)
.wrapping_add(907_633_515);
let noise = (plc.rand_seed >> 16) as i16 as i32;
excitation = excitation.wrapping_add(mul_q16(noise, noise_gain_q16));
let hist_idx = nb_coefs + frame_idx;
let mut lpc_pred_q10 = (nb_coefs as i32) >> 1;
for coef_idx in 0..nb_coefs {
lpc_pred_q10 = smlawb(
lpc_pred_q10,
synth_q14[hist_idx - 1 - coef_idx],
plc.lpc_coeffs[coef_idx],
);
}
let current_q14 = mul_q16(
add_sat32(lshift_sat32(excitation, 14), lshift_sat32(lpc_pred_q10, 4)),
attenuate_q16,
);
synth_q14[hist_idx] = current_q14;
out[frame_idx] = sat16(rshift_round(current_q14, 14));
}
}
lpc_state[..nb_coefs].copy_from_slice(&synth_q14[frame_len..frame_len + nb_coefs]);
plc.last_frame_out.clear();
plc.last_frame_out.extend_from_slice(out);
plc.shape_gain = ((plc.shape_gain as i64 * 16_220) >> 14) as i32;
}
fn plc_pitch_sample(plc: &SilkPlcState, frame_idx: usize) -> i32 {
if plc.pitch_lag <= 0 || plc.last_frame_out.is_empty() {
return 0;
}
let lag = plc.pitch_lag as usize;
let history_len = plc.last_frame_out.len();
let lag_idx = frame_idx.wrapping_sub(lag) % history_len;
plc.last_frame_out[lag_idx] as i32
}
fn mul_q16(value: i32, gain_q16: i32) -> i32 {
((value as i64 * gain_q16 as i64) >> 16) as i32
}
fn mul_q14_q16(value_q14: i32, value_q16: i32) -> i32 {
((value_q14 as i64 * value_q16 as i64) >> 14) as i32
}
fn smlawb(a32: i32, b32: i32, c32: i32) -> i32 {
a32.wrapping_add(smulwb(b32, c32))
}
fn smulwb(a32: i32, b32: i32) -> i32 {
let b16 = b32 as i16 as i32;
let high = (a32 >> 16) * b16;
let low = ((a32 & 0xFFFF) * b16) >> 16;
high.wrapping_add(low)
}
fn add_sat32(a: i32, b: i32) -> i32 {
a.saturating_add(b)
}
fn lshift_sat32(value: i32, shift: usize) -> i32 {
let widened = (value as i64) << shift;
widened.clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32
}
fn rshift_round(value: i32, shift: usize) -> i32 {
if shift == 1 {
(value >> 1) + (value & 1)
} else {
((value >> (shift - 1)) + 1) >> 1
}
}
fn sat16(value: i32) -> i16 {
value.clamp(i16::MIN as i32, i16::MAX as i32) as i16
}