use crate::celt_band_layout::{
celt_band_bins_per_channel, celt_end_coded_band, celt_first_coded_band, CeltFrameSize,
};
pub const BAND_THRESH_BINS_MULTIPLIER: u32 = 24;
pub const BAND_THRESH_BINS_DIVISOR: u32 = 16;
pub const BAND_THRESH_PER_CHANNEL_EIGHTH_BITS: u32 = 8;
pub const BAND_THRESH_MONO_CHANNELS: u32 = 1;
pub const BAND_THRESH_STEREO_CHANNELS: u32 = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BandThreshError {
InvertedBandWindow {
start: usize,
end: usize,
},
BandWindowOutOfRange {
end: usize,
},
OutputBufferTooSmall {
expected: usize,
provided: usize,
},
}
pub fn band_min_thresh(band: usize, frame_size: CeltFrameSize, is_stereo: bool) -> Option<u32> {
let n = celt_band_bins_per_channel(band, frame_size)? as u32;
let bin_term = (BAND_THRESH_BINS_MULTIPLIER * n) / BAND_THRESH_BINS_DIVISOR;
let channels = if is_stereo {
BAND_THRESH_STEREO_CHANNELS
} else {
BAND_THRESH_MONO_CHANNELS
};
let channel_term = BAND_THRESH_PER_CHANNEL_EIGHTH_BITS * channels;
Some(bin_term.max(channel_term))
}
pub fn compute_band_min_thresh(
start: usize,
end: usize,
frame_size: CeltFrameSize,
is_stereo: bool,
thresh: &mut [u32],
) -> Result<(), BandThreshError> {
if start > end {
return Err(BandThreshError::InvertedBandWindow { start, end });
}
if end > celt_end_coded_band() {
return Err(BandThreshError::BandWindowOutOfRange { end });
}
let coded = end - start;
if thresh.len() != coded {
return Err(BandThreshError::OutputBufferTooSmall {
expected: coded,
provided: thresh.len(),
});
}
for (slot, band) in thresh.iter_mut().zip(start..end) {
*slot = band_min_thresh(band, frame_size, is_stereo)
.expect("§4.3 band < CELT_NUM_BANDS by window check");
}
Ok(())
}
pub fn band_min_thresh_vec(
start: usize,
end: usize,
frame_size: CeltFrameSize,
is_stereo: bool,
) -> Result<Vec<u32>, BandThreshError> {
if start > end {
return Err(BandThreshError::InvertedBandWindow { start, end });
}
if end > celt_end_coded_band() {
return Err(BandThreshError::BandWindowOutOfRange { end });
}
let mut v = vec![0u32; end - start];
compute_band_min_thresh(start, end, frame_size, is_stereo, &mut v)?;
Ok(v)
}
pub fn standard_band_window(is_hybrid: bool) -> (usize, usize) {
(celt_first_coded_band(is_hybrid), celt_end_coded_band())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::celt_band_layout::{CELT_NUM_BANDS, HYBRID_FIRST_CODED_BAND};
#[test]
fn bins_multiplier_matches_rfc_24() {
assert_eq!(BAND_THRESH_BINS_MULTIPLIER, 24);
}
#[test]
fn bins_divisor_matches_rfc_16() {
assert_eq!(BAND_THRESH_BINS_DIVISOR, 16);
}
#[test]
fn per_channel_term_matches_rfc_one_whole_bit() {
assert_eq!(BAND_THRESH_PER_CHANNEL_EIGHTH_BITS, 8);
assert_eq!(BAND_THRESH_PER_CHANNEL_EIGHTH_BITS / 8, 1);
}
#[test]
fn channel_multipliers_match_audio_layout() {
assert_eq!(BAND_THRESH_MONO_CHANNELS, 1);
assert_eq!(BAND_THRESH_STEREO_CHANNELS, 2);
}
#[test]
fn band0_2_5ms_mono_uses_channel_term() {
assert_eq!(band_min_thresh(0, CeltFrameSize::Ms2_5, false), Some(8));
}
#[test]
fn band0_2_5ms_stereo_uses_channel_term() {
assert_eq!(band_min_thresh(0, CeltFrameSize::Ms2_5, true), Some(16));
}
#[test]
fn band0_20ms_mono_uses_channel_term_at_eight_bins() {
assert_eq!(band_min_thresh(0, CeltFrameSize::Ms20, false), Some(12));
}
#[test]
fn band0_20ms_stereo_channel_term_still_wins() {
assert_eq!(band_min_thresh(0, CeltFrameSize::Ms20, true), Some(16));
}
#[test]
fn band20_20ms_mono_uses_bin_term() {
assert_eq!(band_min_thresh(20, CeltFrameSize::Ms20, false), Some(264));
}
#[test]
fn band20_20ms_stereo_uses_bin_term() {
assert_eq!(band_min_thresh(20, CeltFrameSize::Ms20, true), Some(264));
}
#[test]
fn band21_or_higher_returns_none() {
assert_eq!(band_min_thresh(21, CeltFrameSize::Ms20, false), None);
assert_eq!(band_min_thresh(100, CeltFrameSize::Ms20, false), None);
}
#[test]
fn band_min_thresh_matches_formula_for_every_band_and_frame_size() {
for band in 0..CELT_NUM_BANDS {
for fs in [
CeltFrameSize::Ms2_5,
CeltFrameSize::Ms5,
CeltFrameSize::Ms10,
CeltFrameSize::Ms20,
] {
let n = celt_band_bins_per_channel(band, fs).unwrap() as u32;
let bin_term = (24 * n) / 16;
for (channels, stereo) in [(1u32, false), (2u32, true)] {
let channel_term = 8 * channels;
let want = bin_term.max(channel_term);
let got = band_min_thresh(band, fs, stereo).unwrap();
assert_eq!(got, want, "band={band} fs={fs:?} channels={channels}");
}
}
}
}
#[test]
fn band_min_thresh_independent_of_channel_count_when_bin_term_dominates() {
let mono = band_min_thresh(20, CeltFrameSize::Ms20, false).unwrap();
let stereo = band_min_thresh(20, CeltFrameSize::Ms20, true).unwrap();
assert_eq!(mono, stereo);
assert_eq!(mono, 264);
}
#[test]
fn band_min_thresh_doubles_with_stereo_when_channel_term_dominates() {
let mono = band_min_thresh(0, CeltFrameSize::Ms2_5, false).unwrap();
let stereo = band_min_thresh(0, CeltFrameSize::Ms2_5, true).unwrap();
assert_eq!(stereo, 2 * mono);
}
#[test]
fn compute_full_celt_only_window_mono_20ms() {
let mut out = [0u32; 21];
compute_band_min_thresh(0, 21, CeltFrameSize::Ms20, false, &mut out).unwrap();
for (slot, band) in out.iter().zip(0..21) {
assert_eq!(
*slot,
band_min_thresh(band, CeltFrameSize::Ms20, false).unwrap()
);
}
}
#[test]
fn compute_hybrid_window_stereo_20ms() {
let mut out = [0u32; 4];
compute_band_min_thresh(
HYBRID_FIRST_CODED_BAND,
21,
CeltFrameSize::Ms20,
true,
&mut out,
)
.unwrap();
for (slot, band) in out.iter().zip(HYBRID_FIRST_CODED_BAND..21) {
assert_eq!(
*slot,
band_min_thresh(band, CeltFrameSize::Ms20, true).unwrap()
);
}
}
#[test]
fn compute_partial_window_2_5ms() {
let mut out = [0u32; 13];
compute_band_min_thresh(0, 13, CeltFrameSize::Ms2_5, false, &mut out).unwrap();
for slot in &out {
assert_eq!(*slot, 8);
}
}
#[test]
fn compute_window_5ms_stereo_band20() {
let n = celt_band_bins_per_channel(20, CeltFrameSize::Ms5).unwrap();
assert_eq!(n, 44);
let mut out = [0u32; 1];
compute_band_min_thresh(20, 21, CeltFrameSize::Ms5, true, &mut out).unwrap();
assert_eq!(out[0], 66);
}
#[test]
fn inverted_window_rejected() {
let mut out = [0u32; 0];
let err = compute_band_min_thresh(5, 3, CeltFrameSize::Ms20, false, &mut out).unwrap_err();
assert_eq!(
err,
BandThreshError::InvertedBandWindow { start: 5, end: 3 }
);
}
#[test]
fn window_past_max_band_rejected() {
let mut out = [0u32; 22];
let err = compute_band_min_thresh(0, 22, CeltFrameSize::Ms20, false, &mut out).unwrap_err();
assert_eq!(err, BandThreshError::BandWindowOutOfRange { end: 22 });
}
#[test]
fn output_buffer_too_small_rejected() {
let mut out = [0u32; 20]; let err = compute_band_min_thresh(0, 21, CeltFrameSize::Ms20, false, &mut out).unwrap_err();
assert_eq!(
err,
BandThreshError::OutputBufferTooSmall {
expected: 21,
provided: 20,
}
);
}
#[test]
fn output_buffer_too_large_also_rejected() {
let mut out = [0u32; 22]; let err = compute_band_min_thresh(0, 21, CeltFrameSize::Ms20, false, &mut out).unwrap_err();
assert_eq!(
err,
BandThreshError::OutputBufferTooSmall {
expected: 21,
provided: 22,
}
);
}
#[test]
fn empty_window_succeeds_with_empty_output() {
let mut out = [0u32; 0];
compute_band_min_thresh(5, 5, CeltFrameSize::Ms20, false, &mut out).unwrap();
compute_band_min_thresh(0, 0, CeltFrameSize::Ms2_5, true, &mut out).unwrap();
}
#[test]
fn vec_helper_matches_slice_form() {
let v = band_min_thresh_vec(0, 21, CeltFrameSize::Ms10, true).unwrap();
let mut out = [0u32; 21];
compute_band_min_thresh(0, 21, CeltFrameSize::Ms10, true, &mut out).unwrap();
assert_eq!(v.as_slice(), &out);
}
#[test]
fn vec_helper_propagates_window_errors() {
let err = band_min_thresh_vec(0, 22, CeltFrameSize::Ms20, false).unwrap_err();
assert_eq!(err, BandThreshError::BandWindowOutOfRange { end: 22 });
let err = band_min_thresh_vec(7, 4, CeltFrameSize::Ms20, false).unwrap_err();
assert_eq!(
err,
BandThreshError::InvertedBandWindow { start: 7, end: 4 }
);
}
#[test]
fn vec_helper_returns_empty_for_empty_window() {
let v = band_min_thresh_vec(5, 5, CeltFrameSize::Ms20, true).unwrap();
assert!(v.is_empty());
}
#[test]
fn standard_window_celt_only_is_zero_to_twentyone() {
let (start, end) = standard_band_window(false);
assert_eq!(start, 0);
assert_eq!(end, 21);
}
#[test]
fn standard_window_hybrid_is_seventeen_to_twentyone() {
let (start, end) = standard_band_window(true);
assert_eq!(start, HYBRID_FIRST_CODED_BAND);
assert_eq!(end, 21);
assert_eq!(start, 17);
}
#[test]
fn every_band_thresh_is_at_least_one_whole_bit_mono() {
for band in 0..CELT_NUM_BANDS {
for fs in [
CeltFrameSize::Ms2_5,
CeltFrameSize::Ms5,
CeltFrameSize::Ms10,
CeltFrameSize::Ms20,
] {
let t = band_min_thresh(band, fs, false).unwrap();
assert!(t >= 8, "band={band} fs={fs:?} thresh={t}");
}
}
}
#[test]
fn every_band_thresh_is_at_least_one_whole_bit_per_channel_stereo() {
for band in 0..CELT_NUM_BANDS {
for fs in [
CeltFrameSize::Ms2_5,
CeltFrameSize::Ms5,
CeltFrameSize::Ms10,
CeltFrameSize::Ms20,
] {
let t = band_min_thresh(band, fs, true).unwrap();
assert!(t >= 16, "band={band} fs={fs:?} thresh={t}");
}
}
}
#[test]
fn thresh_monotonic_or_equal_with_frame_size_for_fixed_band() {
for band in 0..CELT_NUM_BANDS {
for stereo in [false, true] {
let t0 = band_min_thresh(band, CeltFrameSize::Ms2_5, stereo).unwrap();
let t1 = band_min_thresh(band, CeltFrameSize::Ms5, stereo).unwrap();
let t2 = band_min_thresh(band, CeltFrameSize::Ms10, stereo).unwrap();
let t3 = band_min_thresh(band, CeltFrameSize::Ms20, stereo).unwrap();
assert!(t0 <= t1, "band={band} stereo={stereo}: t0={t0}, t1={t1}");
assert!(t1 <= t2, "band={band} stereo={stereo}: t1={t1}, t2={t2}");
assert!(t2 <= t3, "band={band} stereo={stereo}: t2={t2}, t3={t3}");
}
}
}
#[test]
fn stereo_thresh_at_least_mono_for_every_band_and_frame_size() {
for band in 0..CELT_NUM_BANDS {
for fs in [
CeltFrameSize::Ms2_5,
CeltFrameSize::Ms5,
CeltFrameSize::Ms10,
CeltFrameSize::Ms20,
] {
let mono = band_min_thresh(band, fs, false).unwrap();
let stereo = band_min_thresh(band, fs, true).unwrap();
assert!(stereo >= mono, "band={band} fs={fs:?}");
}
}
}
#[test]
fn thresh_units_are_eighth_bits() {
let n = 16u32;
let bin_term = (24 * n) / 16;
assert_eq!(bin_term, 24);
let per_bin_128ths = (bin_term * 16) / n; assert_eq!(per_bin_128ths * 2, 48);
}
#[test]
fn band_min_thresh_pins_table55_band8_20ms_stereo() {
let n = celt_band_bins_per_channel(8, CeltFrameSize::Ms20).unwrap();
assert_eq!(n, 16);
assert_eq!(band_min_thresh(8, CeltFrameSize::Ms20, true), Some(24));
}
#[test]
fn band_min_thresh_pins_table55_band20_2_5ms_stereo() {
let n = celt_band_bins_per_channel(20, CeltFrameSize::Ms2_5).unwrap() as u32;
let bin_term = (24 * n) / 16;
let want = bin_term.max(16);
let got = band_min_thresh(20, CeltFrameSize::Ms2_5, true).unwrap();
assert_eq!(got, want);
}
#[test]
fn determinism_across_repeats() {
let a = band_min_thresh_vec(0, 21, CeltFrameSize::Ms20, true).unwrap();
let b = band_min_thresh_vec(0, 21, CeltFrameSize::Ms20, true).unwrap();
assert_eq!(a, b);
}
#[test]
fn debug_format_renders() {
let err = BandThreshError::InvertedBandWindow { start: 7, end: 3 };
let s = format!("{err:?}");
assert!(s.contains("InvertedBandWindow"));
}
#[test]
fn integrates_with_band_layout_hybrid_window() {
let (start, end) = standard_band_window(true);
let v = band_min_thresh_vec(start, end, CeltFrameSize::Ms10, true).unwrap();
assert_eq!(v.len(), 4);
for &t in &v {
assert!(t >= 16);
}
}
}