use crate::celt_band_layout::CELT_NUM_BANDS;
pub const CACHE_CAPS50_LM_COUNT: usize = 4;
pub const CACHE_CAPS50_STEREO_COUNT: usize = 2;
pub const CACHE_CAPS50_STEREO_MONO: usize = 0;
pub const CACHE_CAPS50_STEREO_STEREO: usize = 1;
pub const CACHE_CAPS50_TOTAL_BYTES: usize =
CACHE_CAPS50_LM_COUNT * CACHE_CAPS50_STEREO_COUNT * CELT_NUM_BANDS;
pub const INIT_CAPS_BIAS: u32 = 64;
pub const INIT_CAPS_DIVISOR: u32 = 4;
pub const INIT_CAPS_MAX_CHANNELS: u32 = 2;
#[rustfmt::skip]
pub const CACHE_CAPS50: [u8; CACHE_CAPS50_TOTAL_BYTES] = [
224, 224, 224, 224, 224, 224, 224, 224, 160, 160, 160, 160, 185, 185, 185, 178, 178, 168, 134, 61, 37,
224, 224, 224, 224, 224, 224, 224, 224, 240, 240, 240, 240, 207, 207, 207, 198, 198, 183, 144, 66, 40,
160, 160, 160, 160, 160, 160, 160, 160, 185, 185, 185, 185, 193, 193, 193, 183, 183, 172, 138, 64, 38,
240, 240, 240, 240, 240, 240, 240, 240, 207, 207, 207, 207, 204, 204, 204, 193, 193, 180, 143, 66, 40,
185, 185, 185, 185, 185, 185, 185, 185, 193, 193, 193, 193, 193, 193, 193, 183, 183, 172, 138, 65, 39,
207, 207, 207, 207, 207, 207, 207, 207, 204, 204, 204, 204, 201, 201, 201, 188, 188, 176, 141, 66, 40,
193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 194, 194, 194, 184, 184, 173, 139, 65, 39,
204, 204, 204, 204, 204, 204, 204, 204, 201, 201, 201, 201, 198, 198, 198, 187, 187, 175, 140, 66, 40,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CacheCapsStereo {
Mono,
Stereo,
}
impl CacheCapsStereo {
pub const fn axis_index(self) -> usize {
match self {
CacheCapsStereo::Mono => CACHE_CAPS50_STEREO_MONO,
CacheCapsStereo::Stereo => CACHE_CAPS50_STEREO_STEREO,
}
}
pub const fn channels(self) -> u32 {
match self {
CacheCapsStereo::Mono => 1,
CacheCapsStereo::Stereo => 2,
}
}
pub const fn from_is_stereo(is_stereo: bool) -> Self {
if is_stereo {
CacheCapsStereo::Stereo
} else {
CacheCapsStereo::Mono
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CacheCaps50Error {
LmOutOfRange { lm: u32 },
BandOutOfRange { band: u32 },
ChannelsOutOfRange { channels: u32 },
}
pub const fn cache_caps_offset(lm: usize, stereo: CacheCapsStereo, band: usize) -> usize {
CELT_NUM_BANDS * (2 * lm + stereo.axis_index()) + band
}
pub fn cache_caps_value(
lm: u32,
stereo: CacheCapsStereo,
band: u32,
) -> Result<u8, CacheCaps50Error> {
if lm >= CACHE_CAPS50_LM_COUNT as u32 {
return Err(CacheCaps50Error::LmOutOfRange { lm });
}
if band >= CELT_NUM_BANDS as u32 {
return Err(CacheCaps50Error::BandOutOfRange { band });
}
let off = cache_caps_offset(lm as usize, stereo, band as usize);
Ok(CACHE_CAPS50[off])
}
pub fn cache_caps_row(lm: u32, stereo: CacheCapsStereo) -> Result<&'static [u8], CacheCaps50Error> {
if lm >= CACHE_CAPS50_LM_COUNT as u32 {
return Err(CacheCaps50Error::LmOutOfRange { lm });
}
let base = cache_caps_offset(lm as usize, stereo, 0);
Ok(&CACHE_CAPS50[base..base + CELT_NUM_BANDS])
}
pub const fn init_caps(caps_value: u8, channels: u32, n_bins: u32) -> u32 {
((caps_value as u32 + INIT_CAPS_BIAS) * channels * n_bins) / INIT_CAPS_DIVISOR
}
pub fn cap_for_band_bits(
lm: u32,
stereo: CacheCapsStereo,
band: u32,
channels: u32,
n_bins: u32,
) -> Result<u32, CacheCaps50Error> {
if channels == 0 || channels > INIT_CAPS_MAX_CHANNELS {
return Err(CacheCaps50Error::ChannelsOutOfRange { channels });
}
let caps_value = cache_caps_value(lm, stereo, band)?;
Ok(init_caps(caps_value, channels, n_bins))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::celt_band_layout::{celt_band_bins_per_channel, CeltFrameSize};
#[test]
fn table_shape_constants_match_struct() {
assert_eq!(CACHE_CAPS50_LM_COUNT, 4);
assert_eq!(CACHE_CAPS50_STEREO_COUNT, 2);
assert_eq!(CACHE_CAPS50_TOTAL_BYTES, 168);
assert_eq!(CACHE_CAPS50.len(), CACHE_CAPS50_TOTAL_BYTES);
assert_eq!(CELT_NUM_BANDS, 21);
}
#[test]
fn init_caps_constants_match_rfc() {
assert_eq!(INIT_CAPS_BIAS, 64);
assert_eq!(INIT_CAPS_DIVISOR, 4);
assert_eq!(INIT_CAPS_MAX_CHANNELS, 2);
}
#[test]
fn stereo_axis_index_constants_match_rfc_convention() {
assert_eq!(CACHE_CAPS50_STEREO_MONO, 0);
assert_eq!(CACHE_CAPS50_STEREO_STEREO, 1);
assert_eq!(CacheCapsStereo::Mono.axis_index(), 0);
assert_eq!(CacheCapsStereo::Stereo.axis_index(), 1);
}
#[test]
fn cache_caps_stereo_channels_helper_matches_init_caps_input() {
assert_eq!(CacheCapsStereo::Mono.channels(), 1);
assert_eq!(CacheCapsStereo::Stereo.channels(), 2);
}
#[test]
fn cache_caps_stereo_from_is_stereo_boolean_round_trip() {
assert_eq!(
CacheCapsStereo::from_is_stereo(false),
CacheCapsStereo::Mono
);
assert_eq!(
CacheCapsStereo::from_is_stereo(true),
CacheCapsStereo::Stereo
);
}
#[test]
fn csv_row0_band0_is_224() {
assert_eq!(cache_caps_value(0, CacheCapsStereo::Mono, 0).unwrap(), 224);
}
#[test]
fn csv_row1_band20_is_40() {
assert_eq!(
cache_caps_value(0, CacheCapsStereo::Stereo, 20).unwrap(),
40
);
}
#[test]
fn csv_row2_band0_is_160() {
assert_eq!(cache_caps_value(1, CacheCapsStereo::Mono, 0).unwrap(), 160);
}
#[test]
fn csv_row3_band8_is_207() {
assert_eq!(
cache_caps_value(1, CacheCapsStereo::Stereo, 8).unwrap(),
207
);
}
#[test]
fn csv_row4_band12_is_193() {
assert_eq!(cache_caps_value(2, CacheCapsStereo::Mono, 12).unwrap(), 193);
}
#[test]
fn csv_row5_band17_is_176() {
assert_eq!(
cache_caps_value(2, CacheCapsStereo::Stereo, 17).unwrap(),
176
);
}
#[test]
fn csv_row6_band20_is_39() {
assert_eq!(cache_caps_value(3, CacheCapsStereo::Mono, 20).unwrap(), 39);
}
#[test]
fn csv_row7_band0_is_204() {
assert_eq!(
cache_caps_value(3, CacheCapsStereo::Stereo, 0).unwrap(),
204
);
}
#[test]
fn cache_caps_offset_matches_rfc_row_stride_rule() {
for lm in 0..CACHE_CAPS50_LM_COUNT {
for stereo_idx in 0..CACHE_CAPS50_STEREO_COUNT {
for band in 0..CELT_NUM_BANDS {
let stereo = if stereo_idx == 0 {
CacheCapsStereo::Mono
} else {
CacheCapsStereo::Stereo
};
let off = cache_caps_offset(lm, stereo, band);
let expected = CELT_NUM_BANDS * (2 * lm + stereo_idx) + band;
assert_eq!(off, expected);
}
}
}
}
#[test]
fn cache_caps_offset_at_table_extremes_pins_endpoints() {
assert_eq!(cache_caps_offset(0, CacheCapsStereo::Mono, 0), 0);
assert_eq!(
cache_caps_offset(3, CacheCapsStereo::Stereo, 20),
CACHE_CAPS50_TOTAL_BYTES - 1
);
assert_eq!(CACHE_CAPS50[CACHE_CAPS50_TOTAL_BYTES - 1], 40);
}
#[test]
fn cache_caps_value_is_total_over_in_range_inputs() {
for lm in 0..CACHE_CAPS50_LM_COUNT as u32 {
for stereo in [CacheCapsStereo::Mono, CacheCapsStereo::Stereo] {
for band in 0..CELT_NUM_BANDS as u32 {
let v = cache_caps_value(lm, stereo, band).expect("in-range lookup");
let off = cache_caps_offset(lm as usize, stereo, band as usize);
assert_eq!(v, CACHE_CAPS50[off]);
}
}
}
}
#[test]
fn cache_caps_row_matches_cell_lookup_for_every_lm_and_stereo() {
for lm in 0..CACHE_CAPS50_LM_COUNT as u32 {
for stereo in [CacheCapsStereo::Mono, CacheCapsStereo::Stereo] {
let row = cache_caps_row(lm, stereo).unwrap();
assert_eq!(row.len(), CELT_NUM_BANDS);
for band in 0..CELT_NUM_BANDS as u32 {
let v = cache_caps_value(lm, stereo, band).unwrap();
assert_eq!(v, row[band as usize]);
}
}
}
}
#[test]
fn cache_caps_value_rejects_lm_out_of_range() {
let err =
cache_caps_value(CACHE_CAPS50_LM_COUNT as u32, CacheCapsStereo::Mono, 0).unwrap_err();
assert_eq!(err, CacheCaps50Error::LmOutOfRange { lm: 4 });
let err = cache_caps_value(u32::MAX, CacheCapsStereo::Mono, 0).unwrap_err();
assert_eq!(err, CacheCaps50Error::LmOutOfRange { lm: u32::MAX });
}
#[test]
fn cache_caps_value_rejects_band_out_of_range() {
let err = cache_caps_value(0, CacheCapsStereo::Mono, CELT_NUM_BANDS as u32).unwrap_err();
assert_eq!(err, CacheCaps50Error::BandOutOfRange { band: 21 });
let err = cache_caps_value(0, CacheCapsStereo::Stereo, u32::MAX).unwrap_err();
assert_eq!(err, CacheCaps50Error::BandOutOfRange { band: u32::MAX });
}
#[test]
fn cache_caps_row_rejects_lm_out_of_range() {
let err = cache_caps_row(CACHE_CAPS50_LM_COUNT as u32, CacheCapsStereo::Mono).unwrap_err();
assert_eq!(err, CacheCaps50Error::LmOutOfRange { lm: 4 });
}
#[test]
fn init_caps_matches_rfc_formula_explicit_case() {
assert_eq!(init_caps(224, 2, 4), 576);
assert_eq!(init_caps(40, 1, 12), 312);
assert_eq!(init_caps(0, 1, 1), 16);
assert_eq!(init_caps(255, 2, 192), 30624);
}
#[test]
fn init_caps_integer_division_is_floor() {
assert_eq!(init_caps(1, 1, 1), 16);
assert_eq!(init_caps(2, 1, 1), 16);
assert_eq!(init_caps(3, 1, 1), 16);
assert_eq!(init_caps(4, 1, 1), 17);
}
#[test]
fn cap_for_band_bits_matches_manual_lookup_plus_init_caps() {
let caps_value = cache_caps_value(2, CacheCapsStereo::Stereo, 17).unwrap();
assert_eq!(caps_value, 176);
let n_bins = celt_band_bins_per_channel(17, CeltFrameSize::Ms10).unwrap();
let expected = init_caps(caps_value, 2, n_bins as u32);
let cap = cap_for_band_bits(2, CacheCapsStereo::Stereo, 17, 2, n_bins as u32).unwrap();
assert_eq!(cap, expected);
}
#[test]
fn cap_for_band_bits_rejects_channels_out_of_range() {
let err = cap_for_band_bits(0, CacheCapsStereo::Mono, 0, 0, 4).unwrap_err();
assert_eq!(err, CacheCaps50Error::ChannelsOutOfRange { channels: 0 });
let err = cap_for_band_bits(0, CacheCapsStereo::Mono, 0, 3, 4).unwrap_err();
assert_eq!(err, CacheCaps50Error::ChannelsOutOfRange { channels: 3 });
let err = cap_for_band_bits(0, CacheCapsStereo::Mono, 0, u32::MAX, 4).unwrap_err();
assert_eq!(
err,
CacheCaps50Error::ChannelsOutOfRange { channels: u32::MAX }
);
}
#[test]
fn cap_for_band_bits_propagates_lm_and_band_errors() {
let err = cap_for_band_bits(CACHE_CAPS50_LM_COUNT as u32, CacheCapsStereo::Mono, 0, 1, 4)
.unwrap_err();
assert_eq!(err, CacheCaps50Error::LmOutOfRange { lm: 4 });
let err =
cap_for_band_bits(0, CacheCapsStereo::Mono, CELT_NUM_BANDS as u32, 1, 4).unwrap_err();
assert_eq!(err, CacheCaps50Error::BandOutOfRange { band: 21 });
}
#[test]
fn cap_at_20ms_stereo_fits_in_i16_but_not_i8() {
for band in 0..CELT_NUM_BANDS as u32 {
let n_bins = celt_band_bins_per_channel(band as usize, CeltFrameSize::Ms20).unwrap();
let cap =
cap_for_band_bits(3, CacheCapsStereo::Stereo, band, 2, n_bins as u32).unwrap();
assert!(
cap <= i16::MAX as u32,
"cap[{band}] = {cap} should fit in i16"
);
}
}
#[test]
fn at_least_one_cap_exceeds_i8() {
let n_bins = celt_band_bins_per_channel(0, CeltFrameSize::Ms20).unwrap();
let cap = cap_for_band_bits(3, CacheCapsStereo::Stereo, 0, 2, n_bins as u32).unwrap();
assert!(
cap > i8::MAX as u32,
"expected at least one cap > i8::MAX (= 127); got {cap}"
);
}
#[test]
fn celt_only_20ms_stereo_band0_pins_expected_cap() {
let caps_value = cache_caps_value(3, CacheCapsStereo::Stereo, 0).unwrap();
assert_eq!(caps_value, 204);
let n_bins = celt_band_bins_per_channel(0, CeltFrameSize::Ms20).unwrap();
let cap = cap_for_band_bits(3, CacheCapsStereo::Stereo, 0, 2, n_bins as u32).unwrap();
assert_eq!(cap, 134 * n_bins as u32);
}
#[test]
fn hybrid_band17_at_20ms_mono_pins_expected_cap() {
let caps_value = cache_caps_value(3, CacheCapsStereo::Mono, 17).unwrap();
assert_eq!(caps_value, 173);
let n_bins = celt_band_bins_per_channel(17, CeltFrameSize::Ms20).unwrap();
let cap = cap_for_band_bits(3, CacheCapsStereo::Mono, 17, 1, n_bins as u32).unwrap();
assert_eq!(cap, (237 * n_bins as u32) / 4);
}
}