pub const CELT_NUM_BANDS: usize = 21;
pub const HYBRID_FIRST_CODED_BAND: usize = 17;
pub const CELT_MAX_BINS_PER_BAND: u16 = 176;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum CeltFrameSize {
Ms2_5 = 0,
Ms5 = 1,
Ms10 = 2,
Ms20 = 3,
}
impl CeltFrameSize {
#[inline]
pub const fn column_index(self) -> usize {
self as usize
}
pub const fn from_frame_tenths_ms(tenths: u32) -> Option<Self> {
match tenths {
25 => Some(CeltFrameSize::Ms2_5),
50 => Some(CeltFrameSize::Ms5),
100 => Some(CeltFrameSize::Ms10),
200 => Some(CeltFrameSize::Ms20),
_ => None,
}
}
pub const fn to_frame_tenths_ms(self) -> u32 {
match self {
CeltFrameSize::Ms2_5 => 25,
CeltFrameSize::Ms5 => 50,
CeltFrameSize::Ms10 => 100,
CeltFrameSize::Ms20 => 200,
}
}
}
const TABLE_55_BINS_PER_BAND: [[u16; 4]; CELT_NUM_BANDS] = [
[1, 2, 4, 8],
[1, 2, 4, 8],
[1, 2, 4, 8],
[1, 2, 4, 8],
[1, 2, 4, 8],
[1, 2, 4, 8],
[1, 2, 4, 8],
[1, 2, 4, 8],
[2, 4, 8, 16],
[2, 4, 8, 16],
[2, 4, 8, 16],
[2, 4, 8, 16],
[4, 8, 16, 32],
[4, 8, 16, 32],
[4, 8, 16, 32],
[6, 12, 24, 48],
[6, 12, 24, 48],
[8, 16, 32, 64],
[12, 24, 48, 96],
[18, 36, 72, 144],
[22, 44, 88, 176],
];
const TABLE_55_BAND_EDGES_HZ: [u16; CELT_NUM_BANDS + 1] = [
0, 200, 400, 600, 800, 1000, 1200, 1400, 1600, 2000, 2400, 2800, 3200, 4000, 4800, 5600, 6800,
8000, 9600, 12000, 15600, 20000,
];
pub const fn celt_band_bins_per_channel(band: usize, frame_size: CeltFrameSize) -> Option<u16> {
if band >= CELT_NUM_BANDS {
return None;
}
Some(TABLE_55_BINS_PER_BAND[band][frame_size.column_index()])
}
pub const fn celt_band_start_hz(band: usize) -> Option<u16> {
if band >= CELT_NUM_BANDS {
return None;
}
Some(TABLE_55_BAND_EDGES_HZ[band])
}
pub const fn celt_band_stop_hz(band: usize) -> Option<u16> {
if band >= CELT_NUM_BANDS {
return None;
}
Some(TABLE_55_BAND_EDGES_HZ[band + 1])
}
pub const fn celt_first_coded_band(is_hybrid: bool) -> usize {
if is_hybrid {
HYBRID_FIRST_CODED_BAND
} else {
0
}
}
pub const fn celt_end_coded_band() -> usize {
CELT_NUM_BANDS
}
pub const fn celt_total_bins_per_channel(frame_size: CeltFrameSize, is_hybrid: bool) -> u32 {
let mut sum: u32 = 0;
let mut b = celt_first_coded_band(is_hybrid);
let col = frame_size.column_index();
while b < CELT_NUM_BANDS {
sum += TABLE_55_BINS_PER_BAND[b][col] as u32;
b += 1;
}
sum
}
pub fn celt_band_at_hz(hz: u32) -> Option<usize> {
if hz >= TABLE_55_BAND_EDGES_HZ[CELT_NUM_BANDS] as u32 {
return None;
}
let mut b: usize = 0;
while b < CELT_NUM_BANDS {
let stop = TABLE_55_BAND_EDGES_HZ[b + 1] as u32;
if hz < stop {
return Some(b);
}
b += 1;
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn band_edges_span_full_audible_range() {
assert_eq!(celt_band_start_hz(0), Some(0));
assert_eq!(celt_band_stop_hz(CELT_NUM_BANDS - 1), Some(20000));
}
#[test]
fn adjacent_bands_tile_without_gaps() {
for b in 0..(CELT_NUM_BANDS - 1) {
assert_eq!(celt_band_stop_hz(b), celt_band_start_hz(b + 1), "band {b}");
}
}
#[test]
fn bands_have_positive_width() {
for b in 0..CELT_NUM_BANDS {
let start = celt_band_start_hz(b).unwrap();
let stop = celt_band_stop_hz(b).unwrap();
assert!(stop > start, "band {b}: {start}..{stop}");
}
}
#[test]
fn columns_scale_by_power_of_two() {
for b in 0..CELT_NUM_BANDS {
let col0 = celt_band_bins_per_channel(b, CeltFrameSize::Ms2_5).unwrap();
for (c, fs) in [
CeltFrameSize::Ms2_5,
CeltFrameSize::Ms5,
CeltFrameSize::Ms10,
CeltFrameSize::Ms20,
]
.into_iter()
.enumerate()
{
assert_eq!(
celt_band_bins_per_channel(b, fs),
Some(col0 << c),
"band {b}, column {c}"
);
}
}
}
#[test]
fn bin_count_in_documented_range() {
for b in 0..CELT_NUM_BANDS {
for fs in [
CeltFrameSize::Ms2_5,
CeltFrameSize::Ms5,
CeltFrameSize::Ms10,
CeltFrameSize::Ms20,
] {
let n = celt_band_bins_per_channel(b, fs).unwrap();
assert!(n >= 1, "band {b}, fs {fs:?}: {n} < 1");
assert!(
n <= CELT_MAX_BINS_PER_BAND,
"band {b}, fs {fs:?}: {n} > {CELT_MAX_BINS_PER_BAND}"
);
}
}
}
#[test]
fn table_55_pinned_cells() {
assert_eq!(celt_band_bins_per_channel(0, CeltFrameSize::Ms2_5), Some(1));
assert_eq!(celt_band_bins_per_channel(0, CeltFrameSize::Ms5), Some(2));
assert_eq!(celt_band_bins_per_channel(0, CeltFrameSize::Ms10), Some(4));
assert_eq!(celt_band_bins_per_channel(0, CeltFrameSize::Ms20), Some(8));
assert_eq!(celt_band_bins_per_channel(8, CeltFrameSize::Ms2_5), Some(2));
assert_eq!(celt_band_bins_per_channel(8, CeltFrameSize::Ms20), Some(16));
assert_eq!(
celt_band_bins_per_channel(12, CeltFrameSize::Ms2_5),
Some(4)
);
assert_eq!(
celt_band_bins_per_channel(12, CeltFrameSize::Ms20),
Some(32)
);
assert_eq!(
celt_band_bins_per_channel(15, CeltFrameSize::Ms2_5),
Some(6)
);
assert_eq!(
celt_band_bins_per_channel(15, CeltFrameSize::Ms20),
Some(48)
);
assert_eq!(
celt_band_bins_per_channel(17, CeltFrameSize::Ms2_5),
Some(8)
);
assert_eq!(
celt_band_bins_per_channel(17, CeltFrameSize::Ms20),
Some(64)
);
assert_eq!(
celt_band_bins_per_channel(20, CeltFrameSize::Ms2_5),
Some(22)
);
assert_eq!(celt_band_bins_per_channel(20, CeltFrameSize::Ms5), Some(44));
assert_eq!(
celt_band_bins_per_channel(20, CeltFrameSize::Ms10),
Some(88)
);
assert_eq!(
celt_band_bins_per_channel(20, CeltFrameSize::Ms20),
Some(176)
);
}
#[test]
fn band_edges_pinned() {
assert_eq!(celt_band_start_hz(0), Some(0));
assert_eq!(celt_band_start_hz(1), Some(200));
assert_eq!(celt_band_start_hz(7), Some(1400));
assert_eq!(celt_band_stop_hz(16), Some(8000));
assert_eq!(celt_band_start_hz(17), Some(8000));
assert_eq!(celt_band_start_hz(20), Some(15600));
assert_eq!(celt_band_stop_hz(20), Some(20000));
}
#[test]
fn out_of_range_band_returns_none() {
assert_eq!(celt_band_bins_per_channel(21, CeltFrameSize::Ms20), None);
assert_eq!(
celt_band_bins_per_channel(usize::MAX, CeltFrameSize::Ms5),
None
);
assert_eq!(celt_band_start_hz(21), None);
assert_eq!(celt_band_stop_hz(21), None);
}
#[test]
fn frame_size_round_trips_tenths_ms() {
assert_eq!(
CeltFrameSize::from_frame_tenths_ms(25),
Some(CeltFrameSize::Ms2_5)
);
assert_eq!(
CeltFrameSize::from_frame_tenths_ms(50),
Some(CeltFrameSize::Ms5)
);
assert_eq!(
CeltFrameSize::from_frame_tenths_ms(100),
Some(CeltFrameSize::Ms10)
);
assert_eq!(
CeltFrameSize::from_frame_tenths_ms(200),
Some(CeltFrameSize::Ms20)
);
assert_eq!(CeltFrameSize::from_frame_tenths_ms(400), None);
assert_eq!(CeltFrameSize::from_frame_tenths_ms(600), None);
assert_eq!(CeltFrameSize::from_frame_tenths_ms(0), None);
assert_eq!(CeltFrameSize::from_frame_tenths_ms(99), None);
assert_eq!(CeltFrameSize::from_frame_tenths_ms(u32::MAX), None);
for fs in [
CeltFrameSize::Ms2_5,
CeltFrameSize::Ms5,
CeltFrameSize::Ms10,
CeltFrameSize::Ms20,
] {
assert_eq!(
CeltFrameSize::from_frame_tenths_ms(fs.to_frame_tenths_ms()),
Some(fs)
);
}
}
#[test]
fn column_index_matches_discriminant() {
assert_eq!(CeltFrameSize::Ms2_5.column_index(), 0);
assert_eq!(CeltFrameSize::Ms5.column_index(), 1);
assert_eq!(CeltFrameSize::Ms10.column_index(), 2);
assert_eq!(CeltFrameSize::Ms20.column_index(), 3);
}
#[test]
fn hybrid_first_coded_band_is_17() {
assert_eq!(celt_first_coded_band(true), 17);
assert_eq!(HYBRID_FIRST_CODED_BAND, 17);
assert_eq!(celt_band_start_hz(celt_first_coded_band(true)), Some(8000));
}
#[test]
fn celt_only_first_coded_band_is_0() {
assert_eq!(celt_first_coded_band(false), 0);
assert_eq!(celt_band_start_hz(celt_first_coded_band(false)), Some(0));
}
#[test]
fn end_coded_band_is_celt_num_bands() {
assert_eq!(celt_end_coded_band(), CELT_NUM_BANDS);
}
#[test]
fn total_bins_per_channel_matches_column_sum() {
for fs in [
CeltFrameSize::Ms2_5,
CeltFrameSize::Ms5,
CeltFrameSize::Ms10,
CeltFrameSize::Ms20,
] {
let celt_only: u32 = (0..CELT_NUM_BANDS)
.map(|b| celt_band_bins_per_channel(b, fs).unwrap() as u32)
.sum();
assert_eq!(
celt_total_bins_per_channel(fs, false),
celt_only,
"celt-only sum at {fs:?}"
);
let hybrid: u32 = (HYBRID_FIRST_CODED_BAND..CELT_NUM_BANDS)
.map(|b| celt_band_bins_per_channel(b, fs).unwrap() as u32)
.sum();
assert_eq!(
celt_total_bins_per_channel(fs, true),
hybrid,
"hybrid sum at {fs:?}"
);
assert!(
celt_total_bins_per_channel(fs, true) < celt_total_bins_per_channel(fs, false),
"hybrid total should be < celt-only total at {fs:?}"
);
}
}
#[test]
fn column_sum_pinned_values() {
assert_eq!(
celt_total_bins_per_channel(CeltFrameSize::Ms2_5, false),
100
);
assert_eq!(celt_total_bins_per_channel(CeltFrameSize::Ms5, false), 200);
assert_eq!(celt_total_bins_per_channel(CeltFrameSize::Ms10, false), 400);
assert_eq!(celt_total_bins_per_channel(CeltFrameSize::Ms20, false), 800);
assert_eq!(celt_total_bins_per_channel(CeltFrameSize::Ms2_5, true), 60);
assert_eq!(celt_total_bins_per_channel(CeltFrameSize::Ms5, true), 120);
assert_eq!(celt_total_bins_per_channel(CeltFrameSize::Ms10, true), 240);
assert_eq!(celt_total_bins_per_channel(CeltFrameSize::Ms20, true), 480);
}
#[test]
fn band_at_hz_round_trips_with_edges() {
for b in 0..CELT_NUM_BANDS {
let start = celt_band_start_hz(b).unwrap() as u32;
let stop = celt_band_stop_hz(b).unwrap() as u32;
assert_eq!(celt_band_at_hz(start), Some(b), "start of band {b}");
let mid = (start + stop) / 2;
assert_eq!(celt_band_at_hz(mid), Some(b), "midpoint of band {b}");
assert_eq!(celt_band_at_hz(stop - 1), Some(b), "stop-1 of band {b}");
}
}
#[test]
fn band_at_hz_rejects_above_20khz() {
assert_eq!(celt_band_at_hz(20000), None);
assert_eq!(celt_band_at_hz(20001), None);
assert_eq!(celt_band_at_hz(48000), None);
assert_eq!(celt_band_at_hz(u32::MAX), None);
}
#[test]
fn band_at_8khz_is_first_hybrid_coded() {
assert_eq!(celt_band_at_hz(8000), Some(HYBRID_FIRST_CODED_BAND));
}
#[test]
fn band_widths_multiples_of_200_hz() {
for b in 0..CELT_NUM_BANDS {
let width =
celt_band_stop_hz(b).unwrap() as u32 - celt_band_start_hz(b).unwrap() as u32;
assert_eq!(width % 200, 0, "band {b} width {width} not multiple of 200");
}
assert_eq!(
celt_band_stop_hz(0).unwrap() - celt_band_start_hz(0).unwrap(),
200
);
assert_eq!(
celt_band_stop_hz(8).unwrap() - celt_band_start_hz(8).unwrap(),
400
);
assert_eq!(
celt_band_stop_hz(20).unwrap() - celt_band_start_hz(20).unwrap(),
4400
);
}
#[test]
fn celt_max_bins_per_band_is_table_max() {
let mut actual_max: u16 = 0;
for b in 0..CELT_NUM_BANDS {
for fs in [
CeltFrameSize::Ms2_5,
CeltFrameSize::Ms5,
CeltFrameSize::Ms10,
CeltFrameSize::Ms20,
] {
let n = celt_band_bins_per_channel(b, fs).unwrap();
if n > actual_max {
actual_max = n;
}
}
}
assert_eq!(actual_max, CELT_MAX_BINS_PER_BAND);
}
}