Skip to main content

oxideav_opus/
silk_decode.rs

1//! In-order SILK frame decode — RFC 6716 §4.2.6 / §4.2.7 (Table 5).
2//!
3//! This module composes the individually-tested per-stage SILK decoders
4//! (`silk_frame`, `silk_gains`, `silk_lsf_*`, `silk_ltp`,
5//! `silk_lcg_seed`, `silk_excitation`) into a single
6//! [`decode_silk_frame`] call that reads one regular SILK frame's
7//! bitstream in the **exact Table-5 symbol order**:
8//!
9//! 1. §4.2.7.1 stereo prediction weights (mid channel of a stereo Opus
10//!    frame only),
11//! 2. §4.2.7.2 mid-only flag (conditional),
12//! 3. §4.2.7.3 frame type,
13//! 4. §4.2.7.4 subframe gains,
14//! 5. §4.2.7.5.1 normalized LSF stage-1 index,
15//! 6. §4.2.7.5.2 normalized LSF stage-2 residual,
16//! 7. §4.2.7.5.5 LSF interpolation weight (20 ms frame only),
17//! 8. §4.2.7.6 LTP lags + gains + scaling (voiced frame only),
18//! 9. §4.2.7.7 LCG seed,
19//! 10. §4.2.7.8 quantized excitation.
20//!
21//! The critical correctness property is that the §4.2.7.4 gains are read
22//! *between* the frame type (step 3) and the LSF stage-1 index (step 5),
23//! exactly as Table 5 places them. The convenience
24//! [`crate::silk_frame::SilkFrameHeader::decode`] reads steps 1–3 and 5
25//! back-to-back (no gains in between) and is therefore unsuitable for a
26//! full-frame decode; this module uses the composable
27//! [`crate::silk_frame::SilkFrameHeader::decode_pre_gains`] (steps 1–3)
28//! and [`crate::silk_frame::SilkFrameHeader::decode_lsf_stage1`] (step 5)
29//! entries with the gains read in between.
30//!
31//! After the bitstream is consumed, the module runs the *non-bitstream*
32//! §4.2.7.5.3–§4.2.7.5.8 LSF → LPC reconstruction chain (codebook lookup
33//! → stabilization → interpolation → NLSF→LPC → bandwidth expansion →
34//! prediction-gain limiting) so the returned [`SilkFrameDecoded`] carries
35//! the final stable Q12 LPC coefficients ready for the §4.2.7.9 synthesis
36//! filters, alongside the LTP parameters and the Q23 excitation.
37//!
38//! ## Scope of this round
39//!
40//! This module produces a fully decoded *parameter set + excitation* for
41//! one regular SILK frame: every symbol of the frame's bitstream is
42//! consumed in Table-5 order, and the LSF → LPC transform is run. The
43//! §4.2.7.9 LTP / LPC synthesis filters (which turn the excitation +
44//! filters into time-domain samples) and the §4.2.9 resample to 48 kHz
45//! are composed in a follow-up; [`SilkFrameDecoded`] is the stable
46//! hand-off point between the bitstream-consuming front half and the
47//! signal-reconstructing back half.
48//!
49//! The current entry decodes a **mono** regular SILK frame (no stereo
50//! prediction weights / mid-only flag). The stereo mid/side interleave
51//! (§4.2.6) reuses the same per-frame decode with the §4.2.7.1 / §4.2.7.2
52//! symbols enabled and is wired once the stereo unmixing back half lands.
53
54use crate::range_decoder::RangeDecoder;
55use crate::range_encoder::RangeEncoder;
56use crate::silk_excitation::{Excitation, ExcitationConfig, ExcitationSymbols, SilkFrameSize};
57use crate::silk_frame::{
58    FrameKind, QuantizationOffsetType, SignalType, SilkFrameHeader, SilkFrameHeaderConfig,
59    SilkHeaderSymbols,
60};
61use crate::silk_gains::{GainSymbol, SubframeGains, SubframeGainsConfig};
62use crate::silk_lcg_seed::{decode_lcg_seed, encode_lcg_seed};
63use crate::silk_lsf_interp::{LsfInterpContext, LsfInterpolated};
64use crate::silk_lsf_recon::NlsfReconstructed;
65use crate::silk_lsf_stabilize::NlsfStabilized;
66use crate::silk_lsf_stage2::LsfStage2;
67use crate::silk_lsf_to_lpc::LpcQ12;
68use crate::silk_ltp::{LagCoding, LtpConfig, LtpParameters, LtpSymbols};
69use crate::toc::Bandwidth;
70use crate::Error;
71
72/// Configuration for one regular SILK frame decode, supplying the
73/// per-frame conditions that the §4.2 packet organisation determines
74/// outside the SILK frame itself (the §4.2.4 VAD flag, the §4.2.7.4
75/// independent-gain enumeration, the §4.2.7.6.1 relative-lag base, and
76/// the §4.2.7.6.3 LTP-scaling-present enumeration).
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub struct SilkFrameConfig {
79    /// Internal SILK bandwidth (NB / MB / WB). SWB / FB are rejected.
80    pub bandwidth: Bandwidth,
81    /// SILK frame duration: 10 ms (2 subframes) or 20 ms (4 subframes).
82    pub frame_size: SilkFrameSize,
83    /// §4.2.4 voice-activity flag for this frame's time interval. Selects
84    /// the §4.2.7.3 frame-type PDF (active vs inactive).
85    pub voice_active: bool,
86    /// §4.2.7.4: whether the first subframe gain is coded independently
87    /// (first SILK frame of its type for this channel in the Opus frame,
88    /// or the previous SILK frame of the same type was not coded).
89    pub first_subframe_independent: bool,
90    /// §4.2.7.4 clamp base: the previous SILK frame's last subframe
91    /// `log_gain` for this channel, or `None` after a reset / uncoded
92    /// previous frame (the clamp is then skipped).
93    pub previous_log_gain: Option<u8>,
94    /// §4.2.7.6.1: how the primary pitch lag is coded. `None` defaults to
95    /// absolute coding; `Some(prev)` enables relative coding against the
96    /// previous frame's primary lag.
97    pub previous_primary_lag: Option<i32>,
98    /// §4.2.7.6.3: whether the LTP scaling factor is present in the
99    /// bitstream (first time interval of the Opus frame for its type, or
100    /// an LBRR frame whose prior LBRR frame is not coded).
101    pub ltp_scaling_present: bool,
102    /// §4.2.7.5.5 interpolation context for a 20 ms frame: `true` after a
103    /// decoder reset / uncoded previous frame (the decoded factor is
104    /// discarded and `4` is used). Ignored for a 10 ms frame.
105    pub lsf_interp_after_reset: bool,
106    /// §4.2.7.5.5: the previous coded frame's stabilized NLSF vector
107    /// (`n0_Q15[]`), used as the interpolation base for a 20 ms frame.
108    /// `None` after a reset (the `4` factor is used so `n1 == n2`).
109    pub previous_nlsf_q15: Option<[i16; crate::silk_lsf_stage2::D_LPC_MAX]>,
110    /// Length of the populated prefix of [`Self::previous_nlsf_q15`]
111    /// (the `d_LPC` of the previous frame: 10 for NB/MB, 16 for WB).
112    pub previous_nlsf_len: usize,
113    /// §4.2.7.1 / §4.2.7.2 stereo header context for the **mid channel**
114    /// of a stereo Opus frame. `None` for a mono frame (no stereo
115    /// prediction weights, no mid-only flag). When `Some`, the front-half
116    /// decode reads the §4.2.7.1 prediction weights first and, when
117    /// [`StereoHeaderContext::has_mid_only_flag`] is set, the §4.2.7.2
118    /// mid-only flag, both in Table-5 order ahead of the frame type. The
119    /// decoded values are returned in [`SilkFrameDecoded::stereo_pred`] /
120    /// [`SilkFrameDecoded::mid_only_flag`].
121    pub stereo: Option<StereoHeaderContext>,
122}
123
124/// §4.2.7.1 / §4.2.7.2 stereo header context for the mid channel of a
125/// stereo Opus frame, supplied to [`decode_silk_frame`] via
126/// [`SilkFrameConfig::stereo`].
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub struct StereoHeaderContext {
129    /// Whether the §4.2.7.2 mid-only flag is present for this time
130    /// interval. Per §4.2.7.2 the flag appears iff the side channel of
131    /// this interval is not otherwise required (regular frame with side
132    /// VAD == 0, or LBRR frame with side LBRR == 0). The §4.2 packet
133    /// walker determines this and passes the boolean.
134    pub has_mid_only_flag: bool,
135}
136
137/// One fully decoded regular SILK frame: every Table-5 bitstream symbol
138/// consumed and the §4.2.7.5 LSF → LPC chain run.
139#[derive(Debug, Clone, PartialEq)]
140pub struct SilkFrameDecoded {
141    /// §4.2.7.3 signal type.
142    pub signal_type: SignalType,
143    /// §4.2.7.3 quantization-offset type.
144    pub qoff_type: QuantizationOffsetType,
145    /// §4.2.7.4 per-subframe quantization gains (`log_gain ∈ 0..=63`).
146    pub gains: SubframeGains,
147    /// §4.2.7.5.1 normalized LSF stage-1 index `I1 ∈ 0..32`.
148    pub lsf_stage1: u8,
149    /// The §4.2.7.5.4 stabilized normalized-LSF vector for the *current*
150    /// frame (`n2_Q15[]`), carried forward as the next frame's §4.2.7.5.5
151    /// interpolation base. Only `0..d_lpc` entries are valid.
152    pub nlsf_q15: [i16; crate::silk_lsf_stage2::D_LPC_MAX],
153    /// `d_LPC` (length of [`Self::nlsf_q15`]): 10 for NB/MB, 16 for WB.
154    pub d_lpc: usize,
155    /// §4.2.7.5.5 interpolation factor `w_Q2 ∈ 0..=4` for a 20 ms frame;
156    /// `None` for a 10 ms frame (no first-half split).
157    pub lsf_interp_q2: Option<u8>,
158    /// The final stable §4.2.7.5.8 Q12 LPC filter for the *second half*
159    /// of the frame (derived from the stabilized current-frame NLSF).
160    pub lpc_second_half: LpcQ12,
161    /// The final stable §4.2.7.5.8 Q12 LPC filter for the *first half* of
162    /// a 20 ms frame (derived from the interpolated `n1_Q15[]`); `None`
163    /// for a 10 ms frame, which uses [`Self::lpc_second_half`] throughout.
164    pub lpc_first_half: Option<LpcQ12>,
165    /// §4.2.7.6 LTP parameters (voiced frames only; empty otherwise).
166    pub ltp: LtpParameters,
167    /// §4.2.7.7 LCG seed `0..=3`.
168    pub lcg_seed: u8,
169    /// §4.2.7.8 quantized excitation `e_Q23[]`.
170    pub excitation: Excitation,
171    /// §4.2.7.1 decoded stereo prediction weights (Q13), present only on
172    /// the mid channel of a stereo Opus frame (when
173    /// [`SilkFrameConfig::stereo`] is `Some`); `None` for a mono frame.
174    pub stereo_pred: Option<crate::silk_frame::StereoPredictionWeights>,
175    /// §4.2.7.2 decoded mid-only flag, present only when the stereo
176    /// context had [`StereoHeaderContext::has_mid_only_flag`] set;
177    /// `Some(true)` means the side channel of this interval is skipped.
178    pub mid_only_flag: Option<bool>,
179}
180
181/// Decode one regular **mono** SILK frame from `rd`, reading every
182/// Table-5 bitstream symbol in order and running the §4.2.7.5 LSF → LPC
183/// reconstruction.
184///
185/// Returns [`Error::MalformedPacket`] if any stage rejects (an
186/// out-of-range symbol, an SWB / FB bandwidth, a mismatched length, or a
187/// latched range-coder error).
188pub fn decode_silk_frame(
189    rd: &mut RangeDecoder<'_>,
190    cfg: SilkFrameConfig,
191) -> Result<SilkFrameDecoded, Error> {
192    let num_subframes: u8 = match cfg.frame_size {
193        SilkFrameSize::TenMs => 2,
194        SilkFrameSize::TwentyMs => 4,
195    };
196
197    // ---- Steps 1-3: §4.2.7.1 / §4.2.7.2 / §4.2.7.3. For a mono frame no
198    // stereo weights / mid-only flag are read; for the mid channel of a
199    // stereo Opus frame the §4.2.7.1 weights (and, when signalled, the
200    // §4.2.7.2 mid-only flag) precede the frame type in Table-5 order. ----
201    let header_cfg = SilkFrameHeaderConfig {
202        stereo_mid_channel: cfg.stereo.is_some(),
203        stereo: cfg.stereo.is_some(),
204        has_mid_only_flag: cfg.stereo.is_some_and(|s| s.has_mid_only_flag),
205        kind: if cfg.voice_active {
206            FrameKind::RegularActive
207        } else {
208            FrameKind::RegularInactive
209        },
210        bandwidth: cfg.bandwidth,
211    };
212    let pre = SilkFrameHeader::decode_pre_gains(rd, header_cfg)?;
213    let stereo_pred = pre.stereo_pred;
214    let mid_only_flag = pre.mid_only_flag;
215
216    // ---- Step 4: §4.2.7.4 subframe gains. ----
217    let gains = SubframeGains::decode(
218        rd,
219        SubframeGainsConfig {
220            signal_type: pre.signal_type,
221            num_subframes,
222            first_subframe_is_independent: cfg.first_subframe_independent,
223            previous_log_gain: cfg.previous_log_gain,
224        },
225    )?;
226
227    // ---- Step 5: §4.2.7.5.1 LSF stage-1 index. ----
228    let lsf_stage1 = SilkFrameHeader::decode_lsf_stage1(rd, cfg.bandwidth, pre.signal_type)?;
229
230    // ---- Step 6: §4.2.7.5.2 LSF stage-2 residual. ----
231    let stage2 = LsfStage2::decode(rd, cfg.bandwidth, lsf_stage1)?;
232
233    // §4.2.7.5.3 / §4.2.7.5.4 (non-bitstream): reconstruct + stabilize
234    // the current-frame normalized LSF vector.
235    let recon = NlsfReconstructed::from_stage1_and_stage2(cfg.bandwidth, lsf_stage1, &stage2)?;
236    let stabilized = NlsfStabilized::from_reconstructed(cfg.bandwidth, &recon)?;
237    let d_lpc = stabilized.len();
238    let mut nlsf_q15 = [0i16; crate::silk_lsf_stage2::D_LPC_MAX];
239    nlsf_q15[..d_lpc].copy_from_slice(stabilized.nlsf_q15());
240
241    // ---- Step 7: §4.2.7.5.5 LSF interpolation weight (20 ms only). ----
242    let interp_context = match cfg.frame_size {
243        SilkFrameSize::TenMs => LsfInterpContext::TenMs,
244        SilkFrameSize::TwentyMs => {
245            if cfg.lsf_interp_after_reset || cfg.previous_nlsf_q15.is_none() {
246                LsfInterpContext::TwentyMsAfterResetOrUncoded
247            } else {
248                LsfInterpContext::TwentyMs
249            }
250        }
251    };
252    let n0_slice: Option<&[i16]> = match (&cfg.previous_nlsf_q15, cfg.frame_size) {
253        (Some(prev), SilkFrameSize::TwentyMs) if cfg.previous_nlsf_len == d_lpc => {
254            Some(&prev[..d_lpc])
255        }
256        _ => None,
257    };
258    let interp = LsfInterpolated::decode(rd, &stabilized, n0_slice, interp_context)?;
259    let lsf_interp_q2 = interp.w_q2();
260
261    // §4.2.7.5.6-§4.2.7.5.8 (non-bitstream): NLSF → stable Q12 LPC.
262    let lpc_second_half = nlsf_to_stable_lpc(cfg.bandwidth, &nlsf_q15[..d_lpc])?;
263    let lpc_first_half = match interp.n1_q15() {
264        Some(n1) => Some(nlsf_to_stable_lpc(cfg.bandwidth, n1)?),
265        None => None,
266    };
267
268    // ---- Step 8: §4.2.7.6 LTP lags + gains + scaling (voiced only). ----
269    let lag_coding = match cfg.previous_primary_lag {
270        Some(previous_lag) => LagCoding::Relative { previous_lag },
271        None => LagCoding::Absolute,
272    };
273    let ltp = LtpParameters::decode(
274        rd,
275        LtpConfig {
276            bandwidth: cfg.bandwidth,
277            signal_type: pre.signal_type,
278            num_subframes,
279            lag_coding,
280            ltp_scaling_present: cfg.ltp_scaling_present,
281        },
282    )?;
283
284    // ---- Step 9: §4.2.7.7 LCG seed. ----
285    let lcg_seed = decode_lcg_seed(rd);
286
287    // ---- Step 10: §4.2.7.8 quantized excitation. ----
288    let excitation = Excitation::decode(
289        rd,
290        ExcitationConfig {
291            bandwidth: cfg.bandwidth,
292            frame_size: cfg.frame_size,
293            signal_type: pre.signal_type,
294            qoff_type: pre.qoff_type,
295            lcg_seed,
296        },
297    )?;
298
299    if rd.has_error() {
300        return Err(Error::MalformedPacket);
301    }
302
303    Ok(SilkFrameDecoded {
304        signal_type: pre.signal_type,
305        qoff_type: pre.qoff_type,
306        gains,
307        lsf_stage1,
308        nlsf_q15,
309        d_lpc,
310        lsf_interp_q2,
311        lpc_second_half,
312        lpc_first_half,
313        ltp,
314        lcg_seed,
315        excitation,
316        stereo_pred,
317        mid_only_flag,
318    })
319}
320
321/// The complete Table-5 symbol script for one regular SILK frame on
322/// the encode side, consumed by [`encode_silk_frame`]. Each field is
323/// the per-stage symbol input of the matching stage encoder; see the
324/// per-stage types for the index domains.
325#[derive(Debug, Clone, Copy, PartialEq, Eq)]
326pub struct SilkFrameSymbols<'a> {
327    /// Steps 1-3: §4.2.7.1 stereo weights / §4.2.7.2 mid-only flag /
328    /// §4.2.7.3 frame type. Presence of the stereo / mid-only parts
329    /// must match the frame's [`SilkFrameConfig::stereo`] context, and
330    /// the frame type must lie in the support selected by
331    /// [`SilkFrameConfig::voice_active`].
332    pub header: SilkHeaderSymbols,
333    /// Step 4: §4.2.7.4 per-subframe gain symbols (2 or 4 entries,
334    /// matching the frame size).
335    pub gains: &'a [GainSymbol],
336    /// Step 5: §4.2.7.5.1 LSF stage-1 index `I1 ∈ 0..32`.
337    pub lsf_stage1: u8,
338    /// Step 6: §4.2.7.5.2 signed stage-2 indices `I2[k] ∈ [-10, 10]`
339    /// (10 entries NB/MB, 16 WB).
340    pub lsf_stage2_i2: &'a [i8],
341    /// Step 7: §4.2.7.5.5 interpolation index; must be `Some(0..=4)`
342    /// for a 20 ms frame and `None` for a 10 ms frame.
343    pub lsf_interp_w_q2: Option<u8>,
344    /// Step 8: §4.2.7.6 LTP symbols; must be `Some` iff the frame
345    /// type is voiced.
346    pub ltp: Option<LtpSymbols>,
347    /// Step 9: §4.2.7.7 LCG seed, `0..=3`.
348    pub lcg_seed: u8,
349    /// Step 10: §4.2.7.8 excitation symbols (rate level, per-block
350    /// LSB depths, quantized signed `e_raw[]`).
351    pub excitation: ExcitationSymbols<'a>,
352}
353
354/// Encode one regular **mono** SILK frame into `re`, writing every
355/// Table-5 bitstream symbol in order — the exact write-side mirror of
356/// [`decode_silk_frame`] — and running the same non-bitstream
357/// §4.2.7.5.3-§4.2.7.5.8 LSF → LPC chain.
358///
359/// Returns the [`SilkFrameDecoded`] the decoder will reconstruct from
360/// this bitstream, so the caller can carry the cross-frame state
361/// (`gains.last_log_gain()`, `nlsf_q15`, `ltp.primary_lag()`) exactly
362/// as a decoder would, and drive the §4.2.7.9 synthesis for local
363/// monitoring.
364///
365/// Returns [`Error::MalformedPacket`] on any symbol/config mismatch or
366/// out-of-support index (see the per-stage encoders), or if the LSF
367/// chain rejects the indices.
368pub fn encode_silk_frame(
369    re: &mut RangeEncoder,
370    cfg: SilkFrameConfig,
371    symbols: &SilkFrameSymbols<'_>,
372) -> Result<SilkFrameDecoded, Error> {
373    let num_subframes: u8 = match cfg.frame_size {
374        SilkFrameSize::TenMs => 2,
375        SilkFrameSize::TwentyMs => 4,
376    };
377
378    // ---- Steps 1-3: §4.2.7.1 / §4.2.7.2 / §4.2.7.3. ----
379    let header_cfg = SilkFrameHeaderConfig {
380        stereo_mid_channel: cfg.stereo.is_some(),
381        stereo: cfg.stereo.is_some(),
382        has_mid_only_flag: cfg.stereo.is_some_and(|s| s.has_mid_only_flag),
383        kind: if cfg.voice_active {
384            FrameKind::RegularActive
385        } else {
386            FrameKind::RegularInactive
387        },
388        bandwidth: cfg.bandwidth,
389    };
390    let pre = SilkFrameHeader::encode_pre_gains(re, header_cfg, &symbols.header)?;
391
392    // ---- Step 4: §4.2.7.4 subframe gains. ----
393    let gains = SubframeGains::encode(
394        re,
395        SubframeGainsConfig {
396            signal_type: pre.signal_type,
397            num_subframes,
398            first_subframe_is_independent: cfg.first_subframe_independent,
399            previous_log_gain: cfg.previous_log_gain,
400        },
401        symbols.gains,
402    )?;
403
404    // ---- Step 5: §4.2.7.5.1 LSF stage-1 index. ----
405    SilkFrameHeader::encode_lsf_stage1(re, cfg.bandwidth, pre.signal_type, symbols.lsf_stage1)?;
406
407    // ---- Step 6: §4.2.7.5.2 LSF stage-2 residual. ----
408    let stage2 = LsfStage2::encode(re, cfg.bandwidth, symbols.lsf_stage1, symbols.lsf_stage2_i2)?;
409
410    // §4.2.7.5.3 / §4.2.7.5.4 (non-bitstream) — identical to decode.
411    let recon =
412        NlsfReconstructed::from_stage1_and_stage2(cfg.bandwidth, symbols.lsf_stage1, &stage2)?;
413    let stabilized = NlsfStabilized::from_reconstructed(cfg.bandwidth, &recon)?;
414    let d_lpc = stabilized.len();
415    let mut nlsf_q15 = [0i16; crate::silk_lsf_stage2::D_LPC_MAX];
416    nlsf_q15[..d_lpc].copy_from_slice(stabilized.nlsf_q15());
417
418    // ---- Step 7: §4.2.7.5.5 LSF interpolation weight (20 ms only). ----
419    let interp_context = match cfg.frame_size {
420        SilkFrameSize::TenMs => LsfInterpContext::TenMs,
421        SilkFrameSize::TwentyMs => {
422            if cfg.lsf_interp_after_reset || cfg.previous_nlsf_q15.is_none() {
423                LsfInterpContext::TwentyMsAfterResetOrUncoded
424            } else {
425                LsfInterpContext::TwentyMs
426            }
427        }
428    };
429    LsfInterpolated::encode_index(re, interp_context, symbols.lsf_interp_w_q2)?;
430    let n0_slice: Option<&[i16]> = match (&cfg.previous_nlsf_q15, cfg.frame_size) {
431        (Some(prev), SilkFrameSize::TwentyMs) if cfg.previous_nlsf_len == d_lpc => {
432            Some(&prev[..d_lpc])
433        }
434        _ => None,
435    };
436    let interp = match (cfg.frame_size, symbols.lsf_interp_w_q2) {
437        (SilkFrameSize::TwentyMs, Some(w)) => {
438            LsfInterpolated::from_decoded_index(w, &stabilized, n0_slice, interp_context)
439        }
440        // `encode_index` already rejected every other combination
441        // except the valid 10 ms / None pairing.
442        _ => LsfInterpolated::decode(
443            &mut RangeDecoder::new(&[]),
444            &stabilized,
445            n0_slice,
446            LsfInterpContext::TenMs,
447        )?,
448    };
449    let lsf_interp_q2 = interp.w_q2();
450
451    // §4.2.7.5.6-§4.2.7.5.8 (non-bitstream) — identical to decode.
452    let lpc_second_half = nlsf_to_stable_lpc(cfg.bandwidth, &nlsf_q15[..d_lpc])?;
453    let lpc_first_half = match interp.n1_q15() {
454        Some(n1) => Some(nlsf_to_stable_lpc(cfg.bandwidth, n1)?),
455        None => None,
456    };
457
458    // ---- Step 8: §4.2.7.6 LTP (voiced only). ----
459    if (pre.signal_type == SignalType::Voiced) != symbols.ltp.is_some() {
460        return Err(Error::MalformedPacket);
461    }
462    let lag_coding = match cfg.previous_primary_lag {
463        Some(previous_lag) => LagCoding::Relative { previous_lag },
464        None => LagCoding::Absolute,
465    };
466    let ltp = LtpParameters::encode(
467        re,
468        LtpConfig {
469            bandwidth: cfg.bandwidth,
470            signal_type: pre.signal_type,
471            num_subframes,
472            lag_coding,
473            ltp_scaling_present: cfg.ltp_scaling_present,
474        },
475        symbols.ltp.as_ref(),
476    )?;
477
478    // ---- Step 9: §4.2.7.7 LCG seed. ----
479    encode_lcg_seed(re, symbols.lcg_seed)?;
480
481    // ---- Step 10: §4.2.7.8 quantized excitation. ----
482    let excitation = Excitation::encode(
483        re,
484        ExcitationConfig {
485            bandwidth: cfg.bandwidth,
486            frame_size: cfg.frame_size,
487            signal_type: pre.signal_type,
488            qoff_type: pre.qoff_type,
489            lcg_seed: symbols.lcg_seed,
490        },
491        &symbols.excitation,
492    )?;
493
494    Ok(SilkFrameDecoded {
495        signal_type: pre.signal_type,
496        qoff_type: pre.qoff_type,
497        gains,
498        lsf_stage1: symbols.lsf_stage1,
499        nlsf_q15,
500        d_lpc,
501        lsf_interp_q2,
502        lpc_second_half,
503        lpc_first_half,
504        ltp,
505        lcg_seed: symbols.lcg_seed,
506        excitation,
507        stereo_pred: pre.stereo_pred,
508        mid_only_flag: pre.mid_only_flag,
509    })
510}
511
512/// Run the §4.2.7.5.6–§4.2.7.5.8 NLSF → stable Q12 LPC chain for one
513/// normalized-LSF vector: NLSF→LPC (`silk_NLSF2A`), the §4.2.7.5.7
514/// range-limiting bandwidth expansion, and the §4.2.7.5.8
515/// prediction-gain limiting.
516fn nlsf_to_stable_lpc(bandwidth: Bandwidth, nlsf_q15: &[i16]) -> Result<LpcQ12, Error> {
517    let lpc_q17 = crate::silk_lsf_to_lpc::LpcQ17::from_nlsf(bandwidth, nlsf_q15)?;
518    let range_limited = lpc_q17.range_limited();
519    Ok(range_limited.prediction_gain_limited())
520}
521
522#[cfg(test)]
523mod tests {
524    use super::*;
525
526    /// A SILK frame config for a fresh (post-reset) regular mono frame.
527    fn fresh_cfg(bandwidth: Bandwidth, frame_size: SilkFrameSize, voiced: bool) -> SilkFrameConfig {
528        SilkFrameConfig {
529            bandwidth,
530            frame_size,
531            voice_active: voiced,
532            first_subframe_independent: true,
533            previous_log_gain: None,
534            previous_primary_lag: None,
535            ltp_scaling_present: true,
536            lsf_interp_after_reset: true,
537            previous_nlsf_q15: None,
538            previous_nlsf_len: 0,
539            stereo: None,
540        }
541    }
542
543    /// Decoding from an all-zero buffer is total (never panics) and
544    /// either succeeds or reports MalformedPacket. The all-zero buffer is
545    /// a valid range-coder input; this pins that every stage threads
546    /// through without an index-out-of-bounds or arithmetic panic.
547    #[test]
548    fn decode_from_zero_buffer_is_total() {
549        for &bw in &[Bandwidth::Nb, Bandwidth::Mb, Bandwidth::Wb] {
550            for &fs in &[SilkFrameSize::TenMs, SilkFrameSize::TwentyMs] {
551                for voiced in [false, true] {
552                    let buf = [0u8; 64];
553                    let mut rd = RangeDecoder::new(&buf);
554                    let cfg = fresh_cfg(bw, fs, voiced);
555                    let _ = decode_silk_frame(&mut rd, cfg);
556                }
557            }
558        }
559    }
560
561    /// Decoding consumes bits in Table-5 order: `tell()` after a
562    /// successful decode is strictly greater than at the start (the frame
563    /// always has at least the frame-type + gains + LSF symbols), and the
564    /// decoded `d_lpc` matches the bandwidth.
565    #[test]
566    fn decode_consumes_bits_and_sets_d_lpc() {
567        // A non-trivial buffer so the range coder produces varied
568        // symbols. The exact decoded values are not asserted (no
569        // bit-exact fixture at the codec level yet); the structural
570        // invariants are.
571        let buf: Vec<u8> = (0..96u16)
572            .map(|i| (i.wrapping_mul(37) & 0xff) as u8)
573            .collect();
574        for (&bw, expected_d) in [Bandwidth::Nb, Bandwidth::Mb, Bandwidth::Wb]
575            .iter()
576            .zip([10usize, 10, 16])
577        {
578            let mut rd = RangeDecoder::new(&buf);
579            let start = rd.tell();
580            let cfg = fresh_cfg(bw, SilkFrameSize::TwentyMs, false);
581            if let Ok(decoded) = decode_silk_frame(&mut rd, cfg) {
582                assert!(rd.tell() > start, "bw={bw:?} must consume bits");
583                assert_eq!(decoded.d_lpc, expected_d, "bw={bw:?}");
584                // A 20 ms frame carries an interpolation factor and a
585                // first-half LPC filter.
586                assert!(decoded.lsf_interp_q2.is_some());
587                assert!(decoded.lpc_first_half.is_some());
588                // The stable Q12 LPC has d_lpc taps.
589                assert_eq!(decoded.lpc_second_half.a_q12().len(), expected_d);
590            }
591        }
592    }
593
594    /// A 10 ms frame carries no interpolation factor and reuses the
595    /// second-half LPC throughout (no first-half split).
596    #[test]
597    fn ten_ms_frame_has_no_interpolation_split() {
598        let buf: Vec<u8> = (0..96u16)
599            .map(|i| (i.wrapping_mul(91) & 0xff) as u8)
600            .collect();
601        let mut rd = RangeDecoder::new(&buf);
602        let cfg = fresh_cfg(Bandwidth::Wb, SilkFrameSize::TenMs, false);
603        if let Ok(decoded) = decode_silk_frame(&mut rd, cfg) {
604            assert!(decoded.lsf_interp_q2.is_none());
605            assert!(decoded.lpc_first_half.is_none());
606        }
607    }
608
609    /// SWB / FB are rejected: SILK never sees them after the §4.2.2
610    /// hybrid split. (The public Bandwidth enum carries them, so the
611    /// decode must reject rather than mis-index a table.)
612    #[test]
613    fn swb_fb_rejected() {
614        let buf = [0x42u8; 32];
615        for &bw in &[Bandwidth::Swb, Bandwidth::Fb] {
616            let mut rd = RangeDecoder::new(&buf);
617            let cfg = fresh_cfg(bw, SilkFrameSize::TwentyMs, false);
618            assert!(matches!(
619                decode_silk_frame(&mut rd, cfg),
620                Err(Error::MalformedPacket)
621            ));
622        }
623    }
624
625    /// A mono frame returns no §4.2.7.1 stereo weights and no §4.2.7.2
626    /// mid-only flag.
627    #[test]
628    fn mono_frame_has_no_stereo_fields() {
629        let buf: Vec<u8> = (0..96u16)
630            .map(|i| (i.wrapping_mul(71).wrapping_add(3) & 0xff) as u8)
631            .collect();
632        let mut rd = RangeDecoder::new(&buf);
633        let cfg = fresh_cfg(Bandwidth::Nb, SilkFrameSize::TwentyMs, false);
634        if let Ok(decoded) = decode_silk_frame(&mut rd, cfg) {
635            assert!(decoded.stereo_pred.is_none());
636            assert!(decoded.mid_only_flag.is_none());
637        }
638    }
639
640    /// The mid channel of a stereo Opus frame decodes the §4.2.7.1 stereo
641    /// prediction weights ahead of the frame type and surfaces them in
642    /// `SilkFrameDecoded`. Reading the extra §4.2.7.1 symbols shifts the
643    /// bitstream relative to the mono path, so the two decodes of the same
644    /// buffer differ — this pins that the stereo weights are actually read.
645    #[test]
646    fn stereo_mid_channel_reads_prediction_weights() {
647        let buf: Vec<u8> = (0..128u16)
648            .map(|i| (i.wrapping_mul(83).wrapping_add(17) & 0xff) as u8)
649            .collect();
650
651        let mut rd_stereo = RangeDecoder::new(&buf);
652        let mut cfg_stereo = fresh_cfg(Bandwidth::Wb, SilkFrameSize::TwentyMs, true);
653        cfg_stereo.stereo = Some(StereoHeaderContext {
654            has_mid_only_flag: false,
655        });
656        let start = rd_stereo.tell();
657        if let Ok(decoded) = decode_silk_frame(&mut rd_stereo, cfg_stereo) {
658            // The §4.2.7.1 weights were read (non-None) and the bitstream
659            // advanced past at least the stereo-weight + frame-type symbols.
660            assert!(decoded.stereo_pred.is_some());
661            assert!(decoded.mid_only_flag.is_none()); // not signalled here.
662            assert!(rd_stereo.tell() > start);
663        }
664    }
665
666    /// When the §4.2.7.2 mid-only flag is signalled, it is decoded and
667    /// returned (after the §4.2.7.1 weights, ahead of the frame type).
668    #[test]
669    fn stereo_mid_only_flag_decoded_when_present() {
670        let buf: Vec<u8> = (0..128u16)
671            .map(|i| (i.wrapping_mul(59).wrapping_add(29) & 0xff) as u8)
672            .collect();
673        let mut rd = RangeDecoder::new(&buf);
674        let mut cfg = fresh_cfg(Bandwidth::Nb, SilkFrameSize::TwentyMs, false);
675        cfg.stereo = Some(StereoHeaderContext {
676            has_mid_only_flag: true,
677        });
678        if let Ok(decoded) = decode_silk_frame(&mut rd, cfg) {
679            assert!(decoded.stereo_pred.is_some());
680            // The flag is a real bool (0 or 1), decoded from Table 8.
681            assert!(matches!(decoded.mid_only_flag, Some(false) | Some(true)));
682        }
683    }
684
685    // ----- Whole-frame encode → decode roundtrip --------------------
686
687    /// A tiny deterministic LCG for the whole-frame roundtrip sweep.
688    struct Lcg(u64);
689    impl Lcg {
690        fn next_u32(&mut self) -> u32 {
691            self.0 = self
692                .0
693                .wrapping_mul(6364136223846793005)
694                .wrapping_add(1442695040888963407);
695            (self.0 >> 32) as u32
696        }
697        fn below(&mut self, n: u32) -> u32 {
698            self.next_u32() % n
699        }
700    }
701
702    /// The capstone roundtrip: random full Table-5 symbol scripts across
703    /// every bandwidth / frame size / signal type / stereo-context /
704    /// carried-state combination, written by `encode_silk_frame` and read
705    /// back by `decode_silk_frame`. The decoded `SilkFrameDecoded` —
706    /// every field, including the derived LSF → LPC chain, the LTP
707    /// parameters, and the LCG-reconstructed Q23 excitation — must equal
708    /// the encoder's prediction exactly.
709    #[test]
710    fn whole_frame_encode_decode_roundtrip_random() {
711        use crate::range_encoder::RangeEncoder;
712        use crate::silk_excitation::{shell_block_count, SHELL_BLOCK_SAMPLES};
713        use crate::silk_frame::StereoWeightSymbols;
714        use crate::silk_gains::GainSymbol;
715        use crate::silk_ltp::{LagSymbols, LtpSymbols, LTP_MAX_SUBFRAMES};
716
717        let mut rng = Lcg(0xF8A3_0382);
718        let mut done = 0u32;
719        while done < 250 {
720            let bandwidth = match rng.below(3) {
721                0 => Bandwidth::Nb,
722                1 => Bandwidth::Mb,
723                _ => Bandwidth::Wb,
724            };
725            let frame_size = if rng.below(2) == 0 {
726                SilkFrameSize::TenMs
727            } else {
728                SilkFrameSize::TwentyMs
729            };
730            let num_subframes = if frame_size == SilkFrameSize::TenMs {
731                2u8
732            } else {
733                4
734            };
735            let voice_active = rng.below(2) == 1;
736            let stereo_mid = rng.below(3) == 0;
737            let has_mid_only = stereo_mid && rng.below(2) == 0;
738            let first_independent = rng.below(2) == 0;
739            let previous_log_gain = if first_independent && rng.below(2) == 0 {
740                None
741            } else {
742                Some(rng.below(64) as u8)
743            };
744            let previous_primary_lag = if rng.below(2) == 0 {
745                Some(20 + rng.below(200) as i32)
746            } else {
747                None
748            };
749            let cfg = SilkFrameConfig {
750                bandwidth,
751                frame_size,
752                voice_active,
753                first_subframe_independent: first_independent,
754                previous_log_gain,
755                previous_primary_lag,
756                ltp_scaling_present: rng.below(2) == 1,
757                lsf_interp_after_reset: rng.below(2) == 1,
758                previous_nlsf_q15: None,
759                previous_nlsf_len: 0,
760                stereo: stereo_mid.then_some(StereoHeaderContext {
761                    has_mid_only_flag: has_mid_only,
762                }),
763            };
764
765            // ---- Build a random valid symbol script. ----
766            let frame_type = if voice_active {
767                2 + rng.below(4) as u8
768            } else {
769                rng.below(2) as u8
770            };
771            let voiced = frame_type >= 4;
772            let header = SilkHeaderSymbols {
773                stereo: stereo_mid.then(|| StereoWeightSymbols {
774                    n: rng.below(25) as u8,
775                    i0: rng.below(3) as u8,
776                    i1: rng.below(5) as u8,
777                    i2: rng.below(3) as u8,
778                    i3: rng.below(5) as u8,
779                }),
780                mid_only_flag: has_mid_only.then(|| rng.below(2) == 1),
781                frame_type,
782            };
783            let gains: Vec<GainSymbol> = (0..num_subframes as usize)
784                .map(|k| {
785                    if k == 0 && first_independent {
786                        GainSymbol::Independent(rng.below(64) as u8)
787                    } else {
788                        GainSymbol::Delta(rng.below(41) as u8)
789                    }
790                })
791                .collect();
792            let lsf_stage1 = rng.below(32) as u8;
793            let d_lpc = if bandwidth == Bandwidth::Wb { 16 } else { 10 };
794            let i2: Vec<i8> = (0..d_lpc).map(|_| rng.below(21) as i8 - 10).collect();
795            let lsf_interp_w_q2 =
796                (frame_size == SilkFrameSize::TwentyMs).then(|| rng.below(5) as u8);
797            let ltp = voiced.then(|| {
798                let lag_low_count = match bandwidth {
799                    Bandwidth::Nb => 4u32,
800                    Bandwidth::Mb => 6,
801                    _ => 8,
802                };
803                let lag = if previous_primary_lag.is_some() {
804                    if rng.below(2) == 0 {
805                        LagSymbols::RelativeDelta {
806                            delta_index: 1 + rng.below(20) as u8,
807                        }
808                    } else {
809                        LagSymbols::RelativeFallback {
810                            lag_high: rng.below(32) as u8,
811                            lag_low: rng.below(lag_low_count) as u8,
812                        }
813                    }
814                } else {
815                    LagSymbols::Absolute {
816                        lag_high: rng.below(32) as u8,
817                        lag_low: rng.below(lag_low_count) as u8,
818                    }
819                };
820                let contour_cells = match (bandwidth, num_subframes) {
821                    (Bandwidth::Nb, 2) => 3u32,
822                    (Bandwidth::Nb, 4) => 11,
823                    (_, 2) => 12,
824                    _ => 34,
825                };
826                let periodicity_index = rng.below(3) as u8;
827                let filter_cells = [8u32, 16, 32][periodicity_index as usize];
828                let mut filter_indices = [0u8; LTP_MAX_SUBFRAMES];
829                for f in filter_indices.iter_mut().take(num_subframes as usize) {
830                    *f = rng.below(filter_cells) as u8;
831                }
832                LtpSymbols {
833                    lag,
834                    contour_index: rng.below(contour_cells) as u8,
835                    periodicity_index,
836                    filter_indices,
837                    ltp_scaling_index: cfg.ltp_scaling_present.then(|| rng.below(3) as u8),
838                }
839            });
840            let blocks = shell_block_count(bandwidth, frame_size).unwrap();
841            let total = blocks * SHELL_BLOCK_SAMPLES;
842            let mut lsb_counts = vec![0u8; blocks];
843            let mut e_raw = vec![0i32; total];
844            for (b, lc) in lsb_counts.iter_mut().enumerate() {
845                let lsbs = if rng.below(4) == 0 {
846                    1 + rng.below(2)
847                } else {
848                    0
849                };
850                *lc = lsbs as u8;
851                let budget = rng.below(17);
852                let base = b * SHELL_BLOCK_SAMPLES;
853                let mut spent = 0u32;
854                while spent < budget {
855                    let i = base + rng.below(16) as usize;
856                    let add = 1 + rng.below(budget - spent);
857                    e_raw[i] += (add << lsbs) as i32;
858                    spent += add;
859                }
860                for slot in e_raw[base..base + SHELL_BLOCK_SAMPLES].iter_mut() {
861                    if lsbs > 0 {
862                        *slot += (rng.next_u32() & ((1 << lsbs) - 1)) as i32;
863                    }
864                    if *slot != 0 && rng.below(2) == 0 {
865                        *slot = -*slot;
866                    }
867                }
868            }
869            let symbols = SilkFrameSymbols {
870                header,
871                gains: &gains,
872                lsf_stage1,
873                lsf_stage2_i2: &i2,
874                lsf_interp_w_q2,
875                ltp,
876                lcg_seed: rng.below(4) as u8,
877                excitation: crate::silk_excitation::ExcitationSymbols {
878                    rate_level: rng.below(9) as u8,
879                    lsb_counts: &lsb_counts,
880                    e_raw: &e_raw,
881                },
882            };
883
884            let mut re = RangeEncoder::new();
885            let predicted = encode_silk_frame(&mut re, cfg, &symbols).expect("encode");
886            let bytes = re.finish();
887
888            let mut rd = RangeDecoder::new(&bytes);
889            let decoded = decode_silk_frame(&mut rd, cfg).expect("decode");
890            assert!(!rd.has_error());
891            assert_eq!(decoded, predicted, "cfg={cfg:?}");
892            done += 1;
893        }
894    }
895
896    /// LTP presence must match the frame type: a voiced script without
897    /// LTP symbols (or vice versa) is rejected before any partial write
898    /// beyond the header.
899    #[test]
900    fn whole_frame_encode_rejects_ltp_mismatch() {
901        use crate::range_encoder::RangeEncoder;
902        let cfg = fresh_cfg(Bandwidth::Nb, SilkFrameSize::TenMs, true);
903        let gains = [
904            crate::silk_gains::GainSymbol::Independent(30),
905            crate::silk_gains::GainSymbol::Delta(4),
906        ];
907        let i2 = [0i8; 10];
908        let lsb = [0u8; 5];
909        let e = [0i32; 80];
910        let symbols = SilkFrameSymbols {
911            header: SilkHeaderSymbols {
912                stereo: None,
913                mid_only_flag: None,
914                frame_type: 4, // voiced
915            },
916            gains: &gains,
917            lsf_stage1: 0,
918            lsf_stage2_i2: &i2,
919            lsf_interp_w_q2: None, // 10 ms
920            ltp: None,             // missing for a voiced frame
921            lcg_seed: 0,
922            excitation: crate::silk_excitation::ExcitationSymbols {
923                rate_level: 0,
924                lsb_counts: &lsb,
925                e_raw: &e,
926            },
927        };
928        let mut re = RangeEncoder::new();
929        assert!(encode_silk_frame(&mut re, cfg, &symbols).is_err());
930    }
931}