use crate::celt_band_layout::CeltFrameSize;
pub type TfAdjustment = i8;
pub const TF_ADJUSTMENT_MAX: TfAdjustment = 3;
pub const TF_ADJUSTMENT_ABS_MAX: u8 = 3;
pub const TF_ADJ_NONTRANSIENT_SELECT0: [[TfAdjustment; 2]; 4] = [
[0, -1],
[0, -1],
[0, -2],
[0, -2],
];
pub const TF_ADJ_NONTRANSIENT_SELECT1: [[TfAdjustment; 2]; 4] = [
[0, -1],
[0, -2],
[0, -3],
[0, -3],
];
pub const TF_ADJ_TRANSIENT_SELECT0: [[TfAdjustment; 2]; 4] = [
[0, -1],
[1, 0],
[2, 0],
[3, 0],
];
pub const TF_ADJ_TRANSIENT_SELECT1: [[TfAdjustment; 2]; 4] = [
[0, -1],
[1, -1],
[1, -1],
[1, -1],
];
#[inline]
pub fn celt_tf_adjustment(
frame_size: CeltFrameSize,
transient: bool,
tf_select: bool,
tf_change: bool,
) -> TfAdjustment {
let table = match (transient, tf_select) {
(false, false) => &TF_ADJ_NONTRANSIENT_SELECT0,
(false, true) => &TF_ADJ_NONTRANSIENT_SELECT1,
(true, false) => &TF_ADJ_TRANSIENT_SELECT0,
(true, true) => &TF_ADJ_TRANSIENT_SELECT1,
};
table[frame_size as usize][tf_change as usize]
}
#[inline]
pub fn celt_tf_select_can_affect(
frame_size: CeltFrameSize,
transient: bool,
tf_change: &[bool],
) -> bool {
let (a, b) = if transient {
(
&TF_ADJ_TRANSIENT_SELECT0[frame_size as usize],
&TF_ADJ_TRANSIENT_SELECT1[frame_size as usize],
)
} else {
(
&TF_ADJ_NONTRANSIENT_SELECT0[frame_size as usize],
&TF_ADJ_NONTRANSIENT_SELECT1[frame_size as usize],
)
};
for &c in tf_change {
let idx = c as usize;
if a[idx] != b[idx] {
return true;
}
}
false
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TfDirection {
Unchanged,
IncreaseTime(u8),
IncreaseFrequency(u8),
}
impl TfDirection {
#[inline]
pub const fn from_adjustment(adj: TfAdjustment) -> Self {
if adj == 0 {
Self::Unchanged
} else if adj < 0 {
Self::IncreaseTime((-adj) as u8)
} else {
Self::IncreaseFrequency(adj as u8)
}
}
#[inline]
pub const fn levels(self) -> u8 {
match self {
Self::Unchanged => 0,
Self::IncreaseTime(n) | Self::IncreaseFrequency(n) => n,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tables_have_documented_shape() {
for table in [
&TF_ADJ_NONTRANSIENT_SELECT0,
&TF_ADJ_NONTRANSIENT_SELECT1,
&TF_ADJ_TRANSIENT_SELECT0,
&TF_ADJ_TRANSIENT_SELECT1,
] {
assert_eq!(table.len(), 4);
for row in table.iter() {
assert_eq!(row.len(), 2);
}
}
}
#[test]
fn every_cell_in_documented_range() {
for table in [
&TF_ADJ_NONTRANSIENT_SELECT0,
&TF_ADJ_NONTRANSIENT_SELECT1,
&TF_ADJ_TRANSIENT_SELECT0,
&TF_ADJ_TRANSIENT_SELECT1,
] {
for row in table.iter() {
for &cell in row.iter() {
assert!((-3..=3).contains(&cell), "cell {cell} out of [-3, 3]");
}
}
}
}
#[test]
fn table_60_pinned_cells() {
assert_eq!(TF_ADJ_NONTRANSIENT_SELECT0[0], [0, -1]); assert_eq!(TF_ADJ_NONTRANSIENT_SELECT0[1], [0, -1]); assert_eq!(TF_ADJ_NONTRANSIENT_SELECT0[2], [0, -2]); assert_eq!(TF_ADJ_NONTRANSIENT_SELECT0[3], [0, -2]); }
#[test]
fn table_61_pinned_cells() {
assert_eq!(TF_ADJ_NONTRANSIENT_SELECT1[0], [0, -1]); assert_eq!(TF_ADJ_NONTRANSIENT_SELECT1[1], [0, -2]); assert_eq!(TF_ADJ_NONTRANSIENT_SELECT1[2], [0, -3]); assert_eq!(TF_ADJ_NONTRANSIENT_SELECT1[3], [0, -3]); }
#[test]
fn table_62_pinned_cells() {
assert_eq!(TF_ADJ_TRANSIENT_SELECT0[0], [0, -1]); assert_eq!(TF_ADJ_TRANSIENT_SELECT0[1], [1, 0]); assert_eq!(TF_ADJ_TRANSIENT_SELECT0[2], [2, 0]); assert_eq!(TF_ADJ_TRANSIENT_SELECT0[3], [3, 0]); }
#[test]
fn table_63_pinned_cells() {
assert_eq!(TF_ADJ_TRANSIENT_SELECT1[0], [0, -1]); assert_eq!(TF_ADJ_TRANSIENT_SELECT1[1], [1, -1]); assert_eq!(TF_ADJ_TRANSIENT_SELECT1[2], [1, -1]); assert_eq!(TF_ADJ_TRANSIENT_SELECT1[3], [1, -1]); }
#[test]
fn nontransient_choice_zero_is_always_zero() {
for table in [&TF_ADJ_NONTRANSIENT_SELECT0, &TF_ADJ_NONTRANSIENT_SELECT1] {
for (fs, row) in table.iter().enumerate() {
assert_eq!(row[0], 0, "nontransient fs={fs} choice=0 should be 0");
}
}
}
#[test]
fn nontransient_choice_one_is_nonpositive() {
for table in [&TF_ADJ_NONTRANSIENT_SELECT0, &TF_ADJ_NONTRANSIENT_SELECT1] {
for row in table.iter() {
assert!(row[1] <= 0);
}
}
}
#[test]
fn positive_adjustments_only_on_transient_frames() {
for table in [&TF_ADJ_NONTRANSIENT_SELECT0, &TF_ADJ_NONTRANSIENT_SELECT1] {
for row in table.iter() {
for &cell in row.iter() {
assert!(cell <= 0, "non-transient cell {cell} should be <= 0");
}
}
}
}
#[test]
fn table_62_choice0_scales_with_frame_size() {
assert_eq!(TF_ADJ_TRANSIENT_SELECT0[0][0], 0);
assert_eq!(TF_ADJ_TRANSIENT_SELECT0[1][0], 1);
assert_eq!(TF_ADJ_TRANSIENT_SELECT0[2][0], 2);
assert_eq!(TF_ADJ_TRANSIENT_SELECT0[3][0], 3);
}
#[test]
fn ms2_5_row_is_universal() {
assert_eq!(TF_ADJ_NONTRANSIENT_SELECT0[0], [0, -1]);
assert_eq!(TF_ADJ_NONTRANSIENT_SELECT1[0], [0, -1]);
assert_eq!(TF_ADJ_TRANSIENT_SELECT0[0], [0, -1]);
assert_eq!(TF_ADJ_TRANSIENT_SELECT1[0], [0, -1]);
}
#[test]
fn tf_adjustment_max_matches_tables() {
let mut observed_max: TfAdjustment = i8::MIN;
for table in [
&TF_ADJ_NONTRANSIENT_SELECT0,
&TF_ADJ_NONTRANSIENT_SELECT1,
&TF_ADJ_TRANSIENT_SELECT0,
&TF_ADJ_TRANSIENT_SELECT1,
] {
for row in table.iter() {
for &cell in row.iter() {
if cell > observed_max {
observed_max = cell;
}
}
}
}
assert_eq!(observed_max, TF_ADJUSTMENT_MAX);
}
#[test]
fn tf_adjustment_abs_max_matches_tables() {
let mut observed: u8 = 0;
for table in [
&TF_ADJ_NONTRANSIENT_SELECT0,
&TF_ADJ_NONTRANSIENT_SELECT1,
&TF_ADJ_TRANSIENT_SELECT0,
&TF_ADJ_TRANSIENT_SELECT1,
] {
for row in table.iter() {
for &cell in row.iter() {
let mag = cell.unsigned_abs();
if mag > observed {
observed = mag;
}
}
}
}
assert_eq!(observed, TF_ADJUSTMENT_ABS_MAX);
}
#[test]
fn entry_routes_nontransient_select0_to_table_60() {
for (fs_idx, fs) in [
CeltFrameSize::Ms2_5,
CeltFrameSize::Ms5,
CeltFrameSize::Ms10,
CeltFrameSize::Ms20,
]
.into_iter()
.enumerate()
{
for choice in [false, true] {
assert_eq!(
celt_tf_adjustment(fs, false, false, choice),
TF_ADJ_NONTRANSIENT_SELECT0[fs_idx][choice as usize]
);
}
}
}
#[test]
fn entry_routes_nontransient_select1_to_table_61() {
for (fs_idx, fs) in [
CeltFrameSize::Ms2_5,
CeltFrameSize::Ms5,
CeltFrameSize::Ms10,
CeltFrameSize::Ms20,
]
.into_iter()
.enumerate()
{
for choice in [false, true] {
assert_eq!(
celt_tf_adjustment(fs, false, true, choice),
TF_ADJ_NONTRANSIENT_SELECT1[fs_idx][choice as usize]
);
}
}
}
#[test]
fn entry_routes_transient_select0_to_table_62() {
for (fs_idx, fs) in [
CeltFrameSize::Ms2_5,
CeltFrameSize::Ms5,
CeltFrameSize::Ms10,
CeltFrameSize::Ms20,
]
.into_iter()
.enumerate()
{
for choice in [false, true] {
assert_eq!(
celt_tf_adjustment(fs, true, false, choice),
TF_ADJ_TRANSIENT_SELECT0[fs_idx][choice as usize]
);
}
}
}
#[test]
fn entry_routes_transient_select1_to_table_63() {
for (fs_idx, fs) in [
CeltFrameSize::Ms2_5,
CeltFrameSize::Ms5,
CeltFrameSize::Ms10,
CeltFrameSize::Ms20,
]
.into_iter()
.enumerate()
{
for choice in [false, true] {
assert_eq!(
celt_tf_adjustment(fs, true, true, choice),
TF_ADJ_TRANSIENT_SELECT1[fs_idx][choice as usize]
);
}
}
}
#[test]
fn tf_select_cannot_affect_empty_band_set() {
for fs in [
CeltFrameSize::Ms2_5,
CeltFrameSize::Ms5,
CeltFrameSize::Ms10,
CeltFrameSize::Ms20,
] {
for transient in [false, true] {
assert!(!celt_tf_select_can_affect(fs, transient, &[]));
}
}
}
#[test]
fn ms2_5_nontransient_tf_select_never_matters() {
for choices in [
vec![],
vec![false],
vec![true],
vec![false, true],
vec![true, true, false, false],
] {
assert!(!celt_tf_select_can_affect(
CeltFrameSize::Ms2_5,
false,
&choices
));
}
}
#[test]
fn ms2_5_transient_tf_select_never_matters() {
for choices in [
vec![],
vec![false],
vec![true],
vec![false, true],
vec![true, true, false, false],
] {
assert!(!celt_tf_select_can_affect(
CeltFrameSize::Ms2_5,
true,
&choices
));
}
}
#[test]
fn ms10_nontransient_tf_select_silent_when_all_choice0() {
assert!(!celt_tf_select_can_affect(
CeltFrameSize::Ms10,
false,
&[false, false, false, false],
));
assert!(celt_tf_select_can_affect(
CeltFrameSize::Ms10,
false,
&[false, false, true, false],
));
}
#[test]
fn ms5_transient_tf_select_matters_when_any_choice1() {
assert!(!celt_tf_select_can_affect(
CeltFrameSize::Ms5,
true,
&[false, false]
));
assert!(celt_tf_select_can_affect(
CeltFrameSize::Ms5,
true,
&[false, true]
));
}
#[test]
fn ms20_transient_tf_select_matters_for_any_nonempty_band_set() {
assert!(celt_tf_select_can_affect(
CeltFrameSize::Ms20,
true,
&[false]
));
assert!(celt_tf_select_can_affect(
CeltFrameSize::Ms20,
true,
&[true]
));
assert!(celt_tf_select_can_affect(
CeltFrameSize::Ms20,
true,
&[true, false]
));
}
#[test]
fn tf_direction_classifies_every_documented_cell() {
for table in [
&TF_ADJ_NONTRANSIENT_SELECT0,
&TF_ADJ_NONTRANSIENT_SELECT1,
&TF_ADJ_TRANSIENT_SELECT0,
&TF_ADJ_TRANSIENT_SELECT1,
] {
for row in table.iter() {
for &cell in row.iter() {
let dir = TfDirection::from_adjustment(cell);
match (cell, dir) {
(0, TfDirection::Unchanged) => {}
(n, TfDirection::IncreaseTime(levels)) if n < 0 => {
assert_eq!(levels as i16, -(n as i16));
}
(n, TfDirection::IncreaseFrequency(levels)) if n > 0 => {
assert_eq!(levels as i16, n as i16);
}
(cell, dir) => {
panic!("cell {cell} classified as unexpected {dir:?}");
}
}
}
}
}
}
#[test]
fn tf_direction_levels_equals_abs_value() {
for adj in -3i8..=3i8 {
let dir = TfDirection::from_adjustment(adj);
assert_eq!(dir.levels(), adj.unsigned_abs());
}
}
#[test]
fn increase_frequency_only_reachable_on_transient_frames() {
for table in [&TF_ADJ_NONTRANSIENT_SELECT0, &TF_ADJ_NONTRANSIENT_SELECT1] {
for row in table.iter() {
for &cell in row.iter() {
assert!(
!matches!(
TfDirection::from_adjustment(cell),
TfDirection::IncreaseFrequency(_)
),
"non-transient cell {cell} should not request frequency upgrade"
);
}
}
}
}
#[test]
fn nontransient_choice1_always_increases_time() {
for table in [&TF_ADJ_NONTRANSIENT_SELECT0, &TF_ADJ_NONTRANSIENT_SELECT1] {
for row in table.iter() {
let dir = TfDirection::from_adjustment(row[1]);
assert!(matches!(dir, TfDirection::IncreaseTime(_)));
}
}
}
}