use crate::celt_band_layout::CELT_NUM_BANDS;
pub const CACHE_LM_COUNT: usize = 5;
pub const CACHE_INDEX_LEN: usize = CELT_NUM_BANDS * CACHE_LM_COUNT;
pub const CACHE_BITS_LEN: usize = 392;
pub const CACHE_INDEX_SENTINEL: i16 = -1;
pub const CACHE_MAX_PULSES: u8 = 40;
pub static CACHE_INDEX50: [i16; CACHE_INDEX_LEN] = [
-1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 41, 41, 41, 82, 82, 123, 164, 200, 222, 0, 0, 0, 0,
0, 0, 0, 0, 41, 41, 41, 41, 123, 123, 123, 164, 164, 240, 266, 283, 295, 41, 41, 41, 41, 41,
41, 41, 41, 123, 123, 123, 123, 240, 240, 240, 266, 266, 305, 318, 328, 336, 123, 123, 123,
123, 123, 123, 123, 123, 240, 240, 240, 240, 305, 305, 305, 318, 318, 343, 351, 358, 364, 240,
240, 240, 240, 240, 240, 240, 240, 305, 305, 305, 305, 343, 343, 343, 351, 351, 370, 376, 382,
387,
];
pub static CACHE_BITS50: [u8; CACHE_BITS_LEN] = [
40, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 40, 15, 23, 28, 31, 34, 36, 38, 39, 41, 42, 43, 44, 45, 46, 47,
47, 49, 50, 51, 52, 53, 54, 55, 55, 57, 58, 59, 60, 61, 62, 63, 63, 65, 66, 67, 68, 69, 70, 71,
71, 40, 20, 33, 41, 48, 53, 57, 61, 64, 66, 69, 71, 73, 75, 76, 78, 80, 82, 85, 87, 89, 91, 92,
94, 96, 98, 101, 103, 105, 107, 108, 110, 112, 114, 117, 119, 121, 123, 124, 126, 128, 40, 23,
39, 51, 60, 67, 73, 79, 83, 87, 91, 94, 97, 100, 102, 105, 107, 111, 115, 118, 121, 124, 126,
129, 131, 135, 139, 142, 145, 148, 150, 153, 155, 159, 163, 166, 169, 172, 174, 177, 179, 35,
28, 49, 65, 78, 89, 99, 107, 114, 120, 126, 132, 136, 141, 145, 149, 153, 159, 165, 171, 176,
180, 185, 189, 192, 199, 205, 211, 216, 220, 225, 229, 232, 239, 245, 251, 21, 33, 58, 79, 97,
112, 125, 137, 148, 157, 166, 174, 182, 189, 195, 201, 207, 217, 227, 235, 243, 251, 17, 35,
63, 86, 106, 123, 139, 152, 165, 177, 187, 197, 206, 214, 222, 230, 237, 250, 25, 31, 55, 75,
91, 105, 117, 128, 138, 146, 154, 161, 168, 174, 180, 185, 190, 200, 208, 215, 222, 229, 235,
240, 245, 255, 16, 36, 65, 89, 110, 128, 144, 159, 173, 185, 196, 207, 217, 226, 234, 242, 250,
11, 41, 74, 103, 128, 151, 172, 191, 209, 225, 241, 255, 9, 43, 79, 110, 138, 163, 186, 207,
227, 246, 12, 39, 71, 99, 123, 144, 164, 182, 198, 214, 228, 241, 253, 9, 44, 81, 113, 142,
168, 192, 214, 235, 255, 7, 49, 90, 127, 160, 191, 220, 247, 6, 51, 95, 134, 170, 203, 234, 7,
47, 87, 123, 155, 184, 212, 237, 6, 52, 97, 137, 174, 208, 240, 5, 57, 106, 151, 192, 231, 5,
59, 111, 158, 202, 243, 5, 55, 103, 147, 187, 224, 5, 60, 113, 161, 206, 248, 4, 65, 122, 175,
224, 4, 67, 127, 182, 234,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PulseCacheError {
BandOutOfRange { band: usize },
LmOutOfRange { lm: usize },
SentinelTuple { band: usize, lm: usize },
PulseCountOutOfRange { k: u8, max_k: u8 },
}
pub const fn cache_flat_index(band: usize, lm: usize) -> Result<usize, PulseCacheError> {
if band >= CELT_NUM_BANDS {
return Err(PulseCacheError::BandOutOfRange { band });
}
if lm >= CACHE_LM_COUNT {
return Err(PulseCacheError::LmOutOfRange { lm });
}
Ok(band * CACHE_LM_COUNT + lm)
}
pub const fn cache_run_offset(band: usize, lm: usize) -> Result<usize, PulseCacheError> {
let i = match cache_flat_index(band, lm) {
Ok(i) => i,
Err(e) => return Err(e),
};
let off = CACHE_INDEX50[i];
if off == CACHE_INDEX_SENTINEL {
return Err(PulseCacheError::SentinelTuple { band, lm });
}
Ok(off as usize)
}
pub const fn cache_max_pulses(band: usize, lm: usize) -> Result<u8, PulseCacheError> {
let off = match cache_run_offset(band, lm) {
Ok(off) => off,
Err(e) => return Err(e),
};
Ok(CACHE_BITS50[off])
}
pub const fn cache_pulse_cost(band: usize, lm: usize, k: u8) -> Result<u8, PulseCacheError> {
let off = match cache_run_offset(band, lm) {
Ok(off) => off,
Err(e) => return Err(e),
};
let max_k = CACHE_BITS50[off];
if k == 0 || k > max_k {
return Err(PulseCacheError::PulseCountOutOfRange { k, max_k });
}
Ok(CACHE_BITS50[off + k as usize])
}
pub const fn bits_to_pulses(band: usize, lm: usize, b_target: u8) -> Result<u8, PulseCacheError> {
let off = match cache_run_offset(band, lm) {
Ok(off) => off,
Err(e) => return Err(e),
};
let max_k = CACHE_BITS50[off];
let mut k: u8 = 0;
let mut probe: u8 = 1;
while probe <= max_k {
if CACHE_BITS50[off + probe as usize] > b_target {
break;
}
k = probe;
probe += 1;
}
Ok(k)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn index_table_length_matches_band_lm_grid() {
assert_eq!(CACHE_INDEX50.len(), CACHE_INDEX_LEN);
assert_eq!(CACHE_INDEX_LEN, CELT_NUM_BANDS * CACHE_LM_COUNT);
assert_eq!(CACHE_INDEX_LEN, 105);
}
#[test]
fn bits_table_length_is_392() {
assert_eq!(CACHE_BITS50.len(), CACHE_BITS_LEN);
assert_eq!(CACHE_BITS_LEN, 392);
}
#[test]
fn exactly_eight_sentinels_in_bands_zero_and_one() {
let sentinels: Vec<usize> = (0..CACHE_INDEX_LEN)
.filter(|&i| CACHE_INDEX50[i] == CACHE_INDEX_SENTINEL)
.collect();
assert_eq!(sentinels.len(), 8);
assert_eq!(sentinels, vec![0, 1, 2, 3, 4, 5, 6, 7]);
}
#[test]
fn sentinel_lookup_returns_sentinel_error() {
assert_eq!(
cache_run_offset(0, 0),
Err(PulseCacheError::SentinelTuple { band: 0, lm: 0 })
);
assert_eq!(
cache_run_offset(1, 2),
Err(PulseCacheError::SentinelTuple { band: 1, lm: 2 })
);
assert_eq!(cache_run_offset(1, 3), Ok(0));
}
#[test]
fn twenty_three_distinct_run_offsets() {
let mut offsets: Vec<i16> = CACHE_INDEX50
.iter()
.copied()
.filter(|&o| o != CACHE_INDEX_SENTINEL)
.collect();
offsets.sort_unstable();
offsets.dedup();
assert_eq!(offsets.len(), 23);
assert_eq!(
offsets,
vec![
0, 41, 82, 123, 164, 200, 222, 240, 266, 283, 295, 305, 318, 328, 336, 343, 351,
358, 364, 370, 376, 382, 387,
]
);
}
#[test]
fn runs_pack_every_byte_exactly() {
let mut offsets: Vec<usize> = CACHE_INDEX50
.iter()
.copied()
.filter(|&o| o != CACHE_INDEX_SENTINEL)
.map(|o| o as usize)
.collect();
offsets.sort_unstable();
offsets.dedup();
let mut total = 0usize;
for &off in &offsets {
let max_k = CACHE_BITS50[off] as usize;
total += 1 + max_k;
}
assert_eq!(total, CACHE_BITS_LEN);
}
#[test]
fn each_run_cost_curve_is_monotone_nondecreasing() {
let mut offsets: Vec<usize> = CACHE_INDEX50
.iter()
.copied()
.filter(|&o| o != CACHE_INDEX_SENTINEL)
.map(|o| o as usize)
.collect();
offsets.sort_unstable();
offsets.dedup();
for &off in &offsets {
let max_k = CACHE_BITS50[off] as usize;
for k in 2..=max_k {
assert!(
CACHE_BITS50[off + k] >= CACHE_BITS50[off + k - 1],
"run at {off} not monotone at k={k}"
);
}
}
}
#[test]
fn max_pulses_caps_at_forty() {
let mut offsets: Vec<usize> = CACHE_INDEX50
.iter()
.copied()
.filter(|&o| o != CACHE_INDEX_SENTINEL)
.map(|o| o as usize)
.collect();
offsets.sort_unstable();
offsets.dedup();
for &off in &offsets {
assert!(CACHE_BITS50[off] <= CACHE_MAX_PULSES);
}
}
#[test]
fn first_run_is_flat_seven() {
assert_eq!(cache_max_pulses(1, 3), Ok(40));
for k in 1..=40u8 {
assert_eq!(cache_pulse_cost(1, 3, k), Ok(7));
}
}
#[test]
fn pulse_cost_rejects_zero_and_overflow_k() {
assert_eq!(
cache_pulse_cost(1, 3, 0),
Err(PulseCacheError::PulseCountOutOfRange { k: 0, max_k: 40 })
);
assert_eq!(
cache_pulse_cost(1, 3, 41),
Err(PulseCacheError::PulseCountOutOfRange { k: 41, max_k: 40 })
);
}
#[test]
fn bits_to_pulses_flat_run_fits_all_at_budget_seven() {
assert_eq!(bits_to_pulses(1, 3, 7), Ok(40));
assert_eq!(bits_to_pulses(1, 3, 6), Ok(0));
}
#[test]
fn bits_to_pulses_picks_exact_threshold() {
assert_eq!(cache_pulse_cost(2, 2, 1), Ok(15));
assert_eq!(cache_pulse_cost(2, 2, 2), Ok(23));
assert_eq!(cache_pulse_cost(2, 2, 3), Ok(28));
assert_eq!(bits_to_pulses(2, 2, 23), Ok(2));
assert_eq!(bits_to_pulses(2, 2, 22), Ok(1));
assert_eq!(bits_to_pulses(2, 2, 14), Ok(0));
}
#[test]
fn bits_to_pulses_saturating_budget_returns_max_k() {
let max_k = cache_max_pulses(2, 2).unwrap();
assert_eq!(bits_to_pulses(2, 2, 255), Ok(max_k));
}
#[test]
fn bits_to_pulses_on_sentinel_signals_closed_form() {
assert_eq!(
bits_to_pulses(0, 0, 100),
Err(PulseCacheError::SentinelTuple { band: 0, lm: 0 })
);
}
#[test]
fn lookup_rejects_out_of_range_band_and_lm() {
assert_eq!(
cache_flat_index(CELT_NUM_BANDS, 0),
Err(PulseCacheError::BandOutOfRange {
band: CELT_NUM_BANDS
})
);
assert_eq!(
cache_flat_index(0, CACHE_LM_COUNT),
Err(PulseCacheError::LmOutOfRange { lm: CACHE_LM_COUNT })
);
}
#[test]
fn bits_to_pulses_monotone_in_budget() {
let mut prev = 0u8;
for b in 0..=255u8 {
let k = bits_to_pulses(5, 4, b).unwrap();
assert!(k >= prev, "K decreased at budget {b}");
prev = k;
}
assert_eq!(prev, cache_max_pulses(5, 4).unwrap());
}
#[test]
fn last_run_at_offset_387_has_max_k_four() {
assert_eq!(cache_run_offset(20, 4), Ok(387));
assert_eq!(cache_max_pulses(20, 4), Ok(4));
assert_eq!(cache_pulse_cost(20, 4, 1), Ok(67));
assert_eq!(cache_pulse_cost(20, 4, 4), Ok(234));
}
}