mousiki 0.2.0

Pure Rust Opus codec.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
//! Encoder-side state representations.
//!
//! The C implementation layers multiple encoder structs.  Each channel stores a
//! `silk_encoder_state` (`sCmn` in the fixed-point build) that in turn feeds the
//! adaptive high-pass controller and many other helpers.  This module starts
//! porting that hierarchy by exposing the common fields required by the Rust
//! translation of `silk_HP_variable_cutoff`.

use crate::silk::FrameSignalType;
use crate::silk::SilkNlsfCb;
use crate::silk::StereoEncState;
use crate::silk::decode_indices::SideInfoIndices;
use crate::silk::lin2log::lin2log;
use crate::silk::lp_variable_cutoff::LpState;
use crate::silk::resampler::Resampler;
use crate::silk::tables_nlsf_cb_wb::SILK_NLSF_CB_WB;
use crate::silk::tables_other::SILK_UNIFORM8_ICDF;
use crate::silk::tables_pitch_lag::PITCH_CONTOUR_ICDF;
use crate::silk::tuning_parameters::VARIABLE_HP_MIN_CUTOFF_HZ;
use crate::silk::{MAX_FRAMES_PER_PACKET, MAX_LPC_ORDER, MAX_NB_SUBFR, MAX_SHAPE_LPC_ORDER};
use core::array::from_fn;

/// Number of VAD bands tracked per SILK channel.
pub const VAD_N_BANDS: usize = 4;
/// Number of encoder channels managed by the top-level super-structure.
pub const ENCODER_NUM_CHANNELS: usize = 2;
/// Internal SILK maximum sampling rate (kHz).
pub(crate) const MAX_FS_KHZ: usize = 16;
/// Sub-frame duration in milliseconds.
pub(crate) const SUB_FRAME_LENGTH_MS: usize = 5;
/// Maximum number of samples per 5 ms subframe.
pub(crate) const MAX_SUB_FRAME_LENGTH: usize = SUB_FRAME_LENGTH_MS * MAX_FS_KHZ;
/// Default number of milliseconds per frame.
pub(crate) const MAX_FRAME_LENGTH_MS: usize = SUB_FRAME_LENGTH_MS * MAX_NB_SUBFR;
/// Default internal sampling rate in kHz used when initialising the encoder state.
const DEFAULT_INTERNAL_FS_KHZ: i32 = 16;
/// Default frame length in samples (20 ms @ 16 kHz).
pub(crate) const DEFAULT_FRAME_LENGTH: usize =
    MAX_FRAME_LENGTH_MS * DEFAULT_INTERNAL_FS_KHZ as usize;
/// Look-ahead applied during pitch analysis (ms).
pub(crate) const LA_PITCH_MS: usize = 2;
/// Look-ahead applied during noise-shaping analysis (ms).
pub(crate) const LA_SHAPE_MS: usize = 5;
/// Maximum look-ahead in samples for noise shaping.
pub(crate) const LA_SHAPE_MAX: usize = LA_SHAPE_MS * MAX_FS_KHZ;
/// LPC window (ms) used during 20 ms (4 subframe) pitch estimation.
pub(crate) const FIND_PITCH_LPC_WIN_MS: usize = 20 + (LA_PITCH_MS << 1);
/// LPC window (ms) used during 10 ms (2 subframe) pitch estimation.
pub(crate) const FIND_PITCH_LPC_WIN_MS_2_SF: usize = 10 + (LA_PITCH_MS << 1);
/// Number of milliseconds retained in the pitch-analysis buffer.
pub(crate) const LTP_MEM_LENGTH_MS: usize = 20;
/// Maximum frame length in samples given the supported subframes and sampling rates.
pub(crate) const MAX_FRAME_LENGTH: usize = MAX_FRAME_LENGTH_MS * MAX_FS_KHZ;
/// Size of the encoder input buffer.
pub(crate) const INPUT_BUFFER_LENGTH: usize = MAX_FRAME_LENGTH + 2;
/// Number of samples stored in the pitch-analysis scratch buffer.
pub(crate) const X_BUFFER_LENGTH: usize = 2 * MAX_FRAME_LENGTH + LA_SHAPE_MAX;
/// Maximum number of delayed-decision states.
pub(crate) const MAX_DEL_DEC_STATES: i32 = 4;
/// Maximum LPC order used during pitch estimation.
pub(crate) const MAX_FIND_PITCH_LPC_ORDER: i32 = 16;
/// Upper bound on the noise-shaping analysis window (samples).
pub(crate) const SHAPE_LPC_WIN_MAX: i32 = 15 * MAX_FS_KHZ as i32;
/// Length of the NSQ LPC history buffer.
pub(crate) const NSQ_LPC_BUF_LENGTH: usize = MAX_LPC_ORDER;
/// Maximum number of samples kept in the LTP state buffer.
pub(crate) const MAX_LTP_MEM_LENGTH: usize = 4 * MAX_SUB_FRAME_LENGTH;
/// Bias used by the VAD noise estimator.
pub(crate) const VAD_NOISE_LEVELS_BIAS: i32 = 50;
/// Initial smoothed SNR per VAD band (100 * 256 -> 20 dB).
const INITIAL_NRG_RATIO_Q8: i32 = 100 * 256;
/// Number of frames used for the initial fast noise update phase.
const INITIAL_VAD_COUNTER: i32 = 15;

/// Fixed-point voice activity detector state (mirror of `silk_VAD_state`).
#[derive(Clone, Debug, PartialEq)]
pub struct VadState {
    pub ana_state: [i32; 2],
    pub ana_state1: [i32; 2],
    pub ana_state2: [i32; 2],
    pub xnrg_subfr: [i32; VAD_N_BANDS],
    pub nrg_ratio_smth_q8: [i32; VAD_N_BANDS],
    pub hp_state: i16,
    pub nl: [i32; VAD_N_BANDS],
    pub inv_nl: [i32; VAD_N_BANDS],
    pub noise_level_bias: [i32; VAD_N_BANDS],
    pub counter: i32,
}

impl Default for VadState {
    fn default() -> Self {
        let mut state = Self {
            ana_state: [0; 2],
            ana_state1: [0; 2],
            ana_state2: [0; 2],
            xnrg_subfr: [0; VAD_N_BANDS],
            nrg_ratio_smth_q8: [INITIAL_NRG_RATIO_Q8; VAD_N_BANDS],
            hp_state: 0,
            nl: [0; VAD_N_BANDS],
            inv_nl: [0; VAD_N_BANDS],
            noise_level_bias: [0; VAD_N_BANDS],
            counter: INITIAL_VAD_COUNTER,
        };
        state.reset();
        state
    }
}

impl VadState {
    /// Mirrors `silk_VAD_Init` by reinitialising the noise estimator members.
    pub fn reset(&mut self) {
        for (band, bias) in self.noise_level_bias.iter_mut().enumerate() {
            *bias = (VAD_NOISE_LEVELS_BIAS / (band as i32 + 1)).max(1);
        }
        for (nl, bias) in self.nl.iter_mut().zip(self.noise_level_bias.iter()) {
            *nl = 100 * *bias;
        }
        for (inv, nl) in self.inv_nl.iter_mut().zip(self.nl.iter()) {
            *inv = if *nl != 0 { i32::MAX / *nl } else { 0 };
        }
        self.nrg_ratio_smth_q8 = [INITIAL_NRG_RATIO_Q8; VAD_N_BANDS];
        self.xnrg_subfr = [0; VAD_N_BANDS];
        self.hp_state = 0;
        self.counter = INITIAL_VAD_COUNTER;
    }
}

/// Fixed-point noise-shaping analysis state (`silk_shape_state_FIX`).
#[derive(Clone, Debug, PartialEq, Default)]
pub struct EncoderShapeState {
    pub last_gain_index: i32,
    pub harm_boost_smth_q16: i32,
    pub harm_shape_gain_smth_q16: i32,
    pub tilt_smth_q16: i32,
}

/// Fixed-point NSQ state (`silk_nsq_state`).
#[derive(Clone, Debug, PartialEq)]
pub struct NoiseShapingQuantizerState {
    pub xq: [i16; 2 * MAX_FRAME_LENGTH],
    pub s_ltp_shp_q14: [i32; 2 * MAX_FRAME_LENGTH],
    pub s_lpc_q14: [i32; MAX_SUB_FRAME_LENGTH + NSQ_LPC_BUF_LENGTH],
    pub s_ar2_q14: [i32; MAX_SHAPE_LPC_ORDER],
    pub s_lf_ar_shp_q14: i32,
    pub s_diff_shp_q14: i32,
    pub lag_prev: i32,
    pub s_ltp_buf_idx: usize,
    pub s_ltp_shp_buf_idx: usize,
    pub rand_seed: i32,
    pub prev_gain_q16: i32,
    pub rewhite_flag: bool,
}

impl Default for NoiseShapingQuantizerState {
    fn default() -> Self {
        Self {
            xq: [0; 2 * MAX_FRAME_LENGTH],
            s_ltp_shp_q14: [0; 2 * MAX_FRAME_LENGTH],
            s_lpc_q14: [0; MAX_SUB_FRAME_LENGTH + NSQ_LPC_BUF_LENGTH],
            s_ar2_q14: [0; MAX_SHAPE_LPC_ORDER],
            s_lf_ar_shp_q14: 0,
            s_diff_shp_q14: 0,
            lag_prev: 0,
            s_ltp_buf_idx: 0,
            s_ltp_shp_buf_idx: 0,
            rand_seed: 0,
            prev_gain_q16: 1 << 16,
            rewhite_flag: false,
        }
    }
}

/// Minimal subset of the encoder common state needed by the Rust ports.
#[derive(Clone, Debug, PartialEq)]
pub struct EncoderStateCommon {
    /// Enables discontinuous transmission.
    pub use_dtx: bool,
    /// Enables constant-bit-rate mode.
    pub use_cbr: bool,
    /// Enables in-band forward error correction.
    pub use_in_band_fec: bool,
    /// Previously decoded signal classification.
    pub prev_signal_type: FrameSignalType,
    /// Internal sampling rate in kHz.
    pub fs_khz: i32,
    /// Number of 5 ms subframes tracked per frame (2 or 4).
    pub nb_subfr: usize,
    /// Active frame length in samples.
    pub frame_length: usize,
    /// External API sample rate in Hz.
    pub api_sample_rate_hz: i32,
    /// API sample rate used during the previous packet.
    pub prev_api_sample_rate_hz: i32,
    /// Maximum internal sampling rate allowed in Hz.
    pub max_internal_sample_rate_hz: i32,
    /// Minimum internal sampling rate allowed in Hz.
    pub min_internal_sample_rate_hz: i32,
    /// Requested internal sampling rate in Hz.
    pub desired_internal_sample_rate_hz: i32,
    /// Whether the encoder may change its internal bandwidth this frame.
    pub allow_bandwidth_switch: bool,
    /// Number of channels exposed to the API.
    pub n_channels_api: i32,
    /// Number of internal encoder channels.
    pub n_channels_internal: i32,
    /// Channel index within a stereo encoder.
    pub channel_nb: i32,
    /// Previous frame pitch lag (in samples).
    pub prev_lag: i32,
    /// Samples tracked per subframe.
    pub subfr_length: usize,
    /// Samples stored in the LTP state.
    pub ltp_mem_length: usize,
    /// Look-ahead for pitch analysis (samples).
    pub la_pitch: i32,
    /// Look-ahead for noise-shaping analysis (samples).
    pub la_shape: i32,
    /// Window length for noise-shaping analysis (samples).
    pub shape_win_length: i32,
    /// Maximum supported pitch lag in samples.
    pub max_pitch_lag: i32,
    /// Pitch-analysis LPC window length (samples).
    pub pitch_lpc_win_length: usize,
    /// Active LPC order used for prediction.
    pub predict_lpc_order: usize,
    /// Active NLSF codebook.
    pub ps_nlsf_cb: &'static SilkNlsfCb,
    /// Pointer to the active pitch-contour iCDF.
    pub pitch_contour_icdf: &'static [u8],
    /// Pointer to the active low-bit pitch-lag iCDF.
    pub pitch_lag_low_bits_icdf: &'static [u8],
    /// Cached quantised NLSF vector from the previous frame.
    pub prev_nlsf_q15: [i16; MAX_LPC_ORDER],
    /// Target bitrate expressed in bits per second.
    pub target_rate_bps: i32,
    /// Encoder-side SNR tuning value in Q7.
    pub snr_db_q7: i32,
    /// Cumulative logarithmic LTP gain used to bound the predictor power.
    pub sum_log_gain_q7: i32,
    /// Noise-shaping quantiser state.
    pub nsq_state: NoiseShapingQuantizerState,
    /// Running frame counter used for the entropy seed.
    pub frame_counter: i32,
    /// Buffered input samples preserved across frames.
    pub input_buf: [i16; INPUT_BUFFER_LENGTH],
    /// Per-band input quality metrics in Q15.
    pub input_quality_bands_q15: [i32; VAD_N_BANDS],
    /// Smoothed tilt estimate in Q15.
    pub input_tilt_q15: i32,
    /// Smoothed speech-activity estimate in Q8.
    pub speech_activity_q8: i32,
    /// Counts consecutive non-active frames for DTX handling.
    pub no_speech_counter: i32,
    /// Smoothed logarithmic cut-off frequency in Q15.
    pub variable_hp_smth1_q15: i32,
    /// Secondary smoother used by the adaptive high-pass controller.
    pub variable_hp_smth2_q15: i32,
    /// VAD decisions per frame within the current packet.
    pub vad_flags: [bool; MAX_FRAMES_PER_PACKET],
    /// Flag indicating LBRR data is present in the current packet.
    pub lbrr_flag: bool,
    /// Per-frame LBRR presence flags.
    pub lbrr_flags: [bool; MAX_FRAMES_PER_PACKET],
    /// Previous gain index used when coding LBRR frames.
    pub lbrr_prev_last_gain_index: i8,
    /// In-band LBRR side information per frame.
    pub indices_lbrr: [SideInfoIndices; MAX_FRAMES_PER_PACKET],
    /// Pulse buffers for the main signal.
    pub pulses: [i8; MAX_FRAME_LENGTH],
    /// Pulse buffers for the LBRR side channel.
    pub pulses_lbrr: [[i8; MAX_FRAME_LENGTH]; MAX_FRAMES_PER_PACKET],
    /// Packet size in milliseconds.
    pub packet_size_ms: i32,
    /// Downlink packet loss percentage.
    pub packet_loss_perc: i32,
    /// Number of frames stored per packet.
    pub n_frames_per_packet: usize,
    /// Number of frames encoded so far in the current packet.
    pub n_frames_encoded: usize,
    /// Quantisation indices for the current frame.
    pub indices: SideInfoIndices,
    /// Write index into the input buffer.
    pub input_buf_ix: usize,
    /// Flag indicating the first frame after a reset.
    pub first_frame_after_reset: bool,
    /// Ensures codec control only runs once per packet.
    pub controlled_since_last_payload: bool,
    /// Indicates that only buffers were prefilled (no coding).
    pub prefill_flag: bool,
    /// Pitch-estimator complexity level.
    pub pitch_estimation_complexity: i32,
    /// Pitch-estimator threshold in Q16.
    pub pitch_estimation_threshold_q16: i32,
    /// Pitch-estimator LPC order.
    pub pitch_estimation_lpc_order: i32,
    /// LPC order used for noise-shaping filters.
    pub shaping_lpc_order: i32,
    /// Number of delayed-decision states.
    pub n_states_delayed_decision: i32,
    /// Enables NLSF interpolation.
    pub use_interpolated_nlsfs: bool,
    /// Number of survivors in the NLSF MSVQ search.
    pub nlsf_msvq_survivors: i32,
    /// Warping control parameter in Q16.
    pub warping_q16: i32,
    /// Complexity setting (0-10).
    pub complexity: i32,
    /// Tracks the previous signal type for entropy coding.
    pub ec_prev_signal_type: FrameSignalType,
    /// Tracks the previous lag index for entropy coding.
    pub ec_prev_lag_index: i16,
    /// Indicates whether the encoder is currently inside a DTX stretch.
    pub in_dtx: bool,
    /// Tracks whether low-bit-rate redundancy is enabled.
    pub lbrr_enabled: bool,
    /// Gain increase applied when coding LBRR frames.
    pub lbrr_gain_increases: i32,
    /// Architecture flag used to select specialised kernels.
    pub arch: i32,
}

impl Default for EncoderStateCommon {
    fn default() -> Self {
        let api_fs_hz = DEFAULT_INTERNAL_FS_KHZ * 1000;
        let subfr_length = SUB_FRAME_LENGTH_MS * DEFAULT_INTERNAL_FS_KHZ as usize;
        let ltp_mem_length = LTP_MEM_LENGTH_MS * DEFAULT_INTERNAL_FS_KHZ as usize;
        let la_pitch = (LA_PITCH_MS as i32) * DEFAULT_INTERNAL_FS_KHZ;
        let la_shape = (LA_SHAPE_MS as i32) * DEFAULT_INTERNAL_FS_KHZ;
        let shape_win_length =
            (SUB_FRAME_LENGTH_MS as i32 * DEFAULT_INTERNAL_FS_KHZ) + 2 * la_shape;
        let max_pitch_lag = 18 * DEFAULT_INTERNAL_FS_KHZ;
        let pitch_lpc_win_length = FIND_PITCH_LPC_WIN_MS * DEFAULT_INTERNAL_FS_KHZ as usize;
        Self {
            use_dtx: false,
            use_cbr: false,
            use_in_band_fec: false,
            prev_signal_type: FrameSignalType::Inactive,
            fs_khz: DEFAULT_INTERNAL_FS_KHZ,
            nb_subfr: MAX_NB_SUBFR,
            frame_length: DEFAULT_FRAME_LENGTH,
            api_sample_rate_hz: api_fs_hz,
            prev_api_sample_rate_hz: api_fs_hz,
            max_internal_sample_rate_hz: api_fs_hz,
            min_internal_sample_rate_hz: api_fs_hz,
            desired_internal_sample_rate_hz: api_fs_hz,
            allow_bandwidth_switch: false,
            n_channels_api: 1,
            n_channels_internal: 1,
            channel_nb: 0,
            prev_lag: 0,
            subfr_length,
            ltp_mem_length,
            la_pitch,
            la_shape,
            shape_win_length,
            max_pitch_lag,
            pitch_lpc_win_length,
            predict_lpc_order: MAX_LPC_ORDER,
            ps_nlsf_cb: &SILK_NLSF_CB_WB,
            pitch_contour_icdf: &PITCH_CONTOUR_ICDF,
            pitch_lag_low_bits_icdf: &SILK_UNIFORM8_ICDF,
            prev_nlsf_q15: [0; MAX_LPC_ORDER],
            target_rate_bps: 0,
            snr_db_q7: 0,
            sum_log_gain_q7: 0,
            nsq_state: NoiseShapingQuantizerState::default(),
            frame_counter: 0,
            input_buf: [0; INPUT_BUFFER_LENGTH],
            input_quality_bands_q15: [0; VAD_N_BANDS],
            input_tilt_q15: 0,
            speech_activity_q8: 0,
            no_speech_counter: 0,
            variable_hp_smth1_q15: lin2log(VARIABLE_HP_MIN_CUTOFF_HZ) << 8,
            variable_hp_smth2_q15: lin2log(VARIABLE_HP_MIN_CUTOFF_HZ) << 8,
            vad_flags: [false; MAX_FRAMES_PER_PACKET],
            lbrr_flag: false,
            lbrr_flags: [false; MAX_FRAMES_PER_PACKET],
            lbrr_prev_last_gain_index: 0,
            indices_lbrr: from_fn(|_| SideInfoIndices::default()),
            pulses: [0; MAX_FRAME_LENGTH],
            pulses_lbrr: from_fn(|_| [0; MAX_FRAME_LENGTH]),
            packet_size_ms: MAX_FRAME_LENGTH_MS as i32,
            packet_loss_perc: 0,
            n_frames_per_packet: 1,
            n_frames_encoded: 0,
            indices: SideInfoIndices::default(),
            input_buf_ix: 0,
            first_frame_after_reset: true,
            controlled_since_last_payload: false,
            prefill_flag: false,
            pitch_estimation_complexity: 0,
            pitch_estimation_threshold_q16: 0,
            pitch_estimation_lpc_order: 0,
            shaping_lpc_order: 0,
            n_states_delayed_decision: 0,
            use_interpolated_nlsfs: false,
            nlsf_msvq_survivors: 0,
            warping_q16: 0,
            complexity: 0,
            ec_prev_signal_type: FrameSignalType::Inactive,
            ec_prev_lag_index: 0,
            in_dtx: false,
            lbrr_enabled: false,
            lbrr_gain_increases: 0,
            arch: 0,
        }
    }
}

/// Encoder channel state (Rust mirror of `silk_encoder_state` minus unported fields).
#[derive(Clone, Debug)]
pub struct EncoderChannelState {
    /// Common fields shared with the floating-point build.
    pub common: EncoderStateCommon,
    // Keep VAD state outside `common` so callers can borrow VAD + common mutably without unsafe aliasing.
    /// Voice activity detector state.
    pub vad_state: VadState,
    // Likewise, keep the LP transition state disjoint from `common` to permit safe split borrows.
    /// Variable low-pass filter state used during bandwidth transitions.
    pub lp_state: LpState,
    /// Noise-shaping analysis state.
    pub shape_state: EncoderShapeState,
    /// High-level SILK resampler state used by the API wrapper.
    pub resampler_state: Resampler,
    /// Pitch-analysis buffer mirrored from `x_buf`.
    pub x_buf: [i16; X_BUFFER_LENGTH],
    /// Normalised correlation from the pitch-lag estimator (Q15).
    pub ltp_corr_q15: i32,
}

impl Default for EncoderChannelState {
    fn default() -> Self {
        Self {
            common: EncoderStateCommon::default(),
            vad_state: VadState::default(),
            lp_state: LpState::default(),
            shape_state: EncoderShapeState::default(),
            resampler_state: Resampler::default(),
            x_buf: [0; X_BUFFER_LENGTH],
            ltp_corr_q15: 0,
        }
    }
}

impl EncoderChannelState {
    /// Creates a new channel state with default-initialised members.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Construct a channel state around an existing common state snapshot.
    #[must_use]
    pub fn with_common(common: EncoderStateCommon) -> Self {
        Self {
            common,
            vad_state: VadState::default(),
            lp_state: LpState::default(),
            shape_state: EncoderShapeState::default(),
            resampler_state: Resampler::default(),
            x_buf: [0; X_BUFFER_LENGTH],
            ltp_corr_q15: 0,
        }
    }

    /// Borrow the common encoder fields.
    #[must_use]
    pub fn common(&self) -> &EncoderStateCommon {
        &self.common
    }

    /// Mutably borrow the common encoder fields.
    #[must_use]
    pub fn common_mut(&mut self) -> &mut EncoderStateCommon {
        &mut self.common
    }

    /// Borrow the VAD state.
    #[must_use]
    pub fn vad(&self) -> &VadState {
        &self.vad_state
    }

    /// Mutably borrow the VAD state.
    #[must_use]
    pub fn vad_mut(&mut self) -> &mut VadState {
        &mut self.vad_state
    }

    /// Simultaneously borrow the common encoder fields and VAD state.
    pub(crate) fn parts_mut(&mut self) -> (&mut EncoderStateCommon, &mut VadState) {
        (&mut self.common, &mut self.vad_state)
    }

    /// Simultaneously borrow the common encoder fields and low-pass transition state.
    pub(crate) fn common_and_lp_mut(&mut self) -> (&mut EncoderStateCommon, &mut LpState) {
        (&mut self.common, &mut self.lp_state)
    }

    /// Borrow the bandwidth-transition low-pass state.
    #[must_use]
    pub fn low_pass_state(&self) -> &LpState {
        &self.lp_state
    }

    /// Mutably borrow the bandwidth-transition low-pass state.
    #[must_use]
    pub fn low_pass_state_mut(&mut self) -> &mut LpState {
        &mut self.lp_state
    }

    /// Update the adaptive high-pass smoother using the current channel statistics.
    pub fn update_variable_high_pass(&mut self) {
        crate::silk::hp_variable_cutoff::hp_variable_cutoff(self);
    }
}

/// Top-level encoder super-structure mirroring `silk_encoder`.
#[derive(Clone, Debug)]
pub struct Encoder {
    /// Per-channel encoder states (`state_Fxx` in the reference sources).
    pub state_fxx: [EncoderChannelState; ENCODER_NUM_CHANNELS],
    /// Stereo mid/side predictor state shared across channels.
    pub stereo_state: StereoEncState,
    /// Number of bits consumed by the LBRR side channel in the current packet.
    pub n_bits_used_lbrr: i32,
    /// Tracks bit-budget overflows reported by the encoder control path.
    pub n_bits_exceeded: i32,
    /// Number of active channels exposed through the public API.
    pub n_channels_api: i32,
    /// Number of internally active encoder channels.
    pub n_channels_internal: i32,
    /// Previous number of internal channels (used during transitions).
    pub n_prev_channels_internal: i32,
    /// Cooldown timer before another bandwidth switch is permitted.
    pub time_since_switch_allowed_ms: i32,
    /// Indicates whether bandwidth switching is currently allowed.
    pub allow_bandwidth_switch: bool,
    /// Remembers whether the decoder was forced to mid-only last frame.
    pub prev_decode_only_middle: bool,
}

impl Default for Encoder {
    fn default() -> Self {
        Self {
            state_fxx: from_fn(|_| EncoderChannelState::default()),
            stereo_state: StereoEncState::default(),
            n_bits_used_lbrr: 0,
            n_bits_exceeded: 0,
            n_channels_api: 1,
            n_channels_internal: 1,
            n_prev_channels_internal: 1,
            time_since_switch_allowed_ms: 0,
            allow_bandwidth_switch: false,
            prev_decode_only_middle: false,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn encoder_state_common_defaults_match_reference() {
        let common = EncoderStateCommon::default();
        let api_fs = DEFAULT_INTERNAL_FS_KHZ * 1000;
        let subfr_len = SUB_FRAME_LENGTH_MS * DEFAULT_INTERNAL_FS_KHZ as usize;
        let ltp_mem_len = LTP_MEM_LENGTH_MS * DEFAULT_INTERNAL_FS_KHZ as usize;
        assert_eq!(common.prev_signal_type, FrameSignalType::Inactive);
        assert!(!common.use_dtx);
        assert!(!common.use_cbr);
        assert!(!common.use_in_band_fec);
        assert_eq!(common.fs_khz, DEFAULT_INTERNAL_FS_KHZ);
        assert_eq!(common.nb_subfr, MAX_NB_SUBFR);
        assert_eq!(common.frame_length, DEFAULT_FRAME_LENGTH);
        assert_eq!(common.api_sample_rate_hz, api_fs);
        assert_eq!(common.prev_api_sample_rate_hz, api_fs);
        assert_eq!(common.max_internal_sample_rate_hz, api_fs);
        assert_eq!(common.min_internal_sample_rate_hz, api_fs);
        assert_eq!(common.desired_internal_sample_rate_hz, api_fs);
        assert!(!common.allow_bandwidth_switch);
        assert_eq!(common.n_channels_api, 1);
        assert_eq!(common.n_channels_internal, 1);
        assert_eq!(common.channel_nb, 0);
        assert_eq!(common.prev_lag, 0);
        assert_eq!(common.subfr_length, subfr_len);
        assert_eq!(common.ltp_mem_length, ltp_mem_len);
        assert_eq!(
            common.la_pitch,
            LA_PITCH_MS as i32 * DEFAULT_INTERNAL_FS_KHZ
        );
        assert_eq!(
            common.la_shape,
            LA_SHAPE_MS as i32 * DEFAULT_INTERNAL_FS_KHZ
        );
        assert_eq!(
            common.pitch_lpc_win_length,
            FIND_PITCH_LPC_WIN_MS * DEFAULT_INTERNAL_FS_KHZ as usize
        );
        assert_eq!(common.predict_lpc_order, MAX_LPC_ORDER);
        assert_eq!(common.ps_nlsf_cb, &SILK_NLSF_CB_WB);
        assert_eq!(common.pitch_contour_icdf, &PITCH_CONTOUR_ICDF);
        assert_eq!(common.pitch_lag_low_bits_icdf, &SILK_UNIFORM8_ICDF);
        assert_eq!(common.prev_nlsf_q15, [0; MAX_LPC_ORDER]);
        assert_eq!(common.target_rate_bps, 0);
        assert_eq!(common.snr_db_q7, 0);
        assert_eq!(common.nsq_state, NoiseShapingQuantizerState::default());
        assert_eq!(common.frame_counter, 0);
        assert_eq!(common.input_buf, [0; INPUT_BUFFER_LENGTH]);
        assert_eq!(common.input_quality_bands_q15, [0; VAD_N_BANDS]);
        assert_eq!(common.input_tilt_q15, 0);
        assert_eq!(common.speech_activity_q8, 0);
        assert_eq!(common.no_speech_counter, 0);
        assert_eq!(
            common.variable_hp_smth1_q15,
            lin2log(VARIABLE_HP_MIN_CUTOFF_HZ) << 8
        );
        assert_eq!(
            common.variable_hp_smth2_q15,
            lin2log(VARIABLE_HP_MIN_CUTOFF_HZ) << 8
        );
        assert_eq!(common.vad_flags, [false; MAX_FRAMES_PER_PACKET]);
        assert!(!common.lbrr_flag);
        assert_eq!(common.lbrr_flags, [false; MAX_FRAMES_PER_PACKET]);
        assert_eq!(common.lbrr_prev_last_gain_index, 0);
        assert!(
            common
                .indices_lbrr
                .iter()
                .all(|indices| *indices == SideInfoIndices::default())
        );
        assert_eq!(common.pulses, [0; MAX_FRAME_LENGTH]);
        assert!(
            common
                .pulses_lbrr
                .iter()
                .all(|pulses| *pulses == [0; MAX_FRAME_LENGTH])
        );
        assert_eq!(common.packet_size_ms, MAX_FRAME_LENGTH_MS as i32);
        assert_eq!(common.packet_loss_perc, 0);
        assert_eq!(common.n_frames_per_packet, 1);
        assert_eq!(common.n_frames_encoded, 0);
        assert_eq!(common.indices, SideInfoIndices::default());
        assert_eq!(common.input_buf_ix, 0);
        assert!(common.first_frame_after_reset);
        assert!(!common.controlled_since_last_payload);
        assert!(!common.prefill_flag);
        assert_eq!(common.ec_prev_signal_type, FrameSignalType::Inactive);
        assert_eq!(common.ec_prev_lag_index, 0);
        assert!(!common.in_dtx);
        assert!(!common.lbrr_enabled);
        assert_eq!(common.lbrr_gain_increases, 0);
        assert_eq!(common.arch, 0);
    }

    #[test]
    fn encoder_channel_state_default_wraps_common() {
        let channel = EncoderChannelState::default();
        assert_eq!(*channel.common(), EncoderStateCommon::default());
        assert_eq!(channel.vad(), &VadState::default());
        assert_eq!(channel.low_pass_state(), &LpState::default());
        assert_eq!(channel.shape_state, EncoderShapeState::default());
        assert_eq!(channel.resampler_state.fs_in_khz(), 0);
        assert_eq!(channel.resampler_state.fs_out_khz(), 0);
        assert_eq!(channel.x_buf, [0; X_BUFFER_LENGTH]);
    }

    #[test]
    fn encoder_channel_state_with_common_preserves_input() {
        let mut custom = EncoderStateCommon::default();
        custom.fs_khz = 24;
        let channel = EncoderChannelState::with_common(custom.clone());
        assert_eq!(channel.common(), &custom);
    }

    #[test]
    fn vad_state_reset_matches_reference_bias() {
        let mut vad = VadState::default();
        vad.noise_level_bias = [0; VAD_N_BANDS];
        vad.reset();
        assert_eq!(vad.noise_level_bias[0], VAD_NOISE_LEVELS_BIAS);
        assert_eq!(vad.noise_level_bias[1], VAD_NOISE_LEVELS_BIAS / 2);
        assert_eq!(vad.noise_level_bias[2], VAD_NOISE_LEVELS_BIAS / 3);
        assert_eq!(vad.noise_level_bias[3], VAD_NOISE_LEVELS_BIAS / 4);
        assert!(vad.nl.iter().all(|&nl| nl > 0));
        assert!(vad.inv_nl.iter().all(|&inv| inv > 0));
        assert_eq!(vad.nrg_ratio_smth_q8, [INITIAL_NRG_RATIO_Q8; VAD_N_BANDS]);
    }

    #[test]
    fn encoder_super_state_defaults_cover_channels() {
        let encoder = Encoder::default();
        assert_eq!(encoder.state_fxx.len(), ENCODER_NUM_CHANNELS);
        assert!(
            encoder
                .state_fxx
                .iter()
                .all(|channel| *channel.common() == EncoderStateCommon::default())
        );
        assert_eq!(encoder.n_channels_api, 1);
        assert_eq!(encoder.n_channels_internal, 1);
        assert_eq!(encoder.n_prev_channels_internal, 1);
        assert_eq!(encoder.n_bits_used_lbrr, 0);
        assert_eq!(encoder.n_bits_exceeded, 0);
        assert_eq!(encoder.time_since_switch_allowed_ms, 0);
        assert!(!encoder.allow_bandwidth_switch);
        assert!(!encoder.prev_decode_only_middle);
    }
}