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}