#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CeltPostFilterParams {
pub octave: u32,
pub period: u32,
pub gain_index: u32,
pub tapset: u32,
}
impl CeltPostFilterParams {
#[inline]
#[must_use]
pub fn gain(self) -> f64 {
3.0 * (self.gain_index as f64 + 1.0) / 32.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CeltFramePrefix {
pub silence: bool,
pub post_filter: Option<CeltPostFilterParams>,
pub transient: bool,
pub intra: bool,
}
const POST_FILTER_LOGP: u32 = 1;
const TRANSIENT_ICDF: [u8; 2] = [1, 0];
const INTRA_ICDF: [u8; 2] = [1, 0];
const TAPSET_ICDF: [u8; 3] = [2, 1, 0];
const FTB_8: u32 = 3;
const FTB_4: u32 = 2;
pub fn decode_celt_frame_prefix(
rd: &mut crate::range_decoder::RangeDecoder<'_>,
) -> CeltFramePrefix {
let silence = rd.dec_bit_logp(15) == 1;
let post_filter = if rd.dec_bit_logp(POST_FILTER_LOGP) == 1 {
let octave = rd.dec_uint(7).unwrap_or(0);
let fine_pitch = rd.dec_bits(4 + octave);
let period = (16u32 << octave) + fine_pitch - 1;
let gain_index = rd.dec_bits(3);
let tapset = rd.dec_icdf(&TAPSET_ICDF, FTB_4);
Some(CeltPostFilterParams {
octave,
period,
gain_index,
tapset,
})
} else {
None
};
if silence {
return CeltFramePrefix {
silence: true,
post_filter,
transient: false,
intra: false,
};
}
let transient = rd.dec_icdf(&TRANSIENT_ICDF, FTB_8) == 1;
let intra = rd.dec_icdf(&INTRA_ICDF, FTB_8) == 1;
CeltFramePrefix {
silence: false,
post_filter,
transient,
intra,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::range_decoder::RangeDecoder;
#[test]
fn gain_formula() {
let p = CeltPostFilterParams {
octave: 0,
period: 15,
gain_index: 0,
tapset: 0,
};
assert!((p.gain() - 3.0 / 32.0).abs() < 1e-12);
let p7 = CeltPostFilterParams { gain_index: 7, ..p };
assert!((p7.gain() - 0.75).abs() < 1e-12);
}
#[test]
fn silence_frame_prefix_is_clean() {
let buf = [0x00u8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let mut rd = RangeDecoder::new(&buf);
let prefix = decode_celt_frame_prefix(&mut rd);
assert!(!rd.has_error());
if let Some(pf) = prefix.post_filter {
assert!((15..=1022).contains(&pf.period));
assert!(pf.octave <= 6);
assert!(pf.tapset <= 2);
}
}
#[test]
fn flag_tables_have_correct_shape() {
assert_eq!(TRANSIENT_ICDF, [1, 0]);
assert_eq!(INTRA_ICDF, [1, 0]);
assert_eq!(TAPSET_ICDF, [2, 1, 0]);
}
#[test]
fn post_filter_octave_in_range_and_reaches_six() {
let mut max_octave = 0u32;
let mut saw_post_filter = false;
for b0 in 0u16..=255 {
for b1 in 0u16..=255 {
let buf = [b0 as u8, b1 as u8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80];
let mut rd = RangeDecoder::new(&buf);
let p = decode_celt_frame_prefix(&mut rd);
if let Some(pf) = p.post_filter {
saw_post_filter = true;
assert!(pf.octave <= 6, "octave {} out of range", pf.octave);
max_octave = max_octave.max(pf.octave);
}
}
}
assert!(saw_post_filter, "no post-filter frame in the sweep");
assert_eq!(max_octave, 6, "octave 6 must be reachable (ec_dec_uint(7))");
}
#[test]
fn non_silent_prefix_runs_clean() {
for seed in [0x55u8, 0xaa, 0x0f, 0xf0] {
let buf = [seed; 12];
let mut rd = RangeDecoder::new(&buf);
let _ = decode_celt_frame_prefix(&mut rd);
assert!(!rd.has_error(), "seed {seed:#x} latched an error");
}
}
}