Skip to main content

oxideav_opus/
celt_cache_caps50.rs

1//! CELT §4.3.3 per-band maximum-allocation parameter surface
2//! (RFC 6716 §4.3.3, pp. 113–114).
3//!
4//! The §4.3.3 *Bit Allocation* procedure caps each band's allocation
5//! at a precomputed maximum, called `cap[]`. The §4.3.3 narrative
6//! (RFC 6716 §4.3.3, p. 113) describes the cap as an approximation of
7//! the largest space each band can usefully consume for a given mode:
8//! a band that hits the cap cannot consume any further bit allocation,
9//! and the surplus rolls over into the remaining bands.
10//!
11//! The §4.3.3 maximums are bits/sample values precomputed in a static
12//! table indexed by `(LM, stereo, band)`. RFC 6716 §4.3.3 (p. 113) names
13//! the table `cache_caps50[]` and notes its 168 entries are organised
14//! as `i = nbBands * (2*LM + stereo)` with `nbBands = 21`, so the
15//! `i`-th index covers one `(LM ∈ 0..=3, stereo ∈ {0,1}, band ∈ 0..=20)`
16//! triple. The §4.3.3 convert-to-cap rule then folds the bits/sample
17//! cap into a per-band bit cap via
18//!
19//! ```text
20//!     cap[band] = ((cache_caps50[i] + 64) * channels * N) / 4
21//! ```
22//!
23//! with `channels ∈ {1,2}` and `N` = MDCT bins per band per channel
24//! (from §4.3 Table 55 / the [`crate::celt_band_layout`] lookup) and
25//! integer division. RFC 6716 §4.3.3 (p. 114) describes the function
26//! that performs this conversion as `init_caps()`. The resulting
27//! `cap[]` elements fit in `i16` but not in `i8`.
28//!
29//! This module owns only the §4.3.3 *parameter surface*: the 168-byte
30//! table plus the typed accessor that pairs it with the §4.3.3
31//! `(LM, stereo, band)` indexing rule and the `init_caps()` convert
32//! rule. The §4.3.3 bit allocation orchestration that consumes
33//! `cap[]` (boost / trim / anti-collapse / skip / dual-stereo
34//! reservations, the Table 57 static allocation search) is gated on
35//! its own follow-up work and runs at the call site of the lookup.
36//!
37//! The §4.3.3 narrative is transcribed from RFC 6716,
38//! `docs/audio/opus/rfc6716-opus.txt`, pp. 113–114, plus the §2.2
39//! narrative in `docs/audio/celt/spec/celt-coarse-energy-and-allocation.md`.
40//! The 168-byte `cache_caps50` data is uncopyrightable numeric facts
41//! extracted into `docs/audio/celt/tables/cache_caps50.csv` (see
42//! the `cache_caps50.meta` sidecar for the canonical layout). The
43//! values are reproduced inline here so the table is available
44//! without filesystem I/O at runtime.
45//!
46//! ## Layout
47//!
48//! [`CACHE_CAPS50`] is a `[u8; 168]` flattened table; the §4.3.3
49//! indexing rule `i = nbBands * (2*LM + stereo)` with `nbBands = 21`
50//! recovers the `(LM, stereo)` row from a flat offset, and `band ∈
51//! 0..=20` selects the cell inside the row. The CSV's "row r =>
52//! LM=r//2, stereo=r%2" comment row matches: CSV row 0 is
53//! `(LM=0, stereo=0)`, CSV row 1 is `(LM=0, stereo=1)`, …, CSV row 7
54//! is `(LM=3, stereo=1)`.
55//!
56//! ## §4.3.3 `init_caps()` conversion
57//!
58//! Per RFC 6716 §4.3.3 (p. 113), to turn the bits/sample entries in
59//! the table into the per-band bit cap the allocator searches against:
60//!
61//! 1. Pick the `(LM ∈ 0..=3, stereo ∈ {0,1})` selector for the frame.
62//! 2. Look up `caps_value = cache_caps50[i]` with
63//!    `i = nbBands * (2*LM + stereo)` + `band`.
64//! 3. `cap[band] = ((caps_value + 64) * channels * N) / 4` (integer
65//!    division), where `channels ∈ {1,2}` is the frame's channel
66//!    count and `N` is the §4.3 Table 55 per-channel bin count for
67//!    `band` at this `LM`.
68//!
69//! [`cap_for_band_bits`] computes step 3 in a single typed call given
70//! the `(LM, stereo, band, channels, n_bins)` tuple; [`cache_caps_value`]
71//! returns just the raw bits/sample byte (step 2).
72//!
73//! ## Provenance
74//!
75//! Narrative: RFC 6716 §4.3.3 (pp. 113–114) in
76//! `docs/audio/opus/rfc6716-opus.txt`; the §2.2 narrative
77//! `docs/audio/celt/spec/celt-coarse-energy-and-allocation.md`
78//! cross-references both the RFC and the CSV. Numeric table: the
79//! 168-byte sequence from `docs/audio/celt/tables/cache_caps50.csv`
80//! (see the `.meta` sidecar for the canonical layout).
81
82use crate::celt_band_layout::CELT_NUM_BANDS;
83
84/// Number of CELT frame sizes that index the [`CACHE_CAPS50`] outer
85/// axis (`LM ∈ {0,1,2,3}` per RFC 6716 §4.3 = 2.5 / 5 / 10 / 20 ms).
86pub const CACHE_CAPS50_LM_COUNT: usize = 4;
87
88/// Number of channel-count modes per `(LM, band)` cell (RFC 6716
89/// §4.3.3: `0 = mono`, `1 = stereo`).
90pub const CACHE_CAPS50_STEREO_COUNT: usize = 2;
91
92/// Stereo-axis index selecting the **mono** row (RFC 6716 §4.3.3:
93/// `stereo = 0`).
94pub const CACHE_CAPS50_STEREO_MONO: usize = 0;
95
96/// Stereo-axis index selecting the **stereo** row (RFC 6716 §4.3.3:
97/// `stereo = 1`).
98pub const CACHE_CAPS50_STEREO_STEREO: usize = 1;
99
100/// Total entries in [`CACHE_CAPS50`]: 4 × 2 × 21 = 168 bytes.
101pub const CACHE_CAPS50_TOTAL_BYTES: usize =
102    CACHE_CAPS50_LM_COUNT * CACHE_CAPS50_STEREO_COUNT * CELT_NUM_BANDS;
103
104/// §4.3.3 `init_caps()` additive bias applied to every table entry
105/// before the per-band scale (RFC 6716 §4.3.3 p. 113: "the i-th index
106/// of cache.caps + 64").
107pub const INIT_CAPS_BIAS: u32 = 64;
108
109/// §4.3.3 `init_caps()` final divisor (RFC 6716 §4.3.3 p. 113: "divide
110/// the result by 4 using integer division").
111pub const INIT_CAPS_DIVISOR: u32 = 4;
112
113/// §4.3.3 channel-count multiplier upper bound (RFC 6716 §4.3.3 p. 113:
114/// `channels ∈ {1, 2}`).
115pub const INIT_CAPS_MAX_CHANNELS: u32 = 2;
116
117/// §4.3.3 `cache_caps50` per-band maximum-allocation table
118/// (RFC 6716 §4.3.3, pp. 113–114).
119///
120/// Each entry is a Q0 bits/sample value (unsigned byte) the
121/// [`cap_for_band_bits`] / [`init_caps`] conversion folds into the
122/// per-band bit cap. The 168 entries are stored as 8 logical rows of
123/// 21 bytes each; row `r` corresponds to `(LM = r/2, stereo = r%2)`.
124/// Linear indexing is `i = CELT_NUM_BANDS * (2*LM + stereo) + band`
125/// (the §4.3.3 `nbBands * (2*LM + stereo)` row stride with
126/// `nbBands = 21`).
127///
128/// Data provenance: `docs/audio/celt/tables/cache_caps50.csv`
129/// (see the `.meta` sidecar for the canonical layout). Only the
130/// numeric data is reproduced here.
131#[rustfmt::skip]
132pub const CACHE_CAPS50: [u8; CACHE_CAPS50_TOTAL_BYTES] = [
133    // row 0 (LM=0, stereo=0; 2.5 ms mono)
134    224, 224, 224, 224, 224, 224, 224, 224, 160, 160, 160, 160, 185, 185, 185, 178, 178, 168, 134, 61, 37,
135    // row 1 (LM=0, stereo=1; 2.5 ms stereo)
136    224, 224, 224, 224, 224, 224, 224, 224, 240, 240, 240, 240, 207, 207, 207, 198, 198, 183, 144, 66, 40,
137    // row 2 (LM=1, stereo=0; 5 ms mono)
138    160, 160, 160, 160, 160, 160, 160, 160, 185, 185, 185, 185, 193, 193, 193, 183, 183, 172, 138, 64, 38,
139    // row 3 (LM=1, stereo=1; 5 ms stereo)
140    240, 240, 240, 240, 240, 240, 240, 240, 207, 207, 207, 207, 204, 204, 204, 193, 193, 180, 143, 66, 40,
141    // row 4 (LM=2, stereo=0; 10 ms mono)
142    185, 185, 185, 185, 185, 185, 185, 185, 193, 193, 193, 193, 193, 193, 193, 183, 183, 172, 138, 65, 39,
143    // row 5 (LM=2, stereo=1; 10 ms stereo)
144    207, 207, 207, 207, 207, 207, 207, 207, 204, 204, 204, 204, 201, 201, 201, 188, 188, 176, 141, 66, 40,
145    // row 6 (LM=3, stereo=0; 20 ms mono)
146    193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 194, 194, 194, 184, 184, 173, 139, 65, 39,
147    // row 7 (LM=3, stereo=1; 20 ms stereo)
148    204, 204, 204, 204, 204, 204, 204, 204, 201, 201, 201, 201, 198, 198, 198, 187, 187, 175, 140, 66, 40,
149];
150
151/// §4.3.3 stereo-axis selector.
152///
153/// The `stereo` axis of [`CACHE_CAPS50`] is binary: mono = 0, stereo
154/// = 1. Modelled as a typed enum so the caller can't confuse it with
155/// the channel-count multiplier of [`init_caps`] (which is `1` or
156/// `2`, not `0` or `1`).
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub enum CacheCapsStereo {
159    /// Mono row (RFC 6716 §4.3.3: `stereo = 0`).
160    Mono,
161    /// Stereo row (RFC 6716 §4.3.3: `stereo = 1`).
162    Stereo,
163}
164
165impl CacheCapsStereo {
166    /// Stereo-axis index into the §4.3.3 row-stride `2*LM + stereo`.
167    pub const fn axis_index(self) -> usize {
168        match self {
169            CacheCapsStereo::Mono => CACHE_CAPS50_STEREO_MONO,
170            CacheCapsStereo::Stereo => CACHE_CAPS50_STEREO_STEREO,
171        }
172    }
173
174    /// `channels` multiplier consumed by the §4.3.3 `init_caps()`
175    /// conversion (`1` for mono, `2` for stereo).
176    pub const fn channels(self) -> u32 {
177        match self {
178            CacheCapsStereo::Mono => 1,
179            CacheCapsStereo::Stereo => 2,
180        }
181    }
182
183    /// Decode a raw `bool` channel-count signal (`false = mono,
184    /// true = stereo`) into a selector. Use this when the upstream
185    /// signal is the TOC stereo-flag boolean from §3.1.
186    pub const fn from_is_stereo(is_stereo: bool) -> Self {
187        if is_stereo {
188            CacheCapsStereo::Stereo
189        } else {
190            CacheCapsStereo::Mono
191        }
192    }
193}
194
195/// Errors returned by the [`cache_caps_value`] / [`cap_for_band_bits`]
196/// accessors for out-of-range indices.
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198pub enum CacheCaps50Error {
199    /// `lm` is outside `0..4` (RFC 6716 §4.3 only defines four CELT
200    /// frame sizes).
201    LmOutOfRange { lm: u32 },
202    /// `band` is outside `0..21` (the §4.3 Table 55 band count).
203    BandOutOfRange { band: u32 },
204    /// `channels` is outside `1..=2` (RFC 6716 §4.3.3 p. 113 declares
205    /// `channels ∈ {1,2}` for the conversion).
206    ChannelsOutOfRange { channels: u32 },
207}
208
209/// Compute the linear offset into [`CACHE_CAPS50`] for one
210/// `(LM, stereo, band)` cell (RFC 6716 §4.3.3, p. 113).
211///
212/// The §4.3.3 indexing rule is `i = nbBands * (2*LM + stereo) + band`
213/// with `nbBands = 21`. Returns the flat offset into [`CACHE_CAPS50`].
214///
215/// Both `lm < 4` and `band < 21` are caller-side preconditions; this
216/// `const fn` is total over `(lm, stereo, band)` triples constrained
217/// by the const-fn type signature only.
218pub const fn cache_caps_offset(lm: usize, stereo: CacheCapsStereo, band: usize) -> usize {
219    CELT_NUM_BANDS * (2 * lm + stereo.axis_index()) + band
220}
221
222/// Look up the raw `cache_caps50` bits/sample byte for one
223/// `(LM, stereo, band)` cell (RFC 6716 §4.3.3, p. 113).
224///
225/// This is the §4.3.3 step "look up `cache_caps50[i]`" — the lookup
226/// itself, before the [`init_caps`] `(value + 64) * channels * N / 4`
227/// scale. Callers who only need the bits/sample byte (e.g.
228/// table-level cross-checks against the CSV) use this; callers who
229/// need the final per-band bit cap call [`cap_for_band_bits`].
230pub fn cache_caps_value(
231    lm: u32,
232    stereo: CacheCapsStereo,
233    band: u32,
234) -> Result<u8, CacheCaps50Error> {
235    if lm >= CACHE_CAPS50_LM_COUNT as u32 {
236        return Err(CacheCaps50Error::LmOutOfRange { lm });
237    }
238    if band >= CELT_NUM_BANDS as u32 {
239        return Err(CacheCaps50Error::BandOutOfRange { band });
240    }
241    let off = cache_caps_offset(lm as usize, stereo, band as usize);
242    Ok(CACHE_CAPS50[off])
243}
244
245/// Borrow the full 21-byte row for a single `(LM, stereo)` cell of
246/// [`CACHE_CAPS50`] (RFC 6716 §4.3.3, p. 113).
247///
248/// This is the §4.3.3 "one row of 21 caps" (one CSV row in
249/// `docs/audio/celt/tables/cache_caps50.csv`). Returned as a borrowed
250/// slice so callers may iterate the band loop without re-indexing.
251pub fn cache_caps_row(lm: u32, stereo: CacheCapsStereo) -> Result<&'static [u8], CacheCaps50Error> {
252    if lm >= CACHE_CAPS50_LM_COUNT as u32 {
253        return Err(CacheCaps50Error::LmOutOfRange { lm });
254    }
255    let base = cache_caps_offset(lm as usize, stereo, 0);
256    Ok(&CACHE_CAPS50[base..base + CELT_NUM_BANDS])
257}
258
259/// Apply the §4.3.3 `init_caps()` convert rule to a single
260/// `cache_caps50` byte (RFC 6716 §4.3.3, p. 113).
261///
262/// Returns `((caps_value + 64) * channels * n_bins) / 4` per the
263/// §4.3.3 step 4 rule:
264///
265/// > Set the maximum for the band to the i-th index of cache.caps +
266/// > 64 and multiply by the number of channels in the current frame
267/// > (one or two) and by N, then divide the result by 4 using integer
268/// > division.
269///
270/// `channels ∈ {1, 2}` and `n_bins` is the §4.3 Table 55 per-channel
271/// MDCT-bin count for the band. The result is a per-band bit cap that
272/// fits in `i16` but not `i8` per the §4.3.3 narrative; we return
273/// `u32` so the caller can hold the conversion product without risk
274/// of overflow on intermediate arithmetic.
275///
276/// This is `init_caps()` for a single band; iterating the §4.3 band
277/// loop and assembling the full `cap[]` vector is the responsibility
278/// of the §4.3.3 allocator. The function is named `init_caps` per the
279/// §4.3.3 narrative even though it operates on a single band.
280pub const fn init_caps(caps_value: u8, channels: u32, n_bins: u32) -> u32 {
281    ((caps_value as u32 + INIT_CAPS_BIAS) * channels * n_bins) / INIT_CAPS_DIVISOR
282}
283
284/// Compute the §4.3.3 per-band bit cap for one
285/// `(LM, stereo, band, channels, n_bins)` tuple (RFC 6716 §4.3.3,
286/// p. 113).
287///
288/// Looks up `cache_caps50[i]` with `i = nbBands * (2*LM + stereo) +
289/// band`, then applies the [`init_caps`] convert rule. Returns the
290/// final per-band bit cap the §4.3.3 allocator searches against.
291///
292/// The §4.3.3 `stereo` axis selector and the `channels` multiplier
293/// are *independent inputs*: in the standard Opus path they agree
294/// (mono frame → `stereo = Mono` and `channels = 1`; stereo frame →
295/// `stereo = Stereo` and `channels = 2`), but the §4.3.3 narrative
296/// keeps them as separate parameters of `init_caps()`. We mirror that
297/// shape to match the spec and to catch a caller that accidentally
298/// passes mismatched values via the [`CacheCaps50Error::ChannelsOutOfRange`]
299/// error.
300///
301/// `n_bins` is the §4.3 Table 55 *per-channel* MDCT-bin count for
302/// the band at this LM (use [`crate::celt_band_layout::celt_band_bins_per_channel`]).
303pub fn cap_for_band_bits(
304    lm: u32,
305    stereo: CacheCapsStereo,
306    band: u32,
307    channels: u32,
308    n_bins: u32,
309) -> Result<u32, CacheCaps50Error> {
310    if channels == 0 || channels > INIT_CAPS_MAX_CHANNELS {
311        return Err(CacheCaps50Error::ChannelsOutOfRange { channels });
312    }
313    let caps_value = cache_caps_value(lm, stereo, band)?;
314    Ok(init_caps(caps_value, channels, n_bins))
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320    use crate::celt_band_layout::{celt_band_bins_per_channel, CeltFrameSize};
321
322    // ---- Table-shape invariants ----
323
324    #[test]
325    fn table_shape_constants_match_struct() {
326        assert_eq!(CACHE_CAPS50_LM_COUNT, 4);
327        assert_eq!(CACHE_CAPS50_STEREO_COUNT, 2);
328        assert_eq!(CACHE_CAPS50_TOTAL_BYTES, 168);
329        assert_eq!(CACHE_CAPS50.len(), CACHE_CAPS50_TOTAL_BYTES);
330        assert_eq!(CELT_NUM_BANDS, 21);
331    }
332
333    #[test]
334    fn init_caps_constants_match_rfc() {
335        assert_eq!(INIT_CAPS_BIAS, 64);
336        assert_eq!(INIT_CAPS_DIVISOR, 4);
337        assert_eq!(INIT_CAPS_MAX_CHANNELS, 2);
338    }
339
340    // ---- Stereo-axis index constants pinned to the §4.3.3 `2*LM +
341    //      stereo` convention ----
342
343    #[test]
344    fn stereo_axis_index_constants_match_rfc_convention() {
345        assert_eq!(CACHE_CAPS50_STEREO_MONO, 0);
346        assert_eq!(CACHE_CAPS50_STEREO_STEREO, 1);
347        assert_eq!(CacheCapsStereo::Mono.axis_index(), 0);
348        assert_eq!(CacheCapsStereo::Stereo.axis_index(), 1);
349    }
350
351    #[test]
352    fn cache_caps_stereo_channels_helper_matches_init_caps_input() {
353        // The §4.3.3 init_caps() multiplier is `channels`, not the
354        // stereo axis index. Pin the mapping so a future edit can't
355        // silently confuse the two.
356        assert_eq!(CacheCapsStereo::Mono.channels(), 1);
357        assert_eq!(CacheCapsStereo::Stereo.channels(), 2);
358    }
359
360    #[test]
361    fn cache_caps_stereo_from_is_stereo_boolean_round_trip() {
362        assert_eq!(
363            CacheCapsStereo::from_is_stereo(false),
364            CacheCapsStereo::Mono
365        );
366        assert_eq!(
367            CacheCapsStereo::from_is_stereo(true),
368            CacheCapsStereo::Stereo
369        );
370    }
371
372    // ---- Spot-check Q0 values against the CSV extract ----
373    //
374    // These pins reproduce hand-picked cells from
375    // `docs/audio/celt/tables/cache_caps50.csv` so a future edit that
376    // reorders the rows or drops an entry trips the suite.
377
378    #[test]
379    fn csv_row0_band0_is_224() {
380        // CSV row 0 (LM=0, stereo=0), column 0.
381        assert_eq!(cache_caps_value(0, CacheCapsStereo::Mono, 0).unwrap(), 224);
382    }
383
384    #[test]
385    fn csv_row1_band20_is_40() {
386        // CSV row 1 (LM=0, stereo=1), column 20 — the high-band
387        // tail of the 2.5 ms stereo row.
388        assert_eq!(
389            cache_caps_value(0, CacheCapsStereo::Stereo, 20).unwrap(),
390            40
391        );
392    }
393
394    #[test]
395    fn csv_row2_band0_is_160() {
396        // CSV row 2 (LM=1, stereo=0), column 0 — the 5 ms mono first
397        // band.
398        assert_eq!(cache_caps_value(1, CacheCapsStereo::Mono, 0).unwrap(), 160);
399    }
400
401    #[test]
402    fn csv_row3_band8_is_207() {
403        // CSV row 3 (LM=1, stereo=1), column 8 — boundary cell at the
404        // first mid-band tier of the 5 ms stereo row.
405        assert_eq!(
406            cache_caps_value(1, CacheCapsStereo::Stereo, 8).unwrap(),
407            207
408        );
409    }
410
411    #[test]
412    fn csv_row4_band12_is_193() {
413        // CSV row 4 (LM=2, stereo=0), column 12 — mid-band plateau of
414        // the 10 ms mono row.
415        assert_eq!(cache_caps_value(2, CacheCapsStereo::Mono, 12).unwrap(), 193);
416    }
417
418    #[test]
419    fn csv_row5_band17_is_176() {
420        // CSV row 5 (LM=2, stereo=1), column 17 — Hybrid-reachable
421        // band in the 10 ms stereo row.
422        assert_eq!(
423            cache_caps_value(2, CacheCapsStereo::Stereo, 17).unwrap(),
424            176
425        );
426    }
427
428    #[test]
429    fn csv_row6_band20_is_39() {
430        // CSV row 6 (LM=3, stereo=0), column 20 — the high-band tail
431        // of the 20 ms mono row (CELT-only headline frame size).
432        assert_eq!(cache_caps_value(3, CacheCapsStereo::Mono, 20).unwrap(), 39);
433    }
434
435    #[test]
436    fn csv_row7_band0_is_204() {
437        // CSV row 7 (LM=3, stereo=1), column 0 — the first band of
438        // the 20 ms stereo row.
439        assert_eq!(
440            cache_caps_value(3, CacheCapsStereo::Stereo, 0).unwrap(),
441            204
442        );
443    }
444
445    // ---- Row layout matches the §4.3.3 `2*LM + stereo` row-stride
446    //      indexing rule ----
447
448    #[test]
449    fn cache_caps_offset_matches_rfc_row_stride_rule() {
450        for lm in 0..CACHE_CAPS50_LM_COUNT {
451            for stereo_idx in 0..CACHE_CAPS50_STEREO_COUNT {
452                for band in 0..CELT_NUM_BANDS {
453                    let stereo = if stereo_idx == 0 {
454                        CacheCapsStereo::Mono
455                    } else {
456                        CacheCapsStereo::Stereo
457                    };
458                    let off = cache_caps_offset(lm, stereo, band);
459                    // The §4.3.3 rule: i = nbBands * (2*LM + stereo) + band.
460                    let expected = CELT_NUM_BANDS * (2 * lm + stereo_idx) + band;
461                    assert_eq!(off, expected);
462                }
463            }
464        }
465    }
466
467    #[test]
468    fn cache_caps_offset_at_table_extremes_pins_endpoints() {
469        // (LM=0, stereo=Mono, band=0) is the very first byte.
470        assert_eq!(cache_caps_offset(0, CacheCapsStereo::Mono, 0), 0);
471        // (LM=3, stereo=Stereo, band=20) is the very last byte.
472        assert_eq!(
473            cache_caps_offset(3, CacheCapsStereo::Stereo, 20),
474            CACHE_CAPS50_TOTAL_BYTES - 1
475        );
476        // And that last byte is the CSV's final 40.
477        assert_eq!(CACHE_CAPS50[CACHE_CAPS50_TOTAL_BYTES - 1], 40);
478    }
479
480    // ---- Total-function sweep over all (LM, stereo, band) cells ----
481
482    #[test]
483    fn cache_caps_value_is_total_over_in_range_inputs() {
484        for lm in 0..CACHE_CAPS50_LM_COUNT as u32 {
485            for stereo in [CacheCapsStereo::Mono, CacheCapsStereo::Stereo] {
486                for band in 0..CELT_NUM_BANDS as u32 {
487                    let v = cache_caps_value(lm, stereo, band).expect("in-range lookup");
488                    let off = cache_caps_offset(lm as usize, stereo, band as usize);
489                    assert_eq!(v, CACHE_CAPS50[off]);
490                }
491            }
492        }
493    }
494
495    // ---- Row-accessor mirrors raw-table indexing ----
496
497    #[test]
498    fn cache_caps_row_matches_cell_lookup_for_every_lm_and_stereo() {
499        for lm in 0..CACHE_CAPS50_LM_COUNT as u32 {
500            for stereo in [CacheCapsStereo::Mono, CacheCapsStereo::Stereo] {
501                let row = cache_caps_row(lm, stereo).unwrap();
502                assert_eq!(row.len(), CELT_NUM_BANDS);
503                for band in 0..CELT_NUM_BANDS as u32 {
504                    let v = cache_caps_value(lm, stereo, band).unwrap();
505                    assert_eq!(v, row[band as usize]);
506                }
507            }
508        }
509    }
510
511    // ---- Error-path coverage ----
512
513    #[test]
514    fn cache_caps_value_rejects_lm_out_of_range() {
515        let err =
516            cache_caps_value(CACHE_CAPS50_LM_COUNT as u32, CacheCapsStereo::Mono, 0).unwrap_err();
517        assert_eq!(err, CacheCaps50Error::LmOutOfRange { lm: 4 });
518        let err = cache_caps_value(u32::MAX, CacheCapsStereo::Mono, 0).unwrap_err();
519        assert_eq!(err, CacheCaps50Error::LmOutOfRange { lm: u32::MAX });
520    }
521
522    #[test]
523    fn cache_caps_value_rejects_band_out_of_range() {
524        let err = cache_caps_value(0, CacheCapsStereo::Mono, CELT_NUM_BANDS as u32).unwrap_err();
525        assert_eq!(err, CacheCaps50Error::BandOutOfRange { band: 21 });
526        let err = cache_caps_value(0, CacheCapsStereo::Stereo, u32::MAX).unwrap_err();
527        assert_eq!(err, CacheCaps50Error::BandOutOfRange { band: u32::MAX });
528    }
529
530    #[test]
531    fn cache_caps_row_rejects_lm_out_of_range() {
532        let err = cache_caps_row(CACHE_CAPS50_LM_COUNT as u32, CacheCapsStereo::Mono).unwrap_err();
533        assert_eq!(err, CacheCaps50Error::LmOutOfRange { lm: 4 });
534    }
535
536    // ---- init_caps() conversion ----
537
538    #[test]
539    fn init_caps_matches_rfc_formula_explicit_case() {
540        // RFC 6716 §4.3.3 p. 113: cap = (cache.caps[i] + 64) * channels * N / 4.
541        // (caps=224, channels=2, N=4) -> (224+64)*2*4 / 4 = 288*8/4 = 576.
542        assert_eq!(init_caps(224, 2, 4), 576);
543        // (caps=40, channels=1, N=12) -> (40+64)*1*12 / 4 = 104*12/4 = 312.
544        assert_eq!(init_caps(40, 1, 12), 312);
545        // Lowest-allowed (caps=0, channels=1, N=1): (0+64)*1*1 / 4 = 16.
546        assert_eq!(init_caps(0, 1, 1), 16);
547        // Highest-allowed (caps=255, channels=2, N=192): (255+64)*2*192 / 4 = 30624.
548        assert_eq!(init_caps(255, 2, 192), 30624);
549    }
550
551    #[test]
552    fn init_caps_integer_division_is_floor() {
553        // (caps=1, channels=1, N=1): (1+64)*1*1 / 4 = 65/4 = 16 (floor).
554        assert_eq!(init_caps(1, 1, 1), 16);
555        // (caps=2, channels=1, N=1): 66/4 = 16 (floor).
556        assert_eq!(init_caps(2, 1, 1), 16);
557        // (caps=3, channels=1, N=1): 67/4 = 16 (floor).
558        assert_eq!(init_caps(3, 1, 1), 16);
559        // (caps=4, channels=1, N=1): 68/4 = 17.
560        assert_eq!(init_caps(4, 1, 1), 17);
561    }
562
563    // ---- cap_for_band_bits — the §4.3.3 init_caps()-with-lookup
564    //      composite ----
565
566    #[test]
567    fn cap_for_band_bits_matches_manual_lookup_plus_init_caps() {
568        // Use a non-trivial cell: (LM=2, stereo=Stereo, band=17).
569        let caps_value = cache_caps_value(2, CacheCapsStereo::Stereo, 17).unwrap();
570        assert_eq!(caps_value, 176);
571        // Bin count for the 17th band at the 10 ms CELT frame size:
572        // pick it up from the §4.3 Table 55 lookup.
573        let n_bins = celt_band_bins_per_channel(17, CeltFrameSize::Ms10).unwrap();
574        let expected = init_caps(caps_value, 2, n_bins as u32);
575        let cap = cap_for_band_bits(2, CacheCapsStereo::Stereo, 17, 2, n_bins as u32).unwrap();
576        assert_eq!(cap, expected);
577    }
578
579    #[test]
580    fn cap_for_band_bits_rejects_channels_out_of_range() {
581        let err = cap_for_band_bits(0, CacheCapsStereo::Mono, 0, 0, 4).unwrap_err();
582        assert_eq!(err, CacheCaps50Error::ChannelsOutOfRange { channels: 0 });
583        let err = cap_for_band_bits(0, CacheCapsStereo::Mono, 0, 3, 4).unwrap_err();
584        assert_eq!(err, CacheCaps50Error::ChannelsOutOfRange { channels: 3 });
585        let err = cap_for_band_bits(0, CacheCapsStereo::Mono, 0, u32::MAX, 4).unwrap_err();
586        assert_eq!(
587            err,
588            CacheCaps50Error::ChannelsOutOfRange { channels: u32::MAX }
589        );
590    }
591
592    #[test]
593    fn cap_for_band_bits_propagates_lm_and_band_errors() {
594        let err = cap_for_band_bits(CACHE_CAPS50_LM_COUNT as u32, CacheCapsStereo::Mono, 0, 1, 4)
595            .unwrap_err();
596        assert_eq!(err, CacheCaps50Error::LmOutOfRange { lm: 4 });
597        let err =
598            cap_for_band_bits(0, CacheCapsStereo::Mono, CELT_NUM_BANDS as u32, 1, 4).unwrap_err();
599        assert_eq!(err, CacheCaps50Error::BandOutOfRange { band: 21 });
600    }
601
602    // ---- §4.3.3 narrative invariant: caps fit in i16 but not i8 ----
603    //
604    // RFC 6716 §4.3.3 p. 113 calls this out directly. Check the
605    // invariant across the full §4.3 band loop at 20 ms (LM=3, the
606    // largest frame) where the per-channel bin count is at its max.
607
608    #[test]
609    fn cap_at_20ms_stereo_fits_in_i16_but_not_i8() {
610        for band in 0..CELT_NUM_BANDS as u32 {
611            let n_bins = celt_band_bins_per_channel(band as usize, CeltFrameSize::Ms20).unwrap();
612            let cap =
613                cap_for_band_bits(3, CacheCapsStereo::Stereo, band, 2, n_bins as u32).unwrap();
614            assert!(
615                cap <= i16::MAX as u32,
616                "cap[{band}] = {cap} should fit in i16"
617            );
618            // Some bands will exceed 127 (the §4.3.3 "but not i8"
619            // half of the invariant). We only need one of them to be
620            // > 127 to validate that claim; check the explicit
621            // assertion in `at_least_one_cap_exceeds_i8`.
622        }
623    }
624
625    #[test]
626    fn at_least_one_cap_exceeds_i8() {
627        let n_bins = celt_band_bins_per_channel(0, CeltFrameSize::Ms20).unwrap();
628        let cap = cap_for_band_bits(3, CacheCapsStereo::Stereo, 0, 2, n_bins as u32).unwrap();
629        assert!(
630            cap > i8::MAX as u32,
631            "expected at least one cap > i8::MAX (= 127); got {cap}"
632        );
633    }
634
635    // ---- §4.3.3 reachable-cells sanity pins ----
636    //
637    // The §4.3.3 band loop reaches every cell in the table because
638    // every (LM, stereo) row participates in the allocator at one
639    // frame size + channel-count combination. Pin two representative
640    // headline cases.
641
642    #[test]
643    fn celt_only_20ms_stereo_band0_pins_expected_cap() {
644        // (LM=3, stereo=Stereo, band=0): the first band of the 20 ms
645        // CELT-only stereo headline case.
646        let caps_value = cache_caps_value(3, CacheCapsStereo::Stereo, 0).unwrap();
647        assert_eq!(caps_value, 204);
648        let n_bins = celt_band_bins_per_channel(0, CeltFrameSize::Ms20).unwrap();
649        let cap = cap_for_band_bits(3, CacheCapsStereo::Stereo, 0, 2, n_bins as u32).unwrap();
650        // Cap formula: (204+64) * 2 * n_bins / 4 = 268 * 2 * n_bins / 4
651        //            = 134 * n_bins.
652        assert_eq!(cap, 134 * n_bins as u32);
653    }
654
655    #[test]
656    fn hybrid_band17_at_20ms_mono_pins_expected_cap() {
657        // Hybrid frames carve out bands 17..=20 for CELT; the first
658        // band of that carve-out at 20 ms mono is band 17 with
659        // caps[6][17] = 173.
660        let caps_value = cache_caps_value(3, CacheCapsStereo::Mono, 17).unwrap();
661        assert_eq!(caps_value, 173);
662        let n_bins = celt_band_bins_per_channel(17, CeltFrameSize::Ms20).unwrap();
663        let cap = cap_for_band_bits(3, CacheCapsStereo::Mono, 17, 1, n_bins as u32).unwrap();
664        // (173+64) * 1 * n_bins / 4 = 237 * n_bins / 4.
665        assert_eq!(cap, (237 * n_bins as u32) / 4);
666    }
667}