Skip to main content

oxideav_opus/
silk_frame.rs

1//! SILK per-frame header decoding — RFC 6716 §4.2.7.1 through §4.2.7.5.1.
2//!
3//! Each regular SILK frame begins with a fixed prefix of side-information
4//! symbols that drive the subsequent gain / LSF / LTP / excitation
5//! stages. This module parses that prefix:
6//!
7//! * §4.2.7.1 — Stereo prediction weights (mid-channel of a stereo frame
8//!   only). Three range-coded indices `n`, `(i0, i1)`, `(i2, i3)` are
9//!   combined into a pair of Q13 prediction weights `(w0_Q13, w1_Q13)`
10//!   per the formulas at the end of §4.2.7.1.
11//! * §4.2.7.2 — Mid-only flag (mid-channel of a stereo frame, when the
12//!   side channel is not otherwise required).
13//! * §4.2.7.3 — Frame-type symbol, which jointly carries the signal type
14//!   ([`SignalType`]) and the quantization-offset type
15//!   ([`QuantizationOffsetType`]) per Table 10.
16//! * §4.2.7.5.1 — Normalized LSF stage-1 codebook index `I1` (0..32),
17//!   PDF chosen from Table 14 by `(bandwidth, signal_type)`.
18//!
19//! All symbols are read from a [`RangeDecoder`] using the §4.1.3.3
20//! inverse-CDF primitive. The PDFs in Tables 6, 8, 9, and 14 are
21//! transcribed verbatim from RFC 6716.
22//!
23//! Higher-level SILK stages (subframe gains, LSF stage-2 residual, LTP
24//! parameters, LCG seed, excitation) are out of scope for round 4 — the
25//! goal here is to land the entry point onto the SILK frame body and
26//! the four structural decisions that everything downstream branches
27//! on.
28
29use crate::range_decoder::RangeDecoder;
30use crate::toc::Bandwidth;
31use crate::Error;
32
33/// Decoded stereo prediction weights for one mid-channel SILK frame
34/// (RFC 6716 §4.2.7.1).
35///
36/// Both weights are in Q13 fixed-point. By construction
37/// `w0_Q13, w1_Q13 ∈ [-13732 - 0.1*(13732 - 10050), 13732 + ...]`,
38/// i.e. roughly `[-14_100, +14_100]` after the interpolation step.
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub struct StereoPredictionWeights {
41    /// First weight, Q13. The decoded formula:
42    /// `w0 = w_Q13[wi0] + (((w_Q13[wi0+1] - w_Q13[wi0])*6554) >> 16)*(2*i1+1) - w1`.
43    pub w0_q13: i32,
44    /// Second weight, Q13. The decoded formula:
45    /// `w1 = w_Q13[wi1] + (((w_Q13[wi1+1] - w_Q13[wi1])*6554) >> 16)*(2*i3+1)`.
46    pub w1_q13: i32,
47}
48
49/// Signal type carried by the §4.2.7.3 frame-type symbol (Table 10).
50///
51/// Drives downstream LSF stage-1 PDF selection (§4.2.7.5.1) and the
52/// gain MSB PDF (§4.2.7.4).
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum SignalType {
55    /// Frame-type 0 or 1 (Table 10).
56    Inactive,
57    /// Frame-type 2 or 3 (Table 10).
58    Unvoiced,
59    /// Frame-type 4 or 5 (Table 10).
60    Voiced,
61}
62
63/// Quantization-offset type carried by the §4.2.7.3 frame-type symbol
64/// (Table 10).
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum QuantizationOffsetType {
67    /// Even frame-type values (0, 2, 4).
68    Low,
69    /// Odd frame-type values (1, 3, 5).
70    High,
71}
72
73/// Whether the current SILK frame is an LBRR frame or a regular SILK
74/// frame, and the VAD state of the corresponding time interval.
75///
76/// Drives which §4.2.7.3 PDF is used (Table 9) and whether the
77/// mid-only flag (§4.2.7.2) appears.
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum FrameKind {
80    /// Regular SILK frame whose VAD flag is unset for this time
81    /// interval. Frame-type symbol uses the "Inactive" PDF in Table 9.
82    /// Decoded value is always 0 or 1.
83    RegularInactive,
84    /// Regular SILK frame whose VAD flag is set for this time
85    /// interval. Frame-type symbol uses the "Active" PDF in Table 9.
86    /// Decoded value lies in `2..=5`.
87    RegularActive,
88    /// LBRR frame. Per §4.2.7.3, LBRR frames also use the "Active"
89    /// PDF, since every LBRR frame is itself an active-coded frame.
90    Lbrr,
91}
92
93/// Configuration for the mid-only flag (§4.2.7.2).
94///
95/// The mid-only flag is present only on a mid-channel SILK frame of a
96/// stereo Opus frame when the corresponding side channel is not
97/// otherwise required. The caller decides whether this applies and
98/// passes the result via [`SilkFrameHeaderConfig::has_mid_only_flag`].
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub struct SilkFrameHeaderConfig {
101    /// True if this is the mid-channel SILK frame of a stereo Opus
102    /// frame. The stereo prediction weights (§4.2.7.1) are decoded
103    /// only when this is true.
104    pub stereo_mid_channel: bool,
105    /// True if this is a stereo Opus frame at all. When the stereo
106    /// bit of the TOC byte is 0, neither the stereo prediction
107    /// weights nor the mid-only flag is present.
108    pub stereo: bool,
109    /// True if the §4.2.7.2 mid-only flag should be decoded. Per
110    /// §4.2.7.2, this happens when (a) we are on the mid channel of
111    /// a stereo Opus frame, AND (b) the side channel of this time
112    /// interval is not otherwise required (regular frame with side
113    /// VAD == 0, or LBRR frame with side LBRR == 0).
114    pub has_mid_only_flag: bool,
115    /// Frame kind for the current SILK frame (regular vs LBRR; if
116    /// regular, the VAD state). Drives the §4.2.7.3 PDF selection.
117    pub kind: FrameKind,
118    /// Audio bandwidth of the SILK signal. Drives the §4.2.7.5.1 PDF
119    /// selection (NB / MB share a row in Table 14, WB has its own).
120    /// SWB and FB SILK do not exist; the caller passes the SILK-layer
121    /// bandwidth post-§4.2.2 split.
122    pub bandwidth: Bandwidth,
123}
124
125/// SILK frame header — the prefix of side-information that drives the
126/// rest of the SILK frame decoder. RFC 6716 §4.2.7.1 through §4.2.7.5.1.
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub struct SilkFrameHeader {
129    /// Stereo prediction weights (§4.2.7.1) if this is a mid-channel
130    /// SILK frame of a stereo Opus frame; `None` otherwise.
131    pub stereo_pred: Option<StereoPredictionWeights>,
132    /// Mid-only flag (§4.2.7.2): `Some(true)` means the side channel
133    /// of this time interval is skipped; `Some(false)` means the side
134    /// channel is coded normally; `None` means the flag was not
135    /// present.
136    pub mid_only_flag: Option<bool>,
137    /// Raw §4.2.7.3 frame-type symbol value in `0..=5` (per Table 10).
138    pub frame_type: u8,
139    /// Decoded signal type (§4.2.7.3, Table 10).
140    pub signal_type: SignalType,
141    /// Decoded quantization-offset type (§4.2.7.3, Table 10).
142    pub qoff_type: QuantizationOffsetType,
143    /// Normalized LSF stage-1 codebook index `I1` (§4.2.7.5.1), in
144    /// `0..32`.
145    pub lsf_stage1: u8,
146}
147
148/// Table 6 stage-1 PDF (25 symbols) — `silk_stereo_pred_joint_iCDF`
149/// equivalent expressed as an inverse-CDF for the §4.1.3.3 primitive.
150///
151/// The PDF as stated in RFC 6716 Table 6:
152///
153/// ```text
154/// {7, 2, 1, 1, 1, 10, 24, 8, 1, 1, 3, 23, 92, 23, 3, 1, 1,
155///  8, 24, 10, 1, 1, 1, 2, 7}/256
156/// ```
157///
158/// Cumulative `fh[k]` running sum:
159/// `[7,9,10,11,12,22,46,54,55,56,59,82,174,197,200,201,202,210,234,
160///   244,245,246,247,249,256]`.
161/// `icdf[k] = 256 - fh[k]`, terminated by 0:
162const STEREO_STAGE1_ICDF: &[u8] = &[
163    249, 247, 246, 245, 244, 234, 210, 202, 201, 200, 197, 174, 82, 59, 56, 55, 54, 46, 22, 12, 11,
164    10, 9, 7, 0,
165];
166
167/// Table 6 stage-2 PDF — `{85, 86, 85}/256`. Cumulative `fh = [85, 171,
168/// 256]`. `icdf = [171, 85, 0]`.
169const STEREO_STAGE2_ICDF: &[u8] = &[171, 85, 0];
170
171/// Table 6 stage-3 PDF — `{51, 51, 52, 51, 51}/256`. Cumulative
172/// `fh = [51, 102, 154, 205, 256]`. `icdf = [205, 154, 102, 51, 0]`.
173const STEREO_STAGE3_ICDF: &[u8] = &[205, 154, 102, 51, 0];
174
175/// Table 7 — 16-entry weight table indexed by `wi0` / `wi1 + 1` in the
176/// `w0_Q13` / `w1_Q13` computation of §4.2.7.1.
177///
178/// Last entry is included even though `wi*` only ranges over 0..=14, so
179/// that the linear interpolation `w_Q13[wi+1] - w_Q13[wi]` is always
180/// defined.
181const STEREO_WEIGHT_Q13: [i32; 16] = [
182    -13732, -10050, -8266, -7526, -6500, -5000, -2950, -820, 820, 2950, 5000, 6500, 7526, 8266,
183    10050, 13732,
184];
185
186/// Table 8 mid-only flag PDF `{192, 64}/256`. Cumulative `fh = [192,
187/// 256]`. `icdf = [64, 0]`.
188const MID_ONLY_ICDF: &[u8] = &[64, 0];
189
190/// Table 9 inactive frame-type PDF `{26, 230, 0, 0, 0, 0}/256` — only
191/// indices 0 and 1 ever decode. Cumulative `fh = [26, 256]`. `icdf =
192/// [230, 0]`.
193const FRAME_TYPE_INACTIVE_ICDF: &[u8] = &[230, 0];
194
195/// Table 9 active frame-type PDF `{0, 0, 24, 74, 148, 10}/256` —
196/// indices 2..=5. Cumulative `fh = [0, 0, 24, 98, 246, 256]`. The
197/// §4.1.3.3 primitive needs the leading zero-mass cells to be
198/// representable; `icdf = [256, 256, 232, 158, 10, 0]` but 256 is not a
199/// valid `u8`. Instead, the §4.1.3.3 formulation handles the
200/// degenerate "this cell has probability zero" case naturally: the
201/// `s * icdf[k]` product equals `rng` when `icdf[k] == ft`, and
202/// `val < rng` always holds — so the search just falls through. We
203/// approximate the leading-256 entries with their wraparound `0u8`
204/// representation, since the §4.1.3.3 primitive compares `val >= next`
205/// where `next = s * icdf[k]`; for `icdf[k] = 0` this gives `next = 0`
206/// and the loop returns at this index. That is the WRONG behaviour
207/// for a leading zero-probability cell. The clean solution is to use
208/// a different `ftb` that excludes the zero-probability cells, which
209/// gives us a tight inverse-CDF: the active PDF "really" has support
210/// over {2, 3, 4, 5}, so transcribe it as a 4-entry table indexed
211/// `0..=3` and add the +2 offset in the caller. Cumulative
212/// `fh = [24, 98, 246, 256]`. `icdf = [232, 158, 10, 0]`.
213const FRAME_TYPE_ACTIVE_ICDF: &[u8] = &[232, 158, 10, 0];
214
215/// Table 14 LSF stage-1 PDF for NB/MB inactive-or-unvoiced. Sum of
216/// the 32 cells is 256 by construction. Build cumulative `fh` then
217/// `icdf = 256 - fh` with a trailing zero.
218const LSF_STAGE1_NB_MB_INACTIVE_PDF: [u8; 32] = [
219    44, 34, 30, 19, 21, 12, 11, 3, 3, 2, 16, 2, 2, 1, 5, 2, 1, 3, 3, 1, 1, 2, 2, 2, 3, 1, 9, 9, 2,
220    7, 2, 1,
221];
222
223/// Table 14 LSF stage-1 PDF for NB/MB voiced.
224const LSF_STAGE1_NB_MB_VOICED_PDF: [u8; 32] = [
225    1, 10, 1, 8, 3, 8, 8, 14, 13, 14, 1, 14, 12, 13, 11, 11, 12, 11, 10, 10, 11, 8, 9, 8, 7, 8, 1,
226    1, 6, 1, 6, 5,
227];
228
229/// Table 14 LSF stage-1 PDF for WB inactive-or-unvoiced.
230const LSF_STAGE1_WB_INACTIVE_PDF: [u8; 32] = [
231    31, 21, 3, 17, 1, 8, 17, 4, 1, 18, 16, 4, 2, 3, 1, 10, 1, 3, 16, 11, 16, 2, 2, 3, 2, 11, 1, 4,
232    9, 8, 7, 3,
233];
234
235/// Table 14 LSF stage-1 PDF for WB voiced.
236const LSF_STAGE1_WB_VOICED_PDF: [u8; 32] = [
237    1, 4, 16, 5, 18, 11, 5, 14, 15, 1, 3, 12, 13, 14, 14, 6, 14, 12, 2, 6, 1, 12, 12, 11, 10, 3,
238    10, 5, 1, 1, 1, 3,
239];
240
241/// Convert a 32-cell length-256 PDF into an iCDF (`256 - fh[k]`) with
242/// a trailing zero — the format consumed by [`RangeDecoder::dec_icdf`].
243const fn pdf_to_icdf32(pdf: &[u8; 32]) -> [u8; 33] {
244    let mut icdf = [0u8; 33];
245    let mut acc: u32 = 0;
246    let mut k = 0;
247    while k < 32 {
248        acc += pdf[k] as u32;
249        // `256 - acc` fits in u8 as long as acc <= 256, which it is
250        // for any well-formed Table-14 row (sum = 256).
251        icdf[k] = (256 - acc) as u8;
252        k += 1;
253    }
254    // trailing zero terminator
255    icdf[32] = 0;
256    icdf
257}
258
259/// LSF stage-1 iCDFs derived from the four PDF rows in Table 14.
260const LSF_STAGE1_ICDF_NB_MB_INACTIVE: [u8; 33] = pdf_to_icdf32(&LSF_STAGE1_NB_MB_INACTIVE_PDF);
261const LSF_STAGE1_ICDF_NB_MB_VOICED: [u8; 33] = pdf_to_icdf32(&LSF_STAGE1_NB_MB_VOICED_PDF);
262const LSF_STAGE1_ICDF_WB_INACTIVE: [u8; 33] = pdf_to_icdf32(&LSF_STAGE1_WB_INACTIVE_PDF);
263const LSF_STAGE1_ICDF_WB_VOICED: [u8; 33] = pdf_to_icdf32(&LSF_STAGE1_WB_VOICED_PDF);
264
265impl SilkFrameHeader {
266    /// Decode the §4.2.7.1–§4.2.7.5.1 header prefix from `rd`.
267    ///
268    /// The caller is responsible for telling us, via `cfg`, whether
269    /// the stereo prediction weights and the mid-only flag are
270    /// present, and whether the current frame is regular-inactive,
271    /// regular-active, or LBRR. The function does not consult the
272    /// §3.1 TOC byte or the §4.2.3/§4.2.4 packet-level header bits.
273    pub fn decode(rd: &mut RangeDecoder<'_>, cfg: SilkFrameHeaderConfig) -> Result<Self, Error> {
274        // -------- §4.2.7.1 Stereo Prediction Weights --------
275        let stereo_pred = if cfg.stereo && cfg.stereo_mid_channel {
276            Some(Self::decode_stereo_pred(rd))
277        } else {
278            None
279        };
280
281        // -------- §4.2.7.2 Mid-Only Flag --------
282        // Per §4.2.7.2 the flag is present iff (stereo Opus frame) AND
283        // (mid channel) AND (side channel not otherwise required). We
284        // gate strictly on the caller's `has_mid_only_flag` to keep
285        // the LBRR / VAD logic out of the SILK frame decoder.
286        let mid_only_flag = if cfg.has_mid_only_flag {
287            // Table 8: P(0) = 192/256 = 3/4, P(1) = 64/256 = 1/4.
288            // dec_icdf returns the symbol index; index 0 => flag = 0,
289            // index 1 => flag = 1 ("mid only").
290            let v = rd.dec_icdf(MID_ONLY_ICDF, 8);
291            Some(v == 1)
292        } else {
293            None
294        };
295
296        // -------- §4.2.7.3 Frame Type --------
297        let frame_type_raw = match cfg.kind {
298            FrameKind::RegularInactive => {
299                // Inactive PDF — only indices 0 and 1 ever decode.
300                rd.dec_icdf(FRAME_TYPE_INACTIVE_ICDF, 8) as u8
301            }
302            FrameKind::RegularActive | FrameKind::Lbrr => {
303                // Active PDF — indices 2..=5. We use a 4-entry iCDF
304                // covering the support and shift by +2.
305                let k = rd.dec_icdf(FRAME_TYPE_ACTIVE_ICDF, 8) as u8;
306                k + 2
307            }
308        };
309        if frame_type_raw > 5 {
310            // Should not happen for well-formed PDFs; defend anyway.
311            return Err(Error::MalformedPacket);
312        }
313        let (signal_type, qoff_type) = frame_type_to_signal_qoff(frame_type_raw);
314
315        // -------- §4.2.7.5.1 LSF Stage-1 --------
316        let lsf_icdf: &[u8] = match (cfg.bandwidth, signal_type) {
317            (Bandwidth::Nb | Bandwidth::Mb, SignalType::Inactive | SignalType::Unvoiced) => {
318                &LSF_STAGE1_ICDF_NB_MB_INACTIVE
319            }
320            (Bandwidth::Nb | Bandwidth::Mb, SignalType::Voiced) => &LSF_STAGE1_ICDF_NB_MB_VOICED,
321            (Bandwidth::Wb, SignalType::Inactive | SignalType::Unvoiced) => {
322                &LSF_STAGE1_ICDF_WB_INACTIVE
323            }
324            (Bandwidth::Wb, SignalType::Voiced) => &LSF_STAGE1_ICDF_WB_VOICED,
325            // §2 — SILK does not operate on SWB or FB. Hybrid mode
326            // splits the signal so that the SILK layer always sees
327            // NB / MB / WB only. Reject anything else.
328            _ => return Err(Error::MalformedPacket),
329        };
330        let lsf_stage1 = rd.dec_icdf(lsf_icdf, 8) as u8;
331        if lsf_stage1 >= 32 {
332            return Err(Error::MalformedPacket);
333        }
334
335        if rd.has_error() {
336            return Err(Error::MalformedPacket);
337        }
338
339        Ok(Self {
340            stereo_pred,
341            mid_only_flag,
342            frame_type: frame_type_raw,
343            signal_type,
344            qoff_type,
345            lsf_stage1,
346        })
347    }
348
349    /// Internal: decode the five sub-symbols of §4.2.7.1 (`n`, `i0`,
350    /// `i1`, `i2`, `i3`) and compose them into `(w0_Q13, w1_Q13)`.
351    ///
352    /// Reads order is exactly the one stated in §4.2.7.1: "let i0
353    /// and i1 be indices decoded with the stage-2 and stage-3 PDFs in
354    /// Table 6, respectively, and let i2 and i3 be two more indices
355    /// decoded with the stage-2 and stage-3 PDFs, all in that order."
356    fn decode_stereo_pred(rd: &mut RangeDecoder<'_>) -> StereoPredictionWeights {
357        let n = rd.dec_icdf(STEREO_STAGE1_ICDF, 8) as i32;
358        let i0 = rd.dec_icdf(STEREO_STAGE2_ICDF, 8) as i32;
359        let i1 = rd.dec_icdf(STEREO_STAGE3_ICDF, 8) as i32;
360        let i2 = rd.dec_icdf(STEREO_STAGE2_ICDF, 8) as i32;
361        let i3 = rd.dec_icdf(STEREO_STAGE3_ICDF, 8) as i32;
362
363        // §4.2.7.1: wi0 = i0 + 3*(n/5), wi1 = i2 + 3*(n%5); both fall
364        // in 0..=14.
365        let wi0 = (i0 + 3 * (n / 5)) as usize;
366        let wi1 = (i2 + 3 * (n % 5)) as usize;
367        // Defensive clamp: the spec guarantees wi* <= 14 for any
368        // (n, i0, i2) tuple, but we still saturate to keep the
369        // STEREO_WEIGHT_Q13[wi+1] lookup in-bounds even on a
370        // pathologically corrupt frame.
371        let wi0 = wi0.min(14);
372        let wi1 = wi1.min(14);
373
374        // w1 first (w0 depends on w1):
375        //   w1 = w_Q13[wi1] + (((w_Q13[wi1+1] - w_Q13[wi1])*6554) >> 16)*(2*i3+1)
376        let step1: i32 =
377            (((STEREO_WEIGHT_Q13[wi1 + 1] - STEREO_WEIGHT_Q13[wi1]) * 6554) >> 16) * (2 * i3 + 1);
378        let w1_q13 = STEREO_WEIGHT_Q13[wi1] + step1;
379        //   w0 = w_Q13[wi0] + (((w_Q13[wi0+1] - w_Q13[wi0])*6554) >> 16)*(2*i1+1) - w1
380        let step0: i32 =
381            (((STEREO_WEIGHT_Q13[wi0 + 1] - STEREO_WEIGHT_Q13[wi0]) * 6554) >> 16) * (2 * i1 + 1);
382        let w0_q13 = STEREO_WEIGHT_Q13[wi0] + step0 - w1_q13;
383        StereoPredictionWeights { w0_q13, w1_q13 }
384    }
385}
386
387/// Map a frame-type symbol (0..=5) to `(signal_type, qoff_type)` per
388/// RFC 6716 §4.2.7.3 Table 10.
389fn frame_type_to_signal_qoff(frame_type: u8) -> (SignalType, QuantizationOffsetType) {
390    let signal = match frame_type {
391        0 | 1 => SignalType::Inactive,
392        2 | 3 => SignalType::Unvoiced,
393        4 | 5 => SignalType::Voiced,
394        _ => SignalType::Inactive, // unreachable in practice; defensive
395    };
396    let qoff = if frame_type % 2 == 0 {
397        QuantizationOffsetType::Low
398    } else {
399        QuantizationOffsetType::High
400    };
401    (signal, qoff)
402}
403
404#[cfg(test)]
405mod tests {
406    use super::*;
407
408    // --- Table 6 / 7 / 8 / 9 / 14 PDF→iCDF transcription self-checks.
409    //
410    // These tests do not exercise the range decoder; they confirm
411    // the constant tables match the RFC by checking that each PDF row
412    // sums to 256 and that consecutive iCDF cells are strictly
413    // monotonically decreasing (a §4.1.3.3 precondition).
414
415    #[test]
416    fn stereo_stage1_pdf_sums_to_256() {
417        let pdf = [
418            7, 2, 1, 1, 1, 10, 24, 8, 1, 1, 3, 23, 92, 23, 3, 1, 1, 8, 24, 10, 1, 1, 1, 2, 7,
419        ];
420        let sum: u32 = pdf.iter().sum();
421        assert_eq!(sum, 256);
422        assert_eq!(STEREO_STAGE1_ICDF.len(), pdf.len());
423        // iCDF strictly monotone decreasing then terminator zero.
424        for w in STEREO_STAGE1_ICDF.windows(2) {
425            assert!(w[0] > w[1] || (w[0] == 0 && w[1] == 0));
426        }
427        assert_eq!(*STEREO_STAGE1_ICDF.last().unwrap(), 0);
428    }
429
430    #[test]
431    fn stereo_stage2_pdf_self_check() {
432        assert_eq!(STEREO_STAGE2_ICDF, &[171u8, 85, 0]);
433        assert_eq!(STEREO_STAGE3_ICDF, &[205u8, 154, 102, 51, 0]);
434    }
435
436    #[test]
437    fn mid_only_pdf_self_check() {
438        assert_eq!(MID_ONLY_ICDF, &[64u8, 0]);
439    }
440
441    #[test]
442    fn lsf_stage1_nb_mb_inactive_sums_to_256() {
443        let s: u32 = LSF_STAGE1_NB_MB_INACTIVE_PDF
444            .iter()
445            .map(|&x| x as u32)
446            .sum();
447        assert_eq!(s, 256);
448    }
449
450    #[test]
451    fn lsf_stage1_nb_mb_voiced_sums_to_256() {
452        let s: u32 = LSF_STAGE1_NB_MB_VOICED_PDF.iter().map(|&x| x as u32).sum();
453        assert_eq!(s, 256);
454    }
455
456    #[test]
457    fn lsf_stage1_wb_inactive_sums_to_256() {
458        let s: u32 = LSF_STAGE1_WB_INACTIVE_PDF.iter().map(|&x| x as u32).sum();
459        assert_eq!(s, 256);
460    }
461
462    #[test]
463    fn lsf_stage1_wb_voiced_sums_to_256() {
464        let s: u32 = LSF_STAGE1_WB_VOICED_PDF.iter().map(|&x| x as u32).sum();
465        assert_eq!(s, 256);
466    }
467
468    #[test]
469    fn lsf_stage1_icdf_terminator_is_zero() {
470        for icdf in [
471            &LSF_STAGE1_ICDF_NB_MB_INACTIVE,
472            &LSF_STAGE1_ICDF_NB_MB_VOICED,
473            &LSF_STAGE1_ICDF_WB_INACTIVE,
474            &LSF_STAGE1_ICDF_WB_VOICED,
475        ] {
476            assert_eq!(icdf[32], 0, "iCDF must terminate with zero");
477            assert_eq!(icdf.len(), 33);
478            // Strictly decreasing.
479            for w in icdf.windows(2) {
480                assert!(
481                    w[0] >= w[1],
482                    "iCDF must be monotone non-increasing: {:?} -> {:?}",
483                    w[0],
484                    w[1]
485                );
486            }
487        }
488    }
489
490    #[test]
491    fn stereo_weight_table_is_symmetric() {
492        // Table 7 is symmetric around the middle: w[15-k] == -w[k]
493        // for k in 0..=7.
494        for k in 0..8 {
495            assert_eq!(STEREO_WEIGHT_Q13[15 - k], -STEREO_WEIGHT_Q13[k]);
496        }
497        assert_eq!(STEREO_WEIGHT_Q13[0], -13732);
498        assert_eq!(STEREO_WEIGHT_Q13[15], 13732);
499    }
500
501    // --- Table 10 frame-type mapping --------
502
503    #[test]
504    fn frame_type_to_signal_qoff_table10() {
505        let expected = [
506            (0, SignalType::Inactive, QuantizationOffsetType::Low),
507            (1, SignalType::Inactive, QuantizationOffsetType::High),
508            (2, SignalType::Unvoiced, QuantizationOffsetType::Low),
509            (3, SignalType::Unvoiced, QuantizationOffsetType::High),
510            (4, SignalType::Voiced, QuantizationOffsetType::Low),
511            (5, SignalType::Voiced, QuantizationOffsetType::High),
512        ];
513        for (ft, sig, q) in expected {
514            assert_eq!(frame_type_to_signal_qoff(ft), (sig, q));
515        }
516    }
517
518    // --- End-to-end: decode against a hand-crafted RangeDecoder.
519    //
520    // We can't easily construct an arbitrary byte sequence that
521    // produces a specific symbol pattern without an encoder, but we
522    // CAN check round-trip behaviour: every decoded value must
523    // satisfy the spec's range bounds, and the function must not
524    // latch the corrupt-frame flag for a non-corrupt input.
525
526    fn mono_inactive_cfg(bw: Bandwidth) -> SilkFrameHeaderConfig {
527        SilkFrameHeaderConfig {
528            stereo_mid_channel: false,
529            stereo: false,
530            has_mid_only_flag: false,
531            kind: FrameKind::RegularInactive,
532            bandwidth: bw,
533        }
534    }
535
536    fn mono_active_cfg(bw: Bandwidth) -> SilkFrameHeaderConfig {
537        SilkFrameHeaderConfig {
538            stereo_mid_channel: false,
539            stereo: false,
540            has_mid_only_flag: false,
541            kind: FrameKind::RegularActive,
542            bandwidth: bw,
543        }
544    }
545
546    fn stereo_mid_active_cfg(bw: Bandwidth) -> SilkFrameHeaderConfig {
547        SilkFrameHeaderConfig {
548            stereo_mid_channel: true,
549            stereo: true,
550            has_mid_only_flag: true,
551            kind: FrameKind::RegularActive,
552            bandwidth: bw,
553        }
554    }
555
556    #[test]
557    fn mono_inactive_nb_decode_basic() {
558        // A long-enough buffer so the range decoder doesn't immediately
559        // start zero-extending past EOF.
560        let buf = [
561            0x55, 0xAA, 0x33, 0xCC, 0x7F, 0x80, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0,
562            0x12, 0x34,
563        ];
564        let mut rd = RangeDecoder::new(&buf);
565        let hdr = SilkFrameHeader::decode(&mut rd, mono_inactive_cfg(Bandwidth::Nb))
566            .expect("decode must succeed");
567        // No stereo content.
568        assert!(hdr.stereo_pred.is_none());
569        assert!(hdr.mid_only_flag.is_none());
570        // Inactive frame: ft must be 0 or 1.
571        assert!(hdr.frame_type <= 1, "ft={}", hdr.frame_type);
572        assert_eq!(hdr.signal_type, SignalType::Inactive);
573        assert!(hdr.lsf_stage1 < 32);
574    }
575
576    #[test]
577    fn mono_active_wb_decode_basic() {
578        let buf = [
579            0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
580            0x77, 0x88,
581        ];
582        let mut rd = RangeDecoder::new(&buf);
583        let hdr = SilkFrameHeader::decode(&mut rd, mono_active_cfg(Bandwidth::Wb))
584            .expect("decode must succeed");
585        assert!(hdr.stereo_pred.is_none());
586        assert!(hdr.mid_only_flag.is_none());
587        // Active frame: ft must be 2, 3, 4, or 5.
588        assert!((2..=5).contains(&hdr.frame_type), "ft={}", hdr.frame_type);
589        assert!(matches!(
590            hdr.signal_type,
591            SignalType::Unvoiced | SignalType::Voiced
592        ));
593        assert!(hdr.lsf_stage1 < 32);
594    }
595
596    #[test]
597    fn stereo_mid_active_includes_pred_and_mid_only() {
598        let buf = [
599            0xC3, 0x18, 0x42, 0x7F, 0x55, 0xAA, 0x33, 0xCC, 0x77, 0x33, 0x11, 0xAA, 0xDE, 0xAD,
600            0xBE, 0xEF, 0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE,
601        ];
602        let mut rd = RangeDecoder::new(&buf);
603        let hdr = SilkFrameHeader::decode(&mut rd, stereo_mid_active_cfg(Bandwidth::Mb))
604            .expect("decode must succeed");
605        let pred = hdr.stereo_pred.expect("stereo prediction must be present");
606        // w1 is one interpolated Table-7 entry (~±14_100 after the
607        // §4.2.7.1 interpolation step). w0 then subtracts w1 from
608        // another interpolated entry, so |w0| can reach ~ 28_000.
609        assert!(
610            (-30_000..=30_000).contains(&pred.w0_q13),
611            "w0={}",
612            pred.w0_q13
613        );
614        assert!(
615            (-15_000..=15_000).contains(&pred.w1_q13),
616            "w1={}",
617            pred.w1_q13
618        );
619        assert!(hdr.mid_only_flag.is_some());
620        assert!((2..=5).contains(&hdr.frame_type), "ft={}", hdr.frame_type);
621        assert!(hdr.lsf_stage1 < 32);
622    }
623
624    #[test]
625    fn stereo_side_no_prediction() {
626        // Side-channel SILK frame: NOT mid channel, so no stereo pred
627        // weights and no mid-only flag.
628        let cfg = SilkFrameHeaderConfig {
629            stereo_mid_channel: false,
630            stereo: true,
631            has_mid_only_flag: false,
632            kind: FrameKind::RegularActive,
633            bandwidth: Bandwidth::Wb,
634        };
635        let buf = [
636            0x37, 0x91, 0xC4, 0x18, 0xA2, 0x5D, 0x6E, 0xFF, 0x77, 0x33, 0x11, 0xAA,
637        ];
638        let mut rd = RangeDecoder::new(&buf);
639        let hdr = SilkFrameHeader::decode(&mut rd, cfg).expect("decode must succeed");
640        assert!(hdr.stereo_pred.is_none());
641        assert!(hdr.mid_only_flag.is_none());
642    }
643
644    #[test]
645    fn lbrr_frame_uses_active_pdf() {
646        // LBRR frames decode the frame-type symbol from the "Active"
647        // PDF irrespective of the (regular) VAD state.
648        let cfg = SilkFrameHeaderConfig {
649            stereo_mid_channel: false,
650            stereo: false,
651            has_mid_only_flag: false,
652            kind: FrameKind::Lbrr,
653            bandwidth: Bandwidth::Nb,
654        };
655        let buf = [
656            0x55, 0xAA, 0x33, 0xCC, 0x7F, 0x80, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC,
657        ];
658        let mut rd = RangeDecoder::new(&buf);
659        let hdr = SilkFrameHeader::decode(&mut rd, cfg).expect("decode must succeed");
660        assert!(
661            (2..=5).contains(&hdr.frame_type),
662            "lbrr ft must be active: {}",
663            hdr.frame_type
664        );
665    }
666
667    #[test]
668    fn pdf_to_icdf_terminates_and_decreases() {
669        // The const helper must produce a strictly-decreasing iCDF
670        // with a trailing zero, for any well-formed length-32
671        // length-256-sum PDF.
672        let pdf = [8u8; 32]; // uniform 32-way: sum = 256.
673        let icdf = pdf_to_icdf32(&pdf);
674        assert_eq!(icdf[32], 0);
675        for w in icdf.windows(2) {
676            assert!(w[0] >= w[1]);
677        }
678        // 256 - 8 = 248 (first cumulative-fh subtraction).
679        assert_eq!(icdf[0], 248);
680        // After all 32 cells: 256 - 32*8 = 0; uniform PDF sums to ft.
681        assert_eq!(icdf[31], 0);
682    }
683
684    #[test]
685    fn stereo_pred_wi_clamped_in_bounds() {
686        // Even in the pathological case where rd.has_error() is set,
687        // the wi0/wi1 clamps in decode_stereo_pred ensure we never
688        // index past STEREO_WEIGHT_Q13[15]. We can't directly inject
689        // n=24, but the clamp `.min(14)` is exercised by inspection;
690        // exercise it indirectly by running many random buffers.
691        for seed in 0..32u8 {
692            let buf = [
693                seed,
694                seed.wrapping_mul(3),
695                seed.wrapping_add(7),
696                seed ^ 0xA5,
697                seed.wrapping_mul(11),
698                seed.wrapping_add(13),
699                seed ^ 0x5A,
700                seed.wrapping_mul(17),
701                seed.wrapping_add(19),
702                seed ^ 0xC3,
703                seed.wrapping_mul(23),
704                seed.wrapping_add(29),
705                seed ^ 0x3C,
706                seed.wrapping_mul(31),
707                seed.wrapping_add(37),
708                seed ^ 0x55,
709            ];
710            let mut rd = RangeDecoder::new(&buf);
711            let pred = SilkFrameHeader::decode_stereo_pred(&mut rd);
712            // w1 is one interpolated table entry (~±14k); w0 is one
713            // interpolated entry MINUS w1 (~±28k worst case).
714            assert!(
715                (-30_000..=30_000).contains(&pred.w0_q13),
716                "seed={seed}, w0={}",
717                pred.w0_q13
718            );
719            assert!(
720                (-15_000..=15_000).contains(&pred.w1_q13),
721                "seed={seed}, w1={}",
722                pred.w1_q13
723            );
724        }
725    }
726}