use crate::range_decoder::RangeDecoder;
use crate::silk_frame::SignalType;
use crate::toc::Bandwidth;
use crate::Error;
pub const LTP_MAX_SUBFRAMES: usize = 4;
pub const LTP_FILTER_TAPS: usize = 5;
const LAG_HIGH_ICDF: &[u8] = &[
253, 250, 244, 233, 212, 182, 150, 131, 120, 110, 98, 85, 72, 60, 49, 40, 32, 25, 19, 15, 13,
11, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
];
const LAG_LOW_ICDF_NB: &[u8] = &[192, 128, 64, 0]; const LAG_LOW_ICDF_MB: &[u8] = &[213, 171, 128, 85, 43, 0]; const LAG_LOW_ICDF_WB: &[u8] = &[224, 192, 160, 128, 96, 64, 32, 0];
const LAG_DELTA_ICDF: &[u8] = &[
210, 208, 206, 203, 199, 193, 183, 168, 142, 104, 74, 52, 37, 27, 20, 14, 10, 6, 4, 2, 0,
];
const CONTOUR_ICDF_NB_10MS: &[u8] = &[113, 63, 0]; const CONTOUR_ICDF_NB_20MS: &[u8] = &[
188, 176, 155, 138, 119, 97, 67, 43, 26, 10, 0,
];
const CONTOUR_ICDF_MBWB_10MS: &[u8] = &[
165, 119, 80, 61, 47, 35, 27, 20, 14, 9, 4, 0,
];
const CONTOUR_ICDF_MBWB_20MS: &[u8] = &[
223, 201, 183, 167, 152, 138, 124, 111, 98, 88, 79, 70, 62, 56, 50, 44, 39, 35, 31, 27, 24, 21,
18, 16, 14, 12, 10, 8, 6, 4, 3, 2, 1, 0,
];
const CONTOUR_NB_10MS: &[[i8; 2]; 3] = &[[0, 0], [1, 0], [0, 1]];
const CONTOUR_NB_20MS: &[[i8; 4]; 11] = &[
[0, 0, 0, 0],
[2, 1, 0, -1],
[-1, 0, 1, 2],
[-1, 0, 0, 1],
[-1, 0, 0, 0],
[0, 0, 0, 1],
[0, 0, 1, 1],
[1, 1, 0, 0],
[1, 0, 0, 0],
[0, 0, 0, -1],
[1, 0, 0, -1],
];
const CONTOUR_MBWB_10MS: &[[i8; 2]; 12] = &[
[0, 0],
[0, 1],
[1, 0],
[-1, 1],
[1, -1],
[-1, 2],
[2, -1],
[-2, 2],
[2, -2],
[-2, 3],
[3, -2],
[-3, 3],
];
const CONTOUR_MBWB_20MS: &[[i8; 4]; 34] = &[
[0, 0, 0, 0],
[0, 0, 1, 1],
[1, 1, 0, 0],
[-1, 0, 0, 0],
[0, 0, 0, 1],
[1, 0, 0, 0],
[-1, 0, 0, 1],
[0, 0, 0, -1],
[-1, 0, 1, 2],
[1, 0, 0, -1],
[-2, -1, 1, 2],
[2, 1, 0, -1],
[-2, 0, 0, 2],
[-2, 0, 1, 3],
[2, 1, -1, -2],
[-3, -1, 1, 3],
[2, 0, 0, -2],
[3, 1, 0, -2],
[-3, -1, 2, 4],
[-4, -1, 1, 4],
[3, 1, -1, -3],
[-4, -1, 2, 5],
[4, 2, -1, -3],
[4, 1, -1, -4],
[-5, -1, 2, 6],
[5, 2, -1, -4],
[-6, -2, 2, 6],
[-5, -2, 2, 5],
[6, 2, -1, -5],
[-7, -2, 3, 8],
[6, 2, -2, -6],
[5, 2, -2, -5],
[8, 3, -2, -7],
[-9, -3, 3, 9],
];
const PERIODICITY_ICDF: &[u8] = &[179, 99, 0];
const LTP_FILTER_ICDF_P0: &[u8] = &[
71, 56, 43, 30, 21, 12, 6, 0,
];
const LTP_FILTER_ICDF_P1: &[u8] = &[
199, 165, 144, 124, 109, 96, 84, 71, 61, 51, 42, 32, 23, 15, 8, 0,
];
const LTP_FILTER_ICDF_P2: &[u8] = &[
241, 225, 211, 199, 187, 175, 164, 153, 142, 132, 123, 114, 105, 96, 88, 80, 72, 64, 57, 50, 44,
38, 33, 29, 24, 20, 16, 12, 9, 5, 2, 0,
];
const LTP_TAPS_P0: &[[i8; LTP_FILTER_TAPS]; 8] = &[
[4, 6, 24, 7, 5],
[0, 0, 2, 0, 0],
[12, 28, 41, 13, -4],
[-9, 15, 42, 25, 14],
[1, -2, 62, 41, -9],
[-10, 37, 65, -4, 3],
[-6, 4, 66, 7, -8],
[16, 14, 38, -3, 33],
];
const LTP_TAPS_P1: &[[i8; LTP_FILTER_TAPS]; 16] = &[
[13, 22, 39, 23, 12],
[-1, 36, 64, 27, -6],
[-7, 10, 55, 43, 17],
[1, 1, 8, 1, 1],
[6, -11, 74, 53, -9],
[-12, 55, 76, -12, 8],
[-3, 3, 93, 27, -4],
[26, 39, 59, 3, -8],
[2, 0, 77, 11, 9],
[-8, 22, 44, -6, 7],
[40, 9, 26, 3, 9],
[-7, 20, 101, -7, 4],
[3, -8, 42, 26, 0],
[-15, 33, 68, 2, 23],
[-2, 55, 46, -2, 15],
[3, -1, 21, 16, 41],
];
const LTP_TAPS_P2: &[[i8; LTP_FILTER_TAPS]; 32] = &[
[-6, 27, 61, 39, 5],
[-11, 42, 88, 4, 1],
[-2, 60, 65, 6, -4],
[-1, -5, 73, 56, 1],
[-9, 19, 94, 29, -9],
[0, 12, 99, 6, 4],
[8, -19, 102, 46, -13],
[3, 2, 13, 3, 2],
[9, -21, 84, 72, -18],
[-11, 46, 104, -22, 8],
[18, 38, 48, 23, 0],
[-16, 70, 83, -21, 11],
[5, -11, 117, 22, -8],
[-6, 23, 117, -12, 3],
[3, -8, 95, 28, 4],
[-10, 15, 77, 60, -15],
[-1, 4, 124, 2, -4],
[3, 38, 84, 24, -25],
[2, 13, 42, 13, 31],
[21, -4, 56, 46, -1],
[-1, 35, 79, -13, 19],
[-7, 65, 88, -9, -14],
[20, 4, 81, 49, -29],
[20, 0, 75, 3, -17],
[5, -9, 44, 92, -8],
[1, -3, 22, 69, 31],
[-6, 95, 41, -12, 5],
[39, 67, 16, -4, 1],
[0, -6, 120, 55, -36],
[-13, 44, 122, 4, -24],
[81, 5, 11, 3, 7],
[2, 0, 9, 10, 88],
];
const LTP_SCALING_ICDF: &[u8] = &[128, 64, 0];
const LTP_SCALING_VALUES_Q14: [u16; 3] = [15565, 12288, 8192];
pub const LTP_SCALING_DEFAULT_Q14: u16 = 15565;
struct LagLowSpec {
icdf: &'static [u8],
scale: i32,
lag_min: i32,
lag_max: i32,
}
fn lag_low_spec(bandwidth: Bandwidth) -> Result<LagLowSpec, Error> {
match bandwidth {
Bandwidth::Nb => Ok(LagLowSpec {
icdf: LAG_LOW_ICDF_NB,
scale: 4,
lag_min: 16,
lag_max: 144,
}),
Bandwidth::Mb => Ok(LagLowSpec {
icdf: LAG_LOW_ICDF_MB,
scale: 6,
lag_min: 24,
lag_max: 216,
}),
Bandwidth::Wb => Ok(LagLowSpec {
icdf: LAG_LOW_ICDF_WB,
scale: 8,
lag_min: 32,
lag_max: 288,
}),
Bandwidth::Swb | Bandwidth::Fb => Err(Error::MalformedPacket),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LagCoding {
Absolute,
Relative {
previous_lag: i32,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LtpConfig {
pub bandwidth: Bandwidth,
pub signal_type: SignalType,
pub num_subframes: u8,
pub lag_coding: LagCoding,
pub ltp_scaling_present: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LtpParameters {
voiced: bool,
primary_lag: i32,
contour_index: u8,
pitch_lags: [i32; LTP_MAX_SUBFRAMES],
periodicity_index: u8,
filter_taps_q7: [[i8; LTP_FILTER_TAPS]; LTP_MAX_SUBFRAMES],
filter_indices: [u8; LTP_MAX_SUBFRAMES],
ltp_scaling_q14: u16,
len: u8,
}
impl LtpParameters {
pub fn decode(rd: &mut RangeDecoder<'_>, cfg: LtpConfig) -> Result<Self, Error> {
if cfg.num_subframes != 2 && cfg.num_subframes != 4 {
return Err(Error::MalformedPacket);
}
let spec = lag_low_spec(cfg.bandwidth)?;
let num = cfg.num_subframes as usize;
if cfg.signal_type != SignalType::Voiced {
return Ok(Self {
voiced: false,
primary_lag: 0,
contour_index: 0,
pitch_lags: [0; LTP_MAX_SUBFRAMES],
periodicity_index: 0,
filter_taps_q7: [[0; LTP_FILTER_TAPS]; LTP_MAX_SUBFRAMES],
filter_indices: [0; LTP_MAX_SUBFRAMES],
ltp_scaling_q14: LTP_SCALING_DEFAULT_Q14,
len: cfg.num_subframes,
});
}
let primary_lag = Self::decode_primary_lag(rd, &spec, cfg.lag_coding);
let contour_index = Self::decode_contour_index(rd, cfg.bandwidth, num);
let mut pitch_lags = [0i32; LTP_MAX_SUBFRAMES];
for (k, slot) in pitch_lags.iter_mut().enumerate().take(num) {
let offset = contour_offset(cfg.bandwidth, num, contour_index, k) as i32;
*slot = (primary_lag + offset).clamp(spec.lag_min, spec.lag_max);
}
let periodicity_index = rd.dec_icdf(PERIODICITY_ICDF, 8) as u8;
let mut filter_indices = [0u8; LTP_MAX_SUBFRAMES];
let mut filter_taps_q7 = [[0i8; LTP_FILTER_TAPS]; LTP_MAX_SUBFRAMES];
let filter_icdf = ltp_filter_icdf(periodicity_index);
for k in 0..num {
let idx = rd.dec_icdf(filter_icdf, 8) as u8;
filter_indices[k] = idx;
filter_taps_q7[k] = ltp_filter_taps(periodicity_index, idx);
}
let ltp_scaling_q14 = if cfg.ltp_scaling_present {
let s = rd.dec_icdf(LTP_SCALING_ICDF, 8) as usize;
LTP_SCALING_VALUES_Q14[s]
} else {
LTP_SCALING_DEFAULT_Q14
};
Ok(Self {
voiced: true,
primary_lag,
contour_index,
pitch_lags,
periodicity_index,
filter_taps_q7,
filter_indices,
ltp_scaling_q14,
len: cfg.num_subframes,
})
}
fn decode_primary_lag(rd: &mut RangeDecoder<'_>, spec: &LagLowSpec, coding: LagCoding) -> i32 {
match coding {
LagCoding::Absolute => Self::decode_absolute_lag(rd, spec),
LagCoding::Relative { previous_lag } => {
let delta_lag_index = rd.dec_icdf(LAG_DELTA_ICDF, 8) as i32;
if delta_lag_index == 0 {
Self::decode_absolute_lag(rd, spec)
} else {
previous_lag + (delta_lag_index - 9)
}
}
}
}
fn decode_absolute_lag(rd: &mut RangeDecoder<'_>, spec: &LagLowSpec) -> i32 {
let lag_high = rd.dec_icdf(LAG_HIGH_ICDF, 8) as i32;
let lag_low = rd.dec_icdf(spec.icdf, 8) as i32;
lag_high * spec.scale + lag_low + spec.lag_min
}
fn decode_contour_index(rd: &mut RangeDecoder<'_>, bandwidth: Bandwidth, num: usize) -> u8 {
let icdf = contour_icdf(bandwidth, num);
rd.dec_icdf(icdf, 8) as u8
}
pub fn is_voiced(&self) -> bool {
self.voiced
}
pub fn primary_lag(&self) -> i32 {
self.primary_lag
}
pub fn contour_index(&self) -> u8 {
self.contour_index
}
pub fn pitch_lags(&self) -> &[i32] {
if self.voiced {
&self.pitch_lags[..self.len as usize]
} else {
&[]
}
}
pub fn periodicity_index(&self) -> u8 {
self.periodicity_index
}
pub fn filter_taps_q7(&self) -> &[[i8; LTP_FILTER_TAPS]] {
if self.voiced {
&self.filter_taps_q7[..self.len as usize]
} else {
&[]
}
}
pub fn filter_indices(&self) -> &[u8] {
if self.voiced {
&self.filter_indices[..self.len as usize]
} else {
&[]
}
}
pub fn ltp_scaling_q14(&self) -> u16 {
self.ltp_scaling_q14
}
pub fn len(&self) -> usize {
self.len as usize
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
fn contour_icdf(bandwidth: Bandwidth, num: usize) -> &'static [u8] {
match (bandwidth, num) {
(Bandwidth::Nb, 2) => CONTOUR_ICDF_NB_10MS,
(Bandwidth::Nb, _) => CONTOUR_ICDF_NB_20MS,
(_, 2) => CONTOUR_ICDF_MBWB_10MS,
(_, _) => CONTOUR_ICDF_MBWB_20MS,
}
}
fn contour_offset(bandwidth: Bandwidth, num: usize, index: u8, k: usize) -> i8 {
let i = index as usize;
match (bandwidth, num) {
(Bandwidth::Nb, 2) => CONTOUR_NB_10MS[i][k],
(Bandwidth::Nb, _) => CONTOUR_NB_20MS[i][k],
(_, 2) => CONTOUR_MBWB_10MS[i][k],
(_, _) => CONTOUR_MBWB_20MS[i][k],
}
}
fn ltp_filter_icdf(periodicity_index: u8) -> &'static [u8] {
match periodicity_index {
0 => LTP_FILTER_ICDF_P0,
1 => LTP_FILTER_ICDF_P1,
_ => LTP_FILTER_ICDF_P2,
}
}
fn ltp_filter_taps(periodicity_index: u8, filter_index: u8) -> [i8; LTP_FILTER_TAPS] {
let i = filter_index as usize;
match periodicity_index {
0 => LTP_TAPS_P0[i],
1 => LTP_TAPS_P1[i],
_ => LTP_TAPS_P2[i],
}
}
#[cfg(test)]
mod tests {
use super::*;
fn check_icdf(pdf: &[u32], icdf: &[u8]) {
assert_eq!(pdf.iter().sum::<u32>(), 256, "PDF must sum to 256");
let mut cum = 0u32;
let mut expected = Vec::new();
for &p in pdf {
cum += p;
expected.push((256 - cum) as u8);
}
assert_eq!(icdf, expected.as_slice(), "iCDF mismatch");
assert_eq!(*icdf.last().unwrap(), 0, "iCDF must terminate with 0");
for w in icdf.windows(2) {
assert!(w[0] > w[1], "iCDF not strictly decreasing: {icdf:?}");
}
assert_eq!(icdf.len(), pdf.len(), "iCDF length must equal PDF length");
}
#[test]
fn table29_high_part_icdf() {
check_icdf(
&[
3, 3, 6, 11, 21, 30, 32, 19, 11, 10, 12, 13, 13, 12, 11, 9, 8, 7, 6, 4, 2, 2, 2, 1,
1, 1, 1, 1, 1, 1, 1, 1,
],
LAG_HIGH_ICDF,
);
assert_eq!(LAG_HIGH_ICDF.len(), 32);
}
#[test]
fn table30_low_part_icdfs() {
check_icdf(&[64, 64, 64, 64], LAG_LOW_ICDF_NB);
check_icdf(&[43, 42, 43, 43, 42, 43], LAG_LOW_ICDF_MB);
check_icdf(&[32, 32, 32, 32, 32, 32, 32, 32], LAG_LOW_ICDF_WB);
}
#[test]
fn table30_scales_and_ranges() {
let nb = lag_low_spec(Bandwidth::Nb).unwrap();
assert_eq!((nb.scale, nb.lag_min, nb.lag_max), (4, 16, 144));
let mb = lag_low_spec(Bandwidth::Mb).unwrap();
assert_eq!((mb.scale, mb.lag_min, mb.lag_max), (6, 24, 216));
let wb = lag_low_spec(Bandwidth::Wb).unwrap();
assert_eq!((wb.scale, wb.lag_min, wb.lag_max), (8, 32, 288));
assert!(lag_low_spec(Bandwidth::Swb).is_err());
assert!(lag_low_spec(Bandwidth::Fb).is_err());
}
#[test]
fn table31_delta_icdf() {
check_icdf(
&[
46, 2, 2, 3, 4, 6, 10, 15, 26, 38, 30, 22, 15, 10, 7, 6, 4, 4, 2, 2, 2,
],
LAG_DELTA_ICDF,
);
assert_eq!(LAG_DELTA_ICDF.len(), 21);
}
#[test]
fn table32_contour_icdfs() {
check_icdf(&[143, 50, 63], CONTOUR_ICDF_NB_10MS);
check_icdf(
&[68, 12, 21, 17, 19, 22, 30, 24, 17, 16, 10],
CONTOUR_ICDF_NB_20MS,
);
check_icdf(
&[91, 46, 39, 19, 14, 12, 8, 7, 6, 5, 5, 4],
CONTOUR_ICDF_MBWB_10MS,
);
check_icdf(
&[
33, 22, 18, 16, 15, 14, 14, 13, 13, 10, 9, 9, 8, 6, 6, 6, 5, 4, 4, 4, 3, 3, 3, 2,
2, 2, 2, 2, 2, 2, 1, 1, 1, 1,
],
CONTOUR_ICDF_MBWB_20MS,
);
}
#[test]
fn table37_periodicity_icdf() {
check_icdf(&[77, 80, 99], PERIODICITY_ICDF);
}
#[test]
fn table38_ltp_filter_icdfs() {
check_icdf(&[185, 15, 13, 13, 9, 9, 6, 6], LTP_FILTER_ICDF_P0);
check_icdf(
&[57, 34, 21, 20, 15, 13, 12, 13, 10, 10, 9, 10, 9, 8, 7, 8],
LTP_FILTER_ICDF_P1,
);
check_icdf(
&[
15, 16, 14, 12, 12, 12, 11, 11, 11, 10, 9, 9, 9, 9, 8, 8, 8, 8, 7, 7, 6, 6, 5, 4,
5, 4, 4, 4, 3, 4, 3, 2,
],
LTP_FILTER_ICDF_P2,
);
}
#[test]
fn table42_ltp_scaling_icdf() {
check_icdf(&[128, 64, 64], LTP_SCALING_ICDF);
assert_eq!(LTP_SCALING_VALUES_Q14, [15565, 12288, 8192]);
assert_eq!(LTP_SCALING_DEFAULT_Q14, 15565);
}
#[test]
fn contour_codebook_sizes_match_pdfs() {
assert_eq!(CONTOUR_NB_10MS.len(), CONTOUR_ICDF_NB_10MS.len());
assert_eq!(CONTOUR_NB_20MS.len(), CONTOUR_ICDF_NB_20MS.len());
assert_eq!(CONTOUR_MBWB_10MS.len(), CONTOUR_ICDF_MBWB_10MS.len());
assert_eq!(CONTOUR_MBWB_20MS.len(), CONTOUR_ICDF_MBWB_20MS.len());
assert_eq!(CONTOUR_NB_10MS[0], [0, 0]);
assert_eq!(CONTOUR_NB_20MS[0], [0, 0, 0, 0]);
assert_eq!(CONTOUR_MBWB_10MS[0], [0, 0]);
assert_eq!(CONTOUR_MBWB_20MS[0], [0, 0, 0, 0]);
assert_eq!(CONTOUR_NB_20MS[1], [2, 1, 0, -1]);
assert_eq!(CONTOUR_MBWB_20MS[33], [-9, -3, 3, 9]);
assert_eq!(CONTOUR_MBWB_10MS[11], [-3, 3]);
}
#[test]
fn ltp_filter_codebook_sizes_match_pdfs() {
assert_eq!(LTP_TAPS_P0.len(), LTP_FILTER_ICDF_P0.len());
assert_eq!(LTP_TAPS_P1.len(), LTP_FILTER_ICDF_P1.len());
assert_eq!(LTP_TAPS_P2.len(), LTP_FILTER_ICDF_P2.len());
assert_eq!(LTP_TAPS_P0.len(), 8);
assert_eq!(LTP_TAPS_P1.len(), 16);
assert_eq!(LTP_TAPS_P2.len(), 32);
assert_eq!(LTP_TAPS_P0[0], [4, 6, 24, 7, 5]);
assert_eq!(LTP_TAPS_P0[7], [16, 14, 38, -3, 33]);
assert_eq!(LTP_TAPS_P1[0], [13, 22, 39, 23, 12]);
assert_eq!(LTP_TAPS_P1[15], [3, -1, 21, 16, 41]);
assert_eq!(LTP_TAPS_P2[0], [-6, 27, 61, 39, 5]);
assert_eq!(LTP_TAPS_P2[31], [2, 0, 9, 10, 88]);
}
#[test]
fn non_voiced_reads_nothing() {
let buf = [0x5A, 0xC3, 0x17, 0x9E, 0x42, 0xFB, 0x08, 0x71, 0x2D, 0xB6];
for signal_type in [SignalType::Inactive, SignalType::Unvoiced] {
let mut rd = RangeDecoder::new(&buf);
let tell_before = rd.tell();
let cfg = LtpConfig {
bandwidth: Bandwidth::Wb,
signal_type,
num_subframes: 4,
lag_coding: LagCoding::Absolute,
ltp_scaling_present: true,
};
let ltp = LtpParameters::decode(&mut rd, cfg).unwrap();
assert_eq!(rd.tell(), tell_before, "non-voiced must consume no bits");
assert!(!ltp.is_voiced());
assert!(ltp.pitch_lags().is_empty());
assert!(ltp.filter_taps_q7().is_empty());
assert_eq!(ltp.ltp_scaling_q14(), LTP_SCALING_DEFAULT_Q14);
}
}
#[test]
fn rejects_bad_subframe_count() {
let buf = [0x00u8; 8];
let mut rd = RangeDecoder::new(&buf);
let cfg = LtpConfig {
bandwidth: Bandwidth::Nb,
signal_type: SignalType::Voiced,
num_subframes: 3,
lag_coding: LagCoding::Absolute,
ltp_scaling_present: false,
};
assert!(LtpParameters::decode(&mut rd, cfg).is_err());
}
#[test]
fn rejects_non_silk_bandwidth() {
let buf = [0x00u8; 8];
for bw in [Bandwidth::Swb, Bandwidth::Fb] {
let mut rd = RangeDecoder::new(&buf);
let cfg = LtpConfig {
bandwidth: bw,
signal_type: SignalType::Voiced,
num_subframes: 4,
lag_coding: LagCoding::Absolute,
ltp_scaling_present: false,
};
assert!(LtpParameters::decode(&mut rd, cfg).is_err());
}
}
fn manual_absolute_lag(rd: &mut RangeDecoder<'_>, spec: &LagLowSpec) -> i32 {
let high = rd.dec_icdf(LAG_HIGH_ICDF, 8) as i32;
let low = rd.dec_icdf(spec.icdf, 8) as i32;
high * spec.scale + low + spec.lag_min
}
#[test]
fn voiced_absolute_pitch_lags_in_range_and_match_formula() {
let buf = [
0x12, 0x9C, 0x5E, 0xA3, 0x77, 0x10, 0xBB, 0x42, 0x88, 0x01, 0xFE, 0x6D,
];
for bandwidth in [Bandwidth::Nb, Bandwidth::Mb, Bandwidth::Wb] {
for &num in &[2usize, 4usize] {
let spec = lag_low_spec(bandwidth).unwrap();
let mut rd = RangeDecoder::new(&buf);
let cfg = LtpConfig {
bandwidth,
signal_type: SignalType::Voiced,
num_subframes: num as u8,
lag_coding: LagCoding::Absolute,
ltp_scaling_present: true,
};
let ltp = LtpParameters::decode(&mut rd, cfg).unwrap();
let mut rd2 = RangeDecoder::new(&buf);
let primary = manual_absolute_lag(&mut rd2, &spec);
let contour = rd2.dec_icdf(contour_icdf(bandwidth, num), 8) as u8;
assert_eq!(ltp.primary_lag(), primary);
assert_eq!(ltp.contour_index(), contour);
assert_eq!(ltp.pitch_lags().len(), num);
for (k, &lag) in ltp.pitch_lags().iter().enumerate() {
let off = contour_offset(bandwidth, num, contour, k) as i32;
let expected = (primary + off).clamp(spec.lag_min, spec.lag_max);
assert_eq!(lag, expected, "bw={bandwidth:?} num={num} k={k}");
assert!(
(spec.lag_min..=spec.lag_max).contains(&lag),
"lag {lag} out of [{}, {}]",
spec.lag_min,
spec.lag_max
);
}
assert_eq!(ltp.filter_taps_q7().len(), num);
let p = ltp.periodicity_index();
assert!(p <= 2);
for (k, &fi) in ltp.filter_indices().iter().enumerate() {
let expected = ltp_filter_taps(p, fi);
assert_eq!(ltp.filter_taps_q7()[k], expected);
}
}
}
}
#[test]
fn voiced_relative_nonzero_delta_uses_previous_lag() {
let buf = [
0x7F, 0x3A, 0x91, 0x04, 0xCD, 0x6E, 0x22, 0xB0, 0x15, 0x88, 0x40, 0xEE,
];
let bandwidth = Bandwidth::Wb;
let num = 4usize;
let spec = lag_low_spec(bandwidth).unwrap();
let previous_lag = 100;
let mut rd = RangeDecoder::new(&buf);
let cfg = LtpConfig {
bandwidth,
signal_type: SignalType::Voiced,
num_subframes: num as u8,
lag_coding: LagCoding::Relative { previous_lag },
ltp_scaling_present: false,
};
let ltp = LtpParameters::decode(&mut rd, cfg).unwrap();
let mut rd2 = RangeDecoder::new(&buf);
let delta = rd2.dec_icdf(LAG_DELTA_ICDF, 8) as i32;
let primary = if delta == 0 {
manual_absolute_lag(&mut rd2, &spec)
} else {
previous_lag + (delta - 9)
};
assert_eq!(ltp.primary_lag(), primary);
}
#[test]
fn relative_zero_delta_falls_back_to_absolute() {
let buf = [
0xAA, 0x55, 0xF0, 0x0F, 0x3C, 0xC3, 0x99, 0x66, 0x12, 0x34, 0x56, 0x78,
];
let bandwidth = Bandwidth::Mb;
let num = 2usize;
let spec = lag_low_spec(bandwidth).unwrap();
let mut rd = RangeDecoder::new(&buf);
let cfg = LtpConfig {
bandwidth,
signal_type: SignalType::Voiced,
num_subframes: num as u8,
lag_coding: LagCoding::Relative { previous_lag: 50 },
ltp_scaling_present: false,
};
let ltp = LtpParameters::decode(&mut rd, cfg).unwrap();
let mut rd2 = RangeDecoder::new(&buf);
let delta = rd2.dec_icdf(LAG_DELTA_ICDF, 8) as i32;
let primary = if delta == 0 {
manual_absolute_lag(&mut rd2, &spec)
} else {
50 + (delta - 9)
};
let contour = rd2.dec_icdf(contour_icdf(bandwidth, num), 8) as u8;
assert_eq!(ltp.primary_lag(), primary);
assert_eq!(ltp.contour_index(), contour);
assert!(rd.tell() >= rd2.tell());
}
#[test]
fn ltp_scaling_present_decodes_one_of_three() {
let buf = [
0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0, 0xB0, 0xC0,
];
let mut rd = RangeDecoder::new(&buf);
let cfg = LtpConfig {
bandwidth: Bandwidth::Nb,
signal_type: SignalType::Voiced,
num_subframes: 4,
lag_coding: LagCoding::Absolute,
ltp_scaling_present: true,
};
let ltp = LtpParameters::decode(&mut rd, cfg).unwrap();
assert!(LTP_SCALING_VALUES_Q14.contains(<p.ltp_scaling_q14()));
}
#[test]
fn ltp_scaling_absent_uses_default_without_reading() {
let buf = [
0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0, 0xB0, 0xC0,
];
let base = LtpConfig {
bandwidth: Bandwidth::Nb,
signal_type: SignalType::Voiced,
num_subframes: 4,
lag_coding: LagCoding::Absolute,
ltp_scaling_present: false,
};
let mut rd_absent = RangeDecoder::new(&buf);
let ltp_absent = LtpParameters::decode(&mut rd_absent, base).unwrap();
assert_eq!(ltp_absent.ltp_scaling_q14(), LTP_SCALING_DEFAULT_Q14);
let tell_absent = rd_absent.tell();
let mut rd_present = RangeDecoder::new(&buf);
let _ = LtpParameters::decode(
&mut rd_present,
LtpConfig {
ltp_scaling_present: true,
..base
},
)
.unwrap();
assert!(
rd_present.tell() > tell_absent,
"present scaling must consume more bits than absent"
);
}
#[test]
fn voiced_sweep_never_panics_and_stays_in_range() {
let buffers: [&[u8]; 3] = [
&[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99],
&[0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88, 0x77, 0x66],
&[0x5A, 0xA5, 0x3C, 0xC3, 0x0F, 0xF0, 0x69, 0x96, 0x12, 0x48],
];
for buf in buffers {
for bandwidth in [Bandwidth::Nb, Bandwidth::Mb, Bandwidth::Wb] {
let spec = lag_low_spec(bandwidth).unwrap();
for &num in &[2u8, 4u8] {
for &present in &[false, true] {
for coding in [
LagCoding::Absolute,
LagCoding::Relative { previous_lag: 80 },
] {
let mut rd = RangeDecoder::new(buf);
let cfg = LtpConfig {
bandwidth,
signal_type: SignalType::Voiced,
num_subframes: num,
lag_coding: coding,
ltp_scaling_present: present,
};
let ltp = LtpParameters::decode(&mut rd, cfg).unwrap();
assert_eq!(ltp.pitch_lags().len(), num as usize);
for &lag in ltp.pitch_lags() {
assert!((spec.lag_min..=spec.lag_max).contains(&lag));
}
assert!(ltp.periodicity_index() <= 2);
assert_eq!(ltp.filter_taps_q7().len(), num as usize);
}
}
}
}
}
}
}