#![allow(clippy::too_many_arguments)]
use core::convert::TryFrom;
use crate::silk::decode_indices::ConditionalCoding;
use crate::silk::encoder::control_flp::EncoderControlFlp;
use crate::silk::encoder::state::MAX_FRAME_LENGTH;
use crate::silk::encoder::state_flp::EncoderStateFlp;
use crate::silk::find_lpc_flp::find_lpc_flp;
use crate::silk::find_ltp_flp::find_ltp_flp;
use crate::silk::ltp_analysis_filter_flp::ltp_analysis_filter_flp;
use crate::silk::ltp_scale_ctrl::LtpScaleCtrlParams;
use crate::silk::ltp_scale_ctrl_flp::ltp_scale_ctrl_flp;
use crate::silk::process_nlsfs::{ProcessNlsfConfig, process_nlsfs};
use crate::silk::quant_ltp_gains::silk_quant_ltp_gains;
use crate::silk::residual_energy_flp::residual_energy_flp;
use crate::silk::scale_vector::scale_copy_vector;
use crate::silk::sigproc_flp::silk_float2int;
use crate::silk::vq_wmat_ec::LTP_ORDER;
use crate::silk::{FrameSignalType, MAX_LPC_ORDER, MAX_NB_SUBFR};
use libm::powf;
const MAX_PREDICTION_POWER_GAIN: f32 = 1.0e4;
const MAX_PREDICTION_POWER_GAIN_AFTER_RESET: f32 = 1.0e2;
const Q17_SCALE: f32 = 131_072.0;
const INV_Q14_SCALE: f32 = 1.0 / 16_384.0;
const INV_Q12_SCALE: f32 = 1.0 / 4096.0;
const INV_LOG_GAIN_SCALE: f32 = 1.0 / 128.0;
const MAX_LPC_IN_PRE: usize = MAX_NB_SUBFR * MAX_LPC_ORDER + MAX_FRAME_LENGTH;
pub fn find_pred_coefs_flp(
encoder: &mut EncoderStateFlp,
control: &mut EncoderControlFlp,
res_pitch: &[f32],
x: &[f32],
cond_coding: ConditionalCoding,
) {
let nb_subfr = encoder.common.nb_subfr;
assert!(
nb_subfr == MAX_NB_SUBFR || nb_subfr == MAX_NB_SUBFR / 2,
"nb_subfr must be 2 or 4"
);
let order = encoder.common.predict_lpc_order;
assert!(
matches!(order, 6 | 8 | 10 | 12 | 16),
"unsupported LPC order: {order}"
);
let subfr_length = encoder.common.subfr_length;
assert!(subfr_length > 0, "subframe length must be positive");
let frame_length = encoder.common.frame_length;
assert_eq!(
frame_length,
nb_subfr * subfr_length,
"frame length must match nb_subfr × subfr_length"
);
assert!(
encoder.common.ltp_mem_length >= order,
"ltp_mem_length must be at least the LPC order"
);
let la_pitch = usize::try_from(encoder.common.la_pitch).expect("la_pitch must be non-negative");
let required_res_len = encoder.common.ltp_mem_length + frame_length + la_pitch;
assert!(
res_pitch.len() >= required_res_len,
"res_pitch slice too short for history + frame"
);
let total_input = encoder.common.ltp_mem_length + frame_length;
assert!(
x.len() >= total_input,
"input slice too short for history + frame"
);
let mut inv_gains = [0.0f32; MAX_NB_SUBFR];
for (inv_gain, &gain) in inv_gains
.iter_mut()
.zip(control.gains.iter().take(nb_subfr))
{
assert!(gain > 0.0, "subframe gains must stay positive");
*inv_gain = 1.0 / gain;
}
let chunk = subfr_length + order;
let lpc_in_pre_len = nb_subfr
.checked_mul(chunk)
.expect("nb_subfr × chunk overflow");
debug_assert!(
lpc_in_pre_len <= MAX_LPC_IN_PRE,
"LPC_in_pre exceeds stack buffer"
);
let mut lpc_in_pre_buf = [0.0f32; MAX_LPC_IN_PRE];
let lpc_in_pre = &mut lpc_in_pre_buf[..lpc_in_pre_len];
let taps_len = nb_subfr * LTP_ORDER;
if matches!(encoder.common.indices.signal_type, FrameSignalType::Voiced) {
ensure_pitch_history(encoder.common.ltp_mem_length, order, control.pitch_l[0]);
let mut xx_ltp = [0.0f32; MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER];
let mut x_ltp = [0.0f32; MAX_NB_SUBFR * LTP_ORDER];
find_ltp_flp(
&mut xx_ltp[..taps_len * LTP_ORDER],
&mut x_ltp[..taps_len],
res_pitch,
encoder.common.ltp_mem_length,
&control.pitch_l[..nb_subfr],
subfr_length,
nb_subfr,
encoder.common.arch,
);
quant_ltp_gains_flp(
&mut control.ltp_coef[..taps_len],
&mut encoder.common.indices.ltp_index[..nb_subfr],
&mut encoder.common.indices.per_index,
&mut encoder.common.sum_log_gain_q7,
&mut control.lt_pred_cod_gain,
&xx_ltp[..taps_len * LTP_ORDER],
&x_ltp[..taps_len],
subfr_length,
nb_subfr,
encoder.common.arch,
);
let ltp_params = LtpScaleCtrlParams {
packet_loss_perc: encoder.common.packet_loss_perc,
frames_per_packet: encoder.common.n_frames_per_packet,
lbrr_enabled: encoder.common.lbrr_enabled,
snr_db_q7: encoder.common.snr_db_q7,
};
let scale = ltp_scale_ctrl_flp(
<p_params,
&mut encoder.common.indices,
cond_coding,
control.lt_pred_cod_gain,
);
control.ltp_scale = scale;
let x_ptr_offset = encoder.common.ltp_mem_length - order;
ltp_analysis_filter_flp(
lpc_in_pre,
x,
x_ptr_offset,
&control.ltp_coef[..taps_len],
&control.pitch_l[..nb_subfr],
&inv_gains[..nb_subfr],
subfr_length,
nb_subfr,
order,
);
} else {
let mut x_ptr_idx = encoder.common.ltp_mem_length - order;
for (subfr, dest) in lpc_in_pre.chunks_mut(chunk).take(nb_subfr).enumerate() {
let src_end = x_ptr_idx + chunk;
assert!(
src_end <= x.len(),
"input slice too short for subframe {subfr}"
);
scale_copy_vector(dest, &x[x_ptr_idx..src_end], inv_gains[subfr]);
x_ptr_idx += subfr_length;
}
control.ltp_coef[..taps_len].fill(0.0);
encoder.common.indices.ltp_index[..nb_subfr].fill(0);
encoder.common.indices.per_index = 0;
encoder.common.indices.ltp_scale_index = 0;
control.lt_pred_cod_gain = 0.0;
encoder.common.sum_log_gain_q7 = 0;
control.ltp_scale = 0.0;
}
let min_inv_gain = if encoder.common.first_frame_after_reset {
1.0 / MAX_PREDICTION_POWER_GAIN_AFTER_RESET
} else {
let base = powf(2.0, control.lt_pred_cod_gain / 3.0) / MAX_PREDICTION_POWER_GAIN;
base / (0.25 + 0.75 * control.coding_quality)
};
let mut nlsf_q15 = [0i16; MAX_LPC_ORDER];
find_lpc_flp(
&mut encoder.common,
&mut nlsf_q15[..order],
lpc_in_pre,
min_inv_gain,
);
process_nlsfs_flp(&mut encoder.common, control, &mut nlsf_q15[..order]);
residual_energy_flp(
&mut control.res_nrg,
lpc_in_pre,
&control.pred_coef,
&control.gains,
subfr_length,
nb_subfr,
order,
);
encoder.common.prev_nlsf_q15[..order].copy_from_slice(&nlsf_q15[..order]);
}
fn ensure_pitch_history(ltp_mem_length: usize, order: usize, pitch: i32) {
assert!(pitch > 0, "pitch lag must be positive");
let pitch_usize = usize::try_from(pitch).expect("pitch lag must fit in usize");
let required = pitch_usize + (LTP_ORDER / 2);
assert!(
ltp_mem_length.saturating_sub(order) >= required,
"insufficient LTP history for pitch analysis"
);
}
fn quant_ltp_gains_flp(
b: &mut [f32],
cbk_index: &mut [i8],
periodicity_index: &mut i8,
sum_log_gain_q7: &mut i32,
pred_gain_db: &mut f32,
xx: &[f32],
x_x: &[f32],
subfr_len: usize,
nb_subfr: usize,
_arch: i32,
) {
assert!(matches!(nb_subfr, 2 | 4));
let taps_len = nb_subfr * LTP_ORDER;
assert_eq!(
b.len(),
taps_len,
"LTP coefficient buffer must hold nb_subfr × LTP_ORDER"
);
assert_eq!(
cbk_index.len(),
nb_subfr,
"codebook index slice must match nb_subfr"
);
assert_eq!(
xx.len(),
taps_len * LTP_ORDER,
"XX slice must contain nb_subfr correlation matrices"
);
assert_eq!(
x_x.len(),
taps_len,
"xX slice must contain nb_subfr correlation vectors"
);
let mut xx_q17 = [0i32; MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER];
for (dst, &src) in xx_q17.iter_mut().zip(xx.iter()) {
*dst = silk_float2int(src * Q17_SCALE);
}
let mut x_x_q17 = [0i32; MAX_NB_SUBFR * LTP_ORDER];
for (dst, &src) in x_x_q17.iter_mut().zip(x_x.iter()) {
*dst = silk_float2int(src * Q17_SCALE);
}
let mut b_q14 = [0i16; MAX_NB_SUBFR * LTP_ORDER];
let mut pred_gain_db_q7 = 0;
silk_quant_ltp_gains(
&mut b_q14[..taps_len],
cbk_index,
periodicity_index,
sum_log_gain_q7,
&mut pred_gain_db_q7,
&xx_q17[..taps_len * LTP_ORDER],
&x_x_q17[..taps_len],
subfr_len as i32,
nb_subfr,
);
for (dst, &src) in b.iter_mut().zip(b_q14.iter().take(taps_len)) {
*dst = f32::from(src) * INV_Q14_SCALE;
}
*pred_gain_db = (pred_gain_db_q7 as f32) * INV_LOG_GAIN_SCALE;
}
fn process_nlsfs_flp(
encoder: &mut crate::silk::encoder::state::EncoderStateCommon,
control: &mut EncoderControlFlp,
nlsf_q15: &mut [i16],
) {
let order = encoder.predict_lpc_order;
assert!(
nlsf_q15.len() >= order,
"NLSF buffer shorter than LPC order"
);
let survivors =
usize::try_from(encoder.nlsf_msvq_survivors).expect("survivor count must be non-negative");
let cfg = ProcessNlsfConfig {
speech_activity_q8: encoder.speech_activity_q8,
nb_subframes: encoder.nb_subfr,
predict_lpc_order: order,
use_interpolated_nlsfs: encoder.use_interpolated_nlsfs,
nlsf_msvq_survivors: survivors,
codebook: encoder.ps_nlsf_cb,
arch: encoder.arch,
};
let mut pred_coef_q12 = [[0i16; MAX_LPC_ORDER]; 2];
process_nlsfs(
&cfg,
&mut encoder.indices,
&mut pred_coef_q12,
nlsf_q15,
&encoder.prev_nlsf_q15[..order],
);
for (dst_row, src_row) in control.pred_coef.iter_mut().zip(pred_coef_q12.iter()) {
for (dst, &src) in dst_row.iter_mut().zip(src_row.iter().take(order)) {
*dst = f32::from(src) * INV_Q12_SCALE;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn unvoiced_path_resets_ltp_state() {
let mut encoder = EncoderStateFlp::default();
encoder.common.indices.signal_type = FrameSignalType::Unvoiced;
encoder.common.nlsf_msvq_survivors = 1;
let nb_subfr = encoder.common.nb_subfr;
let order = encoder.common.predict_lpc_order;
let total = encoder.common.ltp_mem_length + encoder.common.frame_length;
let la_pitch = usize::try_from(encoder.common.la_pitch).unwrap();
let res_pitch = vec![0.0f32; total + la_pitch];
let mut x = vec![0.0f32; total];
for sample in x.iter_mut().take(total) {
*sample = 0.001;
}
let mut control = EncoderControlFlp::default();
for gain in control.gains.iter_mut().take(nb_subfr) {
*gain = 1.0;
}
find_pred_coefs_flp(
&mut encoder,
&mut control,
&res_pitch,
&x,
ConditionalCoding::Independent,
);
assert!(
control.ltp_coef[..nb_subfr * LTP_ORDER]
.iter()
.all(|&c| c == 0.0)
);
assert_eq!(control.lt_pred_cod_gain, 0.0);
assert_eq!(encoder.common.sum_log_gain_q7, 0);
assert_eq!(encoder.common.indices.ltp_scale_index, 0);
assert_eq!(control.ltp_scale, 0.0);
assert!(control.res_nrg[..nb_subfr].iter().all(|v| v.is_finite()));
assert!(
control
.pred_coef
.iter()
.all(|row| row[..order].iter().all(|v| v.is_finite()))
);
}
}