use oxideav_celt::range_encoder::RangeEncoder;
use oxideav_core::Result;
use crate::silk::excitation::MAG_NIBBLE_ICDF;
use crate::silk::lsf;
use crate::silk::tables;
use crate::toc::OpusBandwidth;
const NLSF_STAGE1_IDX: usize = 0;
const GAIN_INDEX_UNVOICED: i32 = 0;
const CARRIER_FULL_SCALE: f32 = 120.0;
pub struct SilkFrameEncoder {
bandwidth: OpusBandwidth,
lpc_order: usize,
subframe_len: usize,
n_subframes: usize,
prev_synth: Vec<f32>,
}
impl SilkFrameEncoder {
pub fn new_nb_20ms() -> Self {
let bandwidth = OpusBandwidth::Narrowband;
let lpc_order = 10;
let subframe_len = 40; let n_subframes = 4;
Self {
bandwidth,
lpc_order,
subframe_len,
n_subframes,
prev_synth: vec![0.0; lpc_order],
}
}
pub fn frame_len(&self) -> usize {
self.subframe_len * self.n_subframes
}
pub fn encode_frame_body(
&mut self,
pcm_internal: &[f32],
enc: &mut RangeEncoder,
) -> Result<()> {
debug_assert_eq!(pcm_internal.len(), self.frame_len());
let order = self.lpc_order;
let frame_len = self.frame_len();
let subframe_len = self.subframe_len;
let frame_type_sym: usize = 2;
enc.encode_icdf(frame_type_sym, &tables::FRAME_TYPE_ACTIVE_ICDF, 8);
let signal_type: u8 = 1; let _quant_offset_type: u8 = 0;
let residuals = vec![0i32; order];
let nlsf_q15 = synthesize_nlsf_like_decoder(NLSF_STAGE1_IDX, false, order, &residuals);
let nlsf_q15 = lsf::stabilize(&nlsf_q15, order);
let lpc = lsf::nlsf_to_lpc(&nlsf_q15, self.bandwidth);
let gain_index: i32 = GAIN_INDEX_UNVOICED;
let gain_q16 = super::gain_index_to_q16(gain_index);
let g = gain_q16.max(1) as f32 / 65536.0;
let scale = 128.0 / g;
let synth_hist = self.prev_synth.clone(); let mut out = vec![0f32; frame_len];
let mut residual = vec![0f32; frame_len];
let mut signed_mags = vec![0i32; frame_len];
for n in 0..frame_len {
let mut pred = 0f32;
for k in 1..=order {
let idx = n as i32 - k as i32;
let past = if idx >= 0 {
out[idx as usize]
} else {
synth_hist[(synth_hist.len() as i32 + idx) as usize]
};
pred += lpc[k - 1] * past;
}
let e_desired = pcm_internal[n] - pred;
residual[n] = e_desired;
let signed_mag_f = (e_desired * scale).round();
let mag_i = signed_mag_f.abs().clamp(0.0, CARRIER_FULL_SCALE) as i32;
let neg = signed_mag_f < 0.0;
let signed = if neg { -mag_i } else { mag_i };
signed_mags[n] = signed;
let e_quant = (signed as f32 / 128.0) * g;
out[n] = (e_quant + pred).clamp(-1.0, 1.0);
}
let msb = ((gain_index >> 3) & 0x7) as usize;
let lsb = (gain_index & 0x7) as usize;
let msb_icdf = match signal_type {
0 => &tables::GAIN_MSB_INACTIVE_ICDF,
1 => &tables::GAIN_MSB_UNVOICED_ICDF,
_ => &tables::GAIN_MSB_VOICED_ICDF,
};
enc.encode_icdf(msb, msb_icdf, 8);
enc.encode_icdf(lsb, &tables::GAIN_LSB_ICDF, 8);
for _ in 1..self.n_subframes {
enc.encode_icdf(4, &tables::GAIN_DELTA_ICDF, 8);
}
let stage1_icdf: &[u8] = &tables::NLSF_NB_STAGE1_UNVOICED_ICDF;
enc.encode_icdf(NLSF_STAGE1_IDX, stage1_icdf, 8);
let uniform_11 = &tables::NLSF_RESIDUAL_UNIFORM_11_ICDF;
for &r in &residuals {
let mag = (r + 4).clamp(0, 10) as usize;
enc.encode_icdf(mag, uniform_11, 8);
if mag != 4 {
}
}
enc.encode_icdf(3, &[192, 128, 64, 0], 8);
enc.encode_icdf(0, &tables::LCG_SEED_ICDF, 8);
let rate_icdf: &[u8] = &tables::RATE_LEVEL_INACTIVE_ICDF;
enc.encode_icdf(0, rate_icdf, 8);
let n_shells = frame_len.div_ceil(16);
let pulse_icdf = &tables::PULSE_COUNT_ICDF[0];
for _ in 0..n_shells {
enc.encode_icdf(0, pulse_icdf, 8);
}
let _ = residual;
let _ = subframe_len; for &signed in &signed_mags {
let mag_i = signed.unsigned_abs() as i32;
let neg = signed < 0;
let hi = ((mag_i >> 4) & 0xf) as usize;
let lo = (mag_i & 0xf) as usize;
enc.encode_icdf(hi, &MAG_NIBBLE_ICDF, 8);
enc.encode_icdf(lo, &MAG_NIBBLE_ICDF, 8);
if mag_i != 0 {
enc.encode_bit_logp(neg, 1);
}
}
let start = out.len().saturating_sub(order);
self.prev_synth.clear();
self.prev_synth.extend_from_slice(&out[start..]);
Ok(())
}
}
fn synthesize_nlsf_like_decoder(
stage1: usize,
voiced: bool,
order: usize,
residuals: &[i32],
) -> Vec<i16> {
let tilt = (stage1 as f32 / 32.0) * 0.25 + if voiced { 0.0 } else { 0.15 };
let mut nlsf = vec![0i16; order];
for k in 0..order {
let base = (k as f32 + 1.0) / (order as f32 + 1.0);
let tilted = base.powf(1.0 + tilt);
let mut q15 = (tilted * 32768.0) as i32;
q15 += residuals[k].clamp(-7, 7) * 128;
nlsf[k] = q15.clamp(1, 32767) as i16;
}
nlsf
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nlsf_template_mirrors_decoder() {
let nlsf = synthesize_nlsf_like_decoder(0, false, 10, &[0; 10]);
assert_eq!(nlsf.len(), 10);
let stable = crate::silk::lsf::stabilize(&nlsf, 10);
for w in stable.windows(2) {
assert!(
w[1] >= w[0],
"stabilised NLSF should be non-decreasing ({} → {})",
w[0],
w[1]
);
}
}
#[test]
fn encode_decode_zero_frame_matches() {
use oxideav_celt::range_decoder::RangeDecoder;
let mut enc = SilkFrameEncoder::new_nb_20ms();
let pcm = vec![0.0f32; 160];
let mut re = RangeEncoder::new(512);
re.encode_bit_logp(true, 1);
re.encode_bit_logp(false, 1);
enc.encode_frame_body(&pcm, &mut re).unwrap();
let buf = re.done().unwrap();
let mut rc = RangeDecoder::new(&buf);
let _vad = rc.decode_bit_logp(1);
let _lbrr = rc.decode_bit_logp(1);
let mut s = crate::silk::SilkChannelState::new();
let decoded = crate::silk::decode_frame_body_pub(
&mut rc,
true,
OpusBandwidth::Narrowband,
10,
40,
4,
&mut s,
)
.unwrap();
let peak = decoded.iter().copied().fold(0f32, |a, b| a.max(b.abs()));
println!("zero-frame roundtrip peak = {peak:.6}");
assert!(
peak < 0.001,
"zero-frame decode should be ~0, got peak {peak}"
);
}
#[test]
fn encode_decode_zero_frame_produces_finite_output() {
let mut enc = SilkFrameEncoder::new_nb_20ms();
let pcm = vec![0.0f32; 160];
let mut re = RangeEncoder::new(512);
enc.encode_frame_body(&pcm, &mut re).unwrap();
let buf = re.done().unwrap();
assert!(!buf.is_empty());
assert_eq!(buf.len(), 512);
}
#[test]
fn encode_decode_one_frame_internal_rate_snr() {
use oxideav_celt::range_decoder::RangeDecoder;
use oxideav_core::Result;
let mut enc = SilkFrameEncoder::new_nb_20ms();
let freq = 300.0f32;
let pcm: Vec<f32> = (0..160)
.map(|i| (2.0 * std::f32::consts::PI * freq * i as f32 / 8000.0).sin() * 0.3)
.collect();
let mut re = RangeEncoder::new(512);
re.encode_bit_logp(true, 1); re.encode_bit_logp(false, 1); enc.encode_frame_body(&pcm, &mut re).unwrap();
let buf = re.done().unwrap();
let mut dec_state = crate::silk::SilkChannelState::new();
let mut rc = RangeDecoder::new(&buf);
let _vad = rc.decode_bit_logp(1);
let _lbrr = rc.decode_bit_logp(1);
let decoded: Result<Vec<f32>> = decode_one_nb_mono_frame(&mut rc, &mut dec_state);
let decoded = decoded.expect("decode");
assert_eq!(decoded.len(), 160);
let sig: f64 = pcm.iter().map(|v| (*v as f64) * (*v as f64)).sum();
let err: f64 = pcm
.iter()
.zip(decoded.iter())
.map(|(a, b)| {
let e = (*a - *b) as f64;
e * e
})
.sum();
let snr = 10.0 * (sig / err.max(1e-30)).log10();
println!("internal-rate SNR: {snr:.2} dB");
assert!(snr > 25.0, "internal-rate SNR {snr:.2} dB below 25 dB bar");
}
fn decode_one_nb_mono_frame(
rc: &mut oxideav_celt::range_decoder::RangeDecoder<'_>,
state: &mut crate::silk::SilkChannelState,
) -> oxideav_core::Result<Vec<f32>> {
crate::silk::decode_frame_body_pub(
rc,
true, OpusBandwidth::Narrowband,
10,
40,
4,
state,
)
}
}