Skip to main content

ruopus/celt/
encoder.rs

1//! A CELT encoder (RFC 6716 §5.3): mono or stereo, with transient
2//! detection and short blocks.
3//!
4//! The encoder now runs the full analysis chain - transient detection and
5//! short blocks, the pitch pre-filter (comb whitening), per-band time/
6//! frequency resolution, dynamic-allocation boosts, spreading and
7//! allocation trim - leaving full stereo (no intensity collapse, no dual
8//! stereo) and VBR as the remaining conservative choices. The bit-exact
9//! machinery (energy quantisation, allocation, theta splits, PVQ) mirrors
10//! the decoder's exactly, and every decision is a legal one, so the
11//! bitstream is fully conformant.
12
13use alloc::vec;
14use alloc::vec::Vec;
15
16use super::bands::encode::quant_all_bands_enc;
17use super::bands::haar1;
18use super::decoder::{COMB_GAINS, TF_SELECT_TABLE};
19use super::energy::EnergyState;
20use super::laplace::ec_laplace_encode;
21use super::mdct::MdctLookup;
22use super::modes::{BETA_COEF, BETA_INTRA, E_MEANS, E_PROB_MODEL, EBANDS, LOG_N, MAX_FINE_BITS, NB_EBANDS, PRED_COEF};
23use super::pitch::{COMBFILTER_MAXPERIOD, COMBFILTER_MINPERIOD, pitch_downsample, pitch_search, remove_doubling};
24use super::rate::{AllocEc, BITRES, compute_allocation, init_caps};
25use super::tables::WINDOW120;
26use super::vq::Spread;
27use crate::range::RangeEncoder;
28
29/// Samples per shortest MDCT block.
30const SHORT_MDCT_SIZE: usize = 120;
31/// MDCT overlap.
32const OVERLAP: usize = 120;
33/// Pre-emphasis coefficient of the standard mode.
34const PREEMPH_COEF: f32 = 0.850_006_1;
35/// Spreading decision ICDF (`spread_icdf`).
36const SPREAD_ICDF: [u8; 4] = [25, 23, 2, 0];
37/// Allocation trim ICDF (`trim_icdf`).
38const TRIM_ICDF: [u8; 11] = [126, 124, 119, 109, 87, 41, 19, 9, 4, 2, 0];
39/// Post-filter tapset ICDF (`tapset_icdf`).
40const TAPSET_ICDF: [u8; 3] = [2, 1, 0];
41/// Intensity-stereo band thresholds by rate (kb/s) and their hysteresis.
42const INTENSITY_THRESHOLDS: [f32; 21] = [
43    1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 16.0, 24.0, 36.0, 44.0, 50.0, 56.0, 62.0, 67.0, 72.0, 79.0, 88.0, 106.0,
44    134.0,
45];
46const INTENSITY_HYSTERESIS: [f32; 21] = [
47    1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0, 4.0, 5.0, 6.0, 8.0, 8.0,
48];
49
50/// A CELT encoder at 48 kHz (mono or stereo).
51pub struct CeltEncoder {
52    /// Channel count (1 or 2).
53    channels: usize,
54    /// Pre-emphasis filter memory, per channel.
55    preemph_mem: [f32; 2],
56    /// The previous frame's tail (`in_mem`), `OVERLAP` samples per channel.
57    in_mem: [[f32; OVERLAP]; 2],
58    /// Energy predictor state (`oldBandE`), shared semantics with the
59    /// decoder.
60    energy: EnergyState,
61    /// The previous frame's coarse-energy quantisation error per band
62    /// (`energyError`, clamped to ±0.5), used to stabilise the gain.
63    energy_error: [[f32; NB_EBANDS]; 2],
64    /// Frames encoded (the first is coded intra).
65    frames: u64,
66    /// Consecutive transient frames (`consec_transient`), steering the
67    /// anti-collapse decision.
68    consec_transient: u32,
69    /// Whether the last frame was coded with short blocks (diagnostic).
70    last_transient: bool,
71    /// Recursively averaged tonality metric (`tonal_average`) for the
72    /// spreading decision.
73    tonal_average: i32,
74    /// The previous frame's spreading decision (`spread_decision`).
75    spread_decision: i32,
76    /// The previous frame's intensity-stereo band (`intensity`), for the
77    /// hysteresis decision.
78    intensity: usize,
79    /// Target bitrate in bits/s for VBR (`None` = CBR, fill `nb_bytes`).
80    target_bitrate: Option<u32>,
81    /// The previous frame's coded-band count (`lastCodedBands`), an input to
82    /// the VBR target.
83    last_coded_bands: usize,
84    /// Pre-filter history (`prefilter_mem`): the last `COMBFILTER_MAXPERIOD`
85    /// pre-emphasised samples per channel.
86    prefilter_mem: [Vec<f32>; 2],
87    /// The previous frame's pre-filter period, gain and tapset (continuity
88    /// for the comb cross-fade).
89    prefilter_period: usize,
90    prefilter_gain: f32,
91    prefilter_tapset: usize,
92    /// The most recent pre-filter decision (diagnostic, see
93    /// [`CeltEncoder::last_pitch`]).
94    last_pitch: usize,
95    last_pitch_gain: f32,
96    /// Range state of the last encoded frame (the bit-exactness oracle).
97    final_range: u32,
98    mdct: MdctLookup,
99    /// Reused per-frame scratch (pre-emphasis input, MDCT output) so the hot
100    /// encode path does not reallocate and re-zero them every frame. Both are
101    /// fully overwritten before use, so they carry no state between frames.
102    scratch_inputs: Vec<f32>,
103    scratch_freq: Vec<f32>,
104    /// Reused per-channel pre-filter history+frame buffers (`pre`).
105    scratch_pre: [Vec<f32>; 2],
106    /// Encode complexity 0-10; gates the pre-filter pitch search (≥5), tf
107    /// analysis (≥2), the second transient MDCT (≥8), the two-pass coarse
108    /// energy (≥4) and spreading (none at 0).
109    complexity: u8,
110}
111
112impl Default for CeltEncoder {
113    fn default() -> Self {
114        Self::new()
115    }
116}
117
118impl CeltEncoder {
119    /// Creates a mono 48 kHz encoder.
120    #[must_use]
121    pub fn new() -> Self {
122        Self::with_channels(1)
123    }
124
125    /// Creates a 48 kHz encoder with 1 or 2 channels.
126    ///
127    /// # Panics
128    ///
129    /// Panics unless `channels` is 1 or 2.
130    #[must_use]
131    pub fn with_channels(channels: usize) -> Self {
132        assert!(channels == 1 || channels == 2, "channels must be 1 or 2");
133        CeltEncoder {
134            channels,
135            preemph_mem: [0.0; 2],
136            in_mem: [[0.0; OVERLAP]; 2],
137            energy: EnergyState::default(),
138            energy_error: [[0.0; NB_EBANDS]; 2],
139            frames: 0,
140            consec_transient: 0,
141            last_transient: false,
142            tonal_average: 256,
143            spread_decision: Spread::Normal as i32,
144            intensity: 0,
145            target_bitrate: None,
146            last_coded_bands: 0,
147            prefilter_mem: [vec![0.0; COMBFILTER_MAXPERIOD], vec![0.0; COMBFILTER_MAXPERIOD]],
148            prefilter_period: COMBFILTER_MINPERIOD,
149            prefilter_gain: 0.0,
150            prefilter_tapset: 0,
151            last_pitch: COMBFILTER_MINPERIOD,
152            last_pitch_gain: 0.0,
153            final_range: 0,
154            mdct: MdctLookup::new(1920),
155            scratch_inputs: Vec::new(),
156            scratch_freq: Vec::new(),
157            scratch_pre: [Vec::new(), Vec::new()],
158            complexity: 10,
159        }
160    }
161
162    /// Sets the encode complexity 0-10 (clamped), gating the analysis stages.
163    pub const fn set_complexity(&mut self, complexity: u8) {
164        self.complexity = if complexity > 10 { 10 } else { complexity };
165    }
166
167    /// Encodes one fullband frame of `pcm` (interleaved f32 in `[-1, 1]`;
168    /// 120, 240, 480 or 960 samples per channel at 48 kHz) into `nb_bytes`.
169    ///
170    /// # Panics
171    ///
172    /// Panics on invalid frame sizes or byte budgets outside 2..=1275.
173    pub fn encode_frame(&mut self, pcm: &[f32], nb_bytes: usize) -> Vec<u8> {
174        self.encode_frame_bw(pcm, nb_bytes, NB_EBANDS)
175    }
176
177    /// Encodes one frame coding bands `0..end` (`end` selects the CELT
178    /// bandwidth: 13 = narrowband, 17 = wideband, 19 = super-wideband,
179    /// 21 = fullband). The bands above `end` are left for the decoder to
180    /// fill with folded noise.
181    ///
182    /// # Panics
183    ///
184    /// Panics on invalid frame sizes, byte budgets outside 2..=1275, or
185    /// `end` outside 1..=21.
186    pub fn encode_frame_bw(&mut self, pcm: &[f32], nb_bytes: usize, end: usize) -> Vec<u8> {
187        let mut enc = RangeEncoder::new(nb_bytes);
188        let vbr = self.target_bitrate.is_some();
189        self.encode_core(&mut enc, pcm, 0, end, nb_bytes, vbr);
190        self.final_range = enc.range_size();
191        enc.finalize().expect("budget enforced by construction")
192    }
193
194    /// Encodes the high band (bands `17..end`) of a hybrid packet into the
195    /// shared range coder `enc`, which SILK has already written to. Does not
196    /// finalise. `nb_bytes` is the whole packet's byte budget; the allocation
197    /// derives CELT's share from the bits SILK already spent.
198    pub(crate) fn encode_hybrid_into(&mut self, enc: &mut RangeEncoder, pcm: &[f32], nb_bytes: usize, end: usize) {
199        self.encode_core(enc, pcm, 17, end, nb_bytes, false);
200    }
201
202    /// Encodes CELT bands `start..end` into the (possibly shared) range coder
203    /// `enc`, without creating or finalising it. With `start == 0` this is the
204    /// CELT-only path; with `start == 17` it is the high band of a hybrid
205    /// packet, continuing the coder SILK has already written to (so the
206    /// silence flag - coded only at `tell()==1` - and the post-filter are
207    /// skipped, and VBR shrinking is disabled). `nb_bytes` is the whole
208    /// packet's byte budget; the allocation derives CELT's share from the
209    /// bits already spent.
210    #[allow(clippy::too_many_arguments, reason = "shared CELT-only/hybrid core")]
211    fn encode_core(
212        &mut self,
213        enc: &mut RangeEncoder,
214        pcm: &[f32],
215        start: usize,
216        end: usize,
217        nb_bytes: usize,
218        vbr: bool,
219    ) {
220        let channels = self.channels;
221        assert!(pcm.len() % channels == 0, "interleaved frame length");
222        assert!((1..=NB_EBANDS).contains(&end), "end must be 1..=21");
223        let n = pcm.len() / channels;
224        let lm = (0..=3)
225            .find(|&lm| SHORT_MDCT_SIZE << lm == n)
226            .expect("frame size must be 120/240/480/960 per channel");
227        assert!((2..=1275).contains(&nb_bytes));
228        let m = 1usize << lm;
229
230        let total_bits = (nb_bytes * 8) as u32;
231
232        // Per channel: pre-emphasis into the signal domain (scale 32768),
233        // including the previous frame's overlap. `inputs` is planar.
234        let in_len = OVERLAP + n;
235        // Reuse the scratch buffer (fully overwritten below); `resize` is a
236        // no-op once it is the right length, so no per-frame zeroing.
237        let mut inputs = core::mem::take(&mut self.scratch_inputs);
238        inputs.resize(in_len * channels, 0.0);
239        for c in 0..channels {
240            let input = &mut inputs[c * in_len..(c + 1) * in_len];
241            input[..OVERLAP].copy_from_slice(&self.in_mem[c]);
242            let mut mem = self.preemph_mem[c];
243            for (dst, &s) in input[OVERLAP..].iter_mut().zip(pcm.iter().skip(c).step_by(channels)) {
244                let s = s * 32768.0;
245                *dst = s - mem;
246                mem = PREEMPH_COEF * s;
247            }
248            self.preemph_mem[c] = mem;
249        }
250
251        // Pitch pre-filter: estimate the pitch, comb-filter `inputs` in place
252        // to whiten the harmonic structure, and return the decision to code.
253        // Runs before the transient analysis and MDCT, which both see the
254        // filtered signal (matching the reference order).
255        let (pf_on, pitch_index, qg) = { self.prefilter_analysis(&mut inputs, in_len, n, channels, nb_bytes) };
256
257        // The MDCT overlap for the next frame is the filtered tail.
258        for c in 0..channels {
259            let input = &inputs[c * in_len..(c + 1) * in_len];
260            self.in_mem[c].copy_from_slice(&input[n..n + OVERLAP]);
261        }
262
263        // Transient decision (`transient_analysis`); the flag needs 3 bits.
264        let (is_transient, tf_estimate, tf_chan) = if lm > 0 && enc.tell() + 3 <= total_bits {
265            transient_analysis(&inputs, in_len, channels)
266        } else {
267            (false, 0.0, 0)
268        };
269
270        // Forward MDCT(s) per channel, then band energies (log domain
271        // relative to eMeans) and unit-norm band shapes. `x` is planar.
272        // `band_log_e2` is the long-block variant for dynalloc; it equals
273        // `band_log_e` for non-transient frames, otherwise comes from a
274        // second long-block MDCT.
275        let mut x = vec![0.0f32; n * channels];
276        let mut band_e = [[0.0f32; NB_EBANDS]; 2];
277        let mut band_log_e = [[0.0f32; NB_EBANDS]; 2];
278        let mut band_log_e2 = [[0.0f32; NB_EBANDS]; 2];
279        let mut freq = core::mem::take(&mut self.scratch_freq);
280        freq.resize(n, 0.0);
281        for c in 0..channels {
282            let input = &inputs[c * in_len..(c + 1) * in_len];
283            if is_transient {
284                // `m` short MDCTs, interleaved into `freq`.
285                let sub = n / m;
286                for b in 0..m {
287                    self.mdct.forward(
288                        &input[b * sub..b * sub + sub + OVERLAP],
289                        &mut freq[b..],
290                        &WINDOW120,
291                        OVERLAP,
292                        3,
293                        m,
294                    );
295                }
296                // Second long-block MDCT for `band_log_e2`.
297                let mut freq2 = vec![0.0f32; n];
298                self.mdct.forward(input, &mut freq2, &WINDOW120, OVERLAP, 3 - lm, 1);
299                for i in 0..end {
300                    let lo = m * EBANDS[i] as usize;
301                    let hi = m * EBANDS[i + 1] as usize;
302                    let sum = 1e-27f32 + crate::simd::dot(&freq2[lo..hi], &freq2[lo..hi]);
303                    band_log_e2[c][i] = sum.sqrt().log2() - E_MEANS[i] + 0.5 * lm as f32;
304                }
305            } else {
306                self.mdct.forward(input, &mut freq, &WINDOW120, OVERLAP, 3 - lm, 1);
307            }
308
309            let xc = &mut x[c * n..(c + 1) * n];
310            for i in 0..end {
311                let lo = m * EBANDS[i] as usize;
312                let hi = m * EBANDS[i + 1] as usize;
313                let sum = 1e-27f32 + crate::simd::dot(&freq[lo..hi], &freq[lo..hi]);
314                band_e[c][i] = sum.sqrt();
315                band_log_e[c][i] = band_e[c][i].log2() - E_MEANS[i];
316                if !is_transient {
317                    band_log_e2[c][i] = band_log_e[c][i];
318                }
319                let g = 1.0 / (1e-27 + band_e[c][i]);
320                for (xv, &f) in xc[lo..hi].iter_mut().zip(freq[lo..hi].iter()) {
321                    *xv = f * g;
322                }
323            }
324        }
325
326        // Dynalloc analysis (uses the previous frame's energies, so it must
327        // run before coarse-energy quantisation overwrites them); yields the
328        // boost targets plus the importance/spread weights for tf and spread.
329        let dyn_an = {
330            dynalloc_analysis(
331                &band_log_e,
332                &band_log_e2,
333                &self.energy.old_ebands,
334                start,
335                end,
336                channels,
337                lm,
338                nb_bytes,
339                is_transient,
340            )
341        };
342        let boost_targets = dyn_an.offsets;
343
344        // Per-band time/frequency resolution (`tf_analysis`), enabled above a
345        // low byte threshold. The result is the raw 0/1 flags plus tf_select.
346        let n0 = n;
347        let (mut tf_res, tf_select) = if nb_bytes >= 15 * channels && lm > 0 && self.complexity >= 2 {
348            let lambda = 80.max(20480 / nb_bytes as i32 + 2);
349            tf_analysis(
350                end,
351                is_transient,
352                lambda,
353                &x,
354                n0,
355                lm,
356                tf_estimate,
357                tf_chan,
358                &dyn_an.importance,
359            )
360        } else {
361            ([i32::from(is_transient); NB_EBANDS], usize::from(is_transient))
362        };
363
364        // The CBR-equivalent rate in bits/s, for trim and dynalloc tuning.
365        let equiv_rate =
366            (nb_bytes as i32) * 8 * 50 * (1 << (3 - lm)) - (40 * channels as i32 + 20) * ((400 >> lm) - 50);
367
368        // --- Bitstream, in the decoder's exact order. ---
369        // Silence flag - only at the very start of the packet (`tell() == 1`).
370        // In hybrid the coder already holds SILK data, so it is not coded.
371        if enc.tell() == 1 {
372            enc.encode_bit_logp(false, 15);
373        }
374        // Post-filter parameters. When on, the byte budget that enabled the
375        // pre-filter guarantees the 16-bit gate holds, so they are coded
376        // unconditionally (mirroring the reference); when off, the flag is
377        // gated like the decoder's read.
378        if start == 0 {
379            if pf_on {
380                enc.encode_bit_logp(true, 1);
381                let p = (pitch_index + 1) as u32;
382                // EC_ILOG(p) - 5, where EC_ILOG(p) = 32 - leading_zeros.
383                let octave = (32 - p.leading_zeros()) as i32 - 5;
384                enc.encode_uint(octave as u32, 6);
385                enc.encode_raw_bits(p - (16 << octave), (4 + octave) as u32);
386                enc.encode_raw_bits(qg as u32, 3);
387                if enc.tell() + 2 <= total_bits {
388                    enc.encode_icdf(self.prefilter_tapset, &TAPSET_ICDF, 2);
389                }
390            } else if enc.tell() + 16 <= total_bits {
391                enc.encode_bit_logp(false, 1);
392            }
393        }
394        // Transient flag.
395        if lm > 0 && enc.tell() + 3 <= total_bits {
396            enc.encode_bit_logp(is_transient, 3);
397        }
398        // Intra only on the first frame.
399        let intra = self.frames == 0;
400        if enc.tell() + 3 <= total_bits {
401            enc.encode_bit_logp(intra, 3);
402        }
403
404        // When the energy is stable, bias the coarse quantisation toward the
405        // previous frame's error so the gain stays steady (a constant offset
406        // beats fluctuation). Runs after dynalloc/tf, which saw the unbiased
407        // energies.
408        #[allow(clippy::needless_range_loop, reason = "indices address three per-band arrays")]
409        for c in 0..channels {
410            for i in start..end {
411                if (band_log_e[c][i] - self.energy.old_ebands[c][i]).abs() < 2.0 {
412                    band_log_e[c][i] -= 0.25 * self.energy_error[c][i];
413                }
414            }
415        }
416
417        // Coarse energy.
418        let mut error = [[0.0f32; NB_EBANDS]; 2];
419        {
420            self.quant_coarse_energy(enc, start, end, &band_log_e, &mut error, intra, lm, total_bits);
421        }
422
423        // Time-frequency coding (`tf_encode`): code each band's flag as the
424        // change from the previous band, code tf_select when it matters, and
425        // map the flags through the table to per-band tf_change values.
426        {
427            let mut budget = total_bits;
428            let mut tell = enc.tell();
429            let mut logp: u32 = if is_transient { 2 } else { 4 };
430            let tf_select_rsv = lm > 0 && tell + logp < budget;
431            budget -= u32::from(tf_select_rsv);
432            let mut tf_changed = false;
433            let mut curr = 0i32;
434            for r in tf_res.iter_mut().take(end).skip(start) {
435                if tell + logp <= budget {
436                    enc.encode_bit_logp(*r != curr, logp);
437                    tell = enc.tell();
438                    curr = *r;
439                    tf_changed |= curr != 0;
440                } else {
441                    *r = curr;
442                }
443                logp = if is_transient { 4 } else { 5 };
444            }
445            let base = 4 * usize::from(is_transient);
446            // Only code tf_select if it would make a difference.
447            let tf_select = if tf_select_rsv
448                && TF_SELECT_TABLE[lm][base + usize::from(tf_changed)]
449                    != TF_SELECT_TABLE[lm][base + 2 + usize::from(tf_changed)]
450            {
451                enc.encode_bit_logp(tf_select != 0, 1);
452                tf_select
453            } else {
454                0
455            };
456            for r in tf_res.iter_mut().take(end).skip(start) {
457                *r = TF_SELECT_TABLE[lm][base + 2 * tf_select + *r as usize];
458            }
459        }
460
461        // Spreading decision. At complexity 0 the reference forces SPREAD_NONE
462        // (no rotation), which also skips `exp_rotation` in the band loop.
463        let mut spread = Spread::Normal;
464        if enc.tell() + 4 <= total_bits {
465            let s = if self.complexity == 0 {
466                Spread::None as i32
467            } else {
468                spreading_decision(
469                    &x,
470                    n0,
471                    &mut self.tonal_average,
472                    self.spread_decision,
473                    end,
474                    channels,
475                    m,
476                    &dyn_an.spread_weight,
477                )
478            };
479            self.spread_decision = s;
480            spread = Spread::from_raw(s as u32);
481            enc.encode_icdf(s as usize, &SPREAD_ICDF, 5);
482        }
483
484        // Dynamic allocation: code each band's boost as a run of `1` flags
485        // (one per increment in `boost_targets`) terminated by a `0`,
486        // mirroring the decoder. `offsets[i]` becomes the boost in 8th bits.
487        let caps = init_caps(lm, channels);
488        let mut offsets = [0i32; NB_EBANDS];
489        let total_bits_frac = (total_bits << 3) as i64;
490        let mut total_boost = 0i64;
491        {
492            let mut dynalloc_logp = 6u32;
493            let mut tell_frac = i64::from(enc.tell_frac());
494            for i in start..end {
495                let width = (channels as i32 * i32::from(EBANDS[i + 1] - EBANDS[i])) << lm;
496                // 6 bits, but no more than 1 bit/sample and at least 1/8.
497                let quanta = (width << BITRES).min((6 << BITRES).max(width));
498                let mut dynalloc_loop_logp = dynalloc_logp;
499                let mut boost = 0i32;
500                let mut j = 0i32;
501                while tell_frac + (i64::from(dynalloc_loop_logp) << BITRES) < total_bits_frac - total_boost
502                    && boost < caps[i]
503                {
504                    let flag = j < boost_targets[i];
505                    enc.encode_bit_logp(flag, dynalloc_loop_logp);
506                    tell_frac = i64::from(enc.tell_frac());
507                    if !flag {
508                        break;
509                    }
510                    boost += quanta;
511                    total_boost += i64::from(quanta);
512                    dynalloc_loop_logp = 1;
513                    j += 1;
514                }
515                if j > 0 {
516                    dynalloc_logp = 2.max(dynalloc_logp - 1);
517                }
518                offsets[i] = boost;
519            }
520        }
521
522        // Allocation trim from the spectral tilt and transient estimate.
523        // The budget gate must discount the dynalloc boost, exactly as the
524        // decoder does (its `total_bits_frac` is decremented per boost).
525        let trim = if i64::from(enc.tell_frac()) + (6 << 3) <= total_bits_frac - total_boost {
526            let trim = alloc_trim_analysis(&band_log_e, end, channels, tf_estimate, equiv_rate);
527            enc.encode_icdf(trim as usize, &TRIM_ICDF, 7);
528            trim
529        } else {
530            5
531        };
532
533        // Stereo decisions: the first intensity-stereo band (rate-driven,
534        // with hysteresis) and whether to code the channels separately
535        // (dual stereo). Mono keeps full stereo / no dual.
536        let (intensity, dual_stereo) = if channels == 2 {
537            let dual = lm != 0 && stereo_analysis(&x, n, lm);
538            self.intensity = hysteresis_decision(
539                (equiv_rate / 1000) as f32,
540                &INTENSITY_THRESHOLDS,
541                &INTENSITY_HYSTERESIS,
542                self.intensity,
543            )
544            .clamp(start, end);
545            (self.intensity, dual)
546        } else {
547            (end, false)
548        };
549
550        // Variable bitrate: choose this frame's byte count from its bit
551        // target and shrink the range coder before allocation. (The header
552        // and energy were coded against the original budget, exactly as the
553        // reference does - safe because their budget checks only bite near
554        // exhaustion, far from the chosen size.)
555        let mut nb_bytes = nb_bytes;
556        if let Some(bitrate) = self.target_bitrate.filter(|_| vbr) {
557            let bitrate = bitrate as i32;
558            // Cap per frame size (the allocator can't exceed ~510 kb/s).
559            nb_bytes = nb_bytes.min(1275 >> (3 - lm));
560            let vbr_rate = ((i64::from(bitrate) * n as i64) / 6000) as i32; // 8th bits/frame
561            let base_target = vbr_rate - ((40 * channels as i32 + 20) << BITRES);
562            let mut target = i64::from(compute_vbr(
563                base_target,
564                lm,
565                channels,
566                bitrate,
567                self.last_coded_bands,
568                intensity,
569                0.0,
570                dyn_an.tot_boost,
571                tf_estimate,
572                dyn_an.max_depth,
573                0.0,
574            ));
575            let tell = i64::from(enc.tell_frac());
576            target += tell;
577            let min_allowed = ((tell + total_boost + ((1 << (BITRES + 3)) - 1)) >> (BITRES + 3)) + 2;
578            let nb_avail = ((target + (1 << (BITRES + 2))) >> (BITRES + 3)).clamp(min_allowed, nb_bytes as i64);
579            nb_bytes = nb_avail as usize;
580            enc.shrink(nb_bytes);
581        }
582
583        // The implicit allocation (shared with the decoder).
584        let mut bits = (((nb_bytes * 8) << 3) as i32) - enc.tell_frac() as i32 - 1;
585        let anti_collapse_rsv = if is_transient && lm >= 2 && bits >= ((lm as i32 + 2) << 3) {
586            1 << 3
587        } else {
588            0
589        };
590        bits -= anti_collapse_rsv;
591        let alloc = compute_allocation(
592            &mut AllocEc::Enc {
593                enc: &mut *enc,
594                signal_bandwidth: end - 1,
595                intensity,
596                dual_stereo,
597            },
598            start,
599            end,
600            &offsets,
601            &caps,
602            trim,
603            bits,
604            channels,
605            lm,
606        );
607        self.last_coded_bands = alloc.coded_bands;
608
609        // Fine energy.
610        self.quant_fine_energy(enc, start, end, &mut error, &alloc.fine_quant);
611
612        // Band shapes.
613        let total = ((nb_bytes * 8) << 3) as i32 - anti_collapse_rsv;
614        let (xs, ys) = x.split_at_mut(n);
615        quant_all_bands_enc(
616            enc,
617            start,
618            end,
619            xs,
620            if channels == 2 { Some(ys) } else { None },
621            &alloc.shape_bits,
622            is_transient,
623            spread,
624            alloc.dual_stereo,
625            alloc.intensity,
626            &tf_res,
627            total,
628            alloc.balance,
629            lm,
630            alloc.coded_bands,
631            &band_e,
632        );
633
634        // Anti-collapse: on unless this is a long transient run.
635        if anti_collapse_rsv > 0 {
636            enc.encode_raw_bits(u32::from(self.consec_transient < 2), 1);
637        }
638
639        // Finalise the leftover bits into extra fine energy.
640        let bits_left = nb_bytes as i32 * 8 - enc.tell() as i32;
641        self.quant_energy_finalise(
642            enc,
643            start,
644            end,
645            &mut error,
646            &alloc.fine_quant,
647            &alloc.fine_priority,
648            bits_left,
649        );
650
651        // Store this frame's residual energy error (clamped) for the next
652        // frame's gain-stabilisation bias.
653        #[allow(clippy::needless_range_loop, reason = "indices address two per-band arrays")]
654        for c in 0..channels {
655            for i in start..end {
656                self.energy_error[c][i] = error[c][i].clamp(-0.5, 0.5);
657            }
658        }
659
660        if is_transient {
661            self.consec_transient += 1;
662        } else {
663            self.consec_transient = 0;
664        }
665        self.last_transient = is_transient;
666        self.frames += 1;
667
668        // Return the scratch buffers for the next frame to reuse.
669        self.scratch_inputs = inputs;
670        self.scratch_freq = freq;
671    }
672
673    /// The range state after the last encoded frame; a conformant decoder
674    /// finishes the frame with this exact value.
675    #[must_use]
676    pub const fn final_range(&self) -> u32 {
677        self.final_range
678    }
679
680    /// Sets the VBR target bitrate in bits/s (`None` restores CBR, which
681    /// fills the `nb_bytes` budget exactly). In VBR the `nb_bytes` passed to
682    /// `encode_frame*` is an upper bound; each frame is shrunk to its own
683    /// target.
684    pub const fn set_target_bitrate(&mut self, bitrate: Option<u32>) {
685        self.target_bitrate = bitrate;
686    }
687
688    /// Whether the last frame was detected as a transient and coded with
689    /// short blocks.
690    #[must_use]
691    pub const fn last_transient(&self) -> bool {
692        self.last_transient
693    }
694
695    /// The pitch period (samples) and gain the pre-filter analysis chose for
696    /// the last frame. Currently informational - the comb filter and its
697    /// post-filter bitstream coding are not yet applied.
698    #[must_use]
699    pub const fn last_pitch(&self) -> (usize, f32) {
700        (self.last_pitch, self.last_pitch_gain)
701    }
702
703    /// Pitch pre-filter (`run_prefilter`): estimates the pitch period/gain,
704    /// applies the rate/continuity gain threshold and the 8-level gain
705    /// quantisation, comb-filters `inputs` in place (the FIR whitening, with
706    /// a windowed cross-fade from the previous frame's parameters), advances
707    /// the pre-filter history, and returns `(pf_on, pitch_index, qg)` for the
708    /// bitstream.
709    #[allow(
710        clippy::needless_range_loop,
711        reason = "channel index also addresses prefilter_mem/inputs"
712    )]
713    fn prefilter_analysis(
714        &mut self,
715        inputs: &mut [f32],
716        in_len: usize,
717        n: usize,
718        channels: usize,
719        nb_bytes: usize,
720    ) -> (bool, usize, i32) {
721        // pre[c] = [prefilter history | this frame's new samples].
722        let pre_len = COMBFILTER_MAXPERIOD + n;
723        // Reuse the scratch buffers (fully overwritten for the used channels).
724        let mut pre = core::mem::take(&mut self.scratch_pre);
725        for c in 0..channels {
726            pre[c].resize(pre_len, 0.0);
727            pre[c][..COMBFILTER_MAXPERIOD].copy_from_slice(&self.prefilter_mem[c]);
728            pre[c][COMBFILTER_MAXPERIOD..].copy_from_slice(&inputs[c * in_len + OVERLAP..c * in_len + OVERLAP + n]);
729        }
730
731        let enabled = nb_bytes > 12 * channels && self.complexity >= 5;
732        let (pitch_index, mut gain1) = if enabled {
733            let refs: Vec<&[f32]> = pre[..channels].iter().map(Vec::as_slice).collect();
734            let x_lp = pitch_downsample(&refs, pre_len);
735            let max_pitch = COMBFILTER_MAXPERIOD - 3 * COMBFILTER_MINPERIOD;
736            let coarse = pitch_search(&x_lp[COMBFILTER_MAXPERIOD >> 1..], &x_lp, n, max_pitch);
737            let mut pitch_index = COMBFILTER_MAXPERIOD - coarse;
738            let (gain, refined) = remove_doubling(
739                &x_lp,
740                COMBFILTER_MAXPERIOD,
741                COMBFILTER_MINPERIOD,
742                n,
743                pitch_index,
744                self.prefilter_period,
745                self.prefilter_gain,
746            );
747            pitch_index = refined.min(COMBFILTER_MAXPERIOD - 2);
748            (pitch_index, 0.7 * gain)
749        } else {
750            (COMBFILTER_MINPERIOD, 0.0)
751        };
752
753        // Gain threshold for enabling the (post-)filter, adjusted by rate and
754        // continuity.
755        let mut pf_threshold = 0.2f32;
756        if (pitch_index as i32 - self.prefilter_period as i32).abs() * 10 > pitch_index as i32 {
757            pf_threshold += 0.2;
758        }
759        if nb_bytes < 25 {
760            pf_threshold += 0.1;
761        }
762        if nb_bytes < 35 {
763            pf_threshold += 0.1;
764        }
765        if self.prefilter_gain > 0.4 {
766            pf_threshold -= 0.1;
767        }
768        if self.prefilter_gain > 0.55 {
769            pf_threshold -= 0.1;
770        }
771        pf_threshold = pf_threshold.max(0.2);
772        let mut pf_on = false;
773        let mut qg = 0i32;
774        if gain1 < pf_threshold {
775            gain1 = 0.0;
776        } else {
777            // Snap to the previous gain to avoid needless changes, then
778            // quantise to one of eight levels.
779            if (gain1 - self.prefilter_gain).abs() < 0.1 {
780                gain1 = self.prefilter_gain;
781            }
782            qg = ((0.5 + gain1 * 32.0 / 3.0).floor() as i32 - 1).clamp(0, 7);
783            gain1 = 0.093_75 * (qg + 1) as f32;
784            pf_on = true;
785        }
786
787        // Comb-filter the new samples in place: a FIR whitening reading the
788        // unfiltered `pre` history, cross-fading from the previous frame's
789        // (period, -gain) to this frame's over the overlap. With the standard
790        // mode's `offset == 0` this is a single call over all `n` samples.
791        let old_period = self.prefilter_period.max(COMBFILTER_MINPERIOD);
792        let old_tapset = self.prefilter_tapset;
793        let new_tapset = self.prefilter_tapset;
794        for c in 0..channels {
795            let dst = &mut inputs[c * in_len + OVERLAP..c * in_len + OVERLAP + n];
796            comb_filter_prefilter(
797                dst,
798                &pre[c],
799                COMBFILTER_MAXPERIOD,
800                old_period,
801                pitch_index.max(COMBFILTER_MINPERIOD),
802                n,
803                -self.prefilter_gain,
804                -gain1,
805                old_tapset,
806                new_tapset,
807            );
808        }
809
810        // Advance the pre-filter history (new = last MAXPERIOD of pre[c]).
811        for c in 0..channels {
812            self.prefilter_mem[c].copy_from_slice(&pre[c][n..n + COMBFILTER_MAXPERIOD]);
813        }
814        self.prefilter_period = pitch_index.max(COMBFILTER_MINPERIOD);
815        self.prefilter_gain = gain1;
816        self.last_pitch = pitch_index;
817        self.last_pitch_gain = gain1;
818        self.scratch_pre = pre;
819        (pf_on, pitch_index, qg)
820    }
821
822    /// `quant_coarse_energy` (float build): time/frequency-predicted,
823    /// Laplace-coded 6 dB energy quantisation, channel-interleaved per band.
824    #[allow(clippy::too_many_arguments, reason = "mirrors quant_coarse_energy_impl")]
825    fn quant_coarse_energy(
826        &mut self,
827        enc: &mut RangeEncoder,
828        start: usize,
829        end: usize,
830        band_log_e: &[[f32; NB_EBANDS]; 2],
831        error: &mut [[f32; NB_EBANDS]; 2],
832        intra: bool,
833        lm: usize,
834        budget: u32,
835    ) {
836        let channels = self.channels;
837        let prob = &E_PROB_MODEL[lm][usize::from(intra)];
838        let (coef, beta) = if intra {
839            (0.0, BETA_INTRA)
840        } else {
841            (PRED_COEF[lm], BETA_COEF[lm])
842        };
843        let max_decay = 16.0f32.min((budget as f32 / 3.0).max(0.0));
844
845        let mut prev = [0.0f32; 2];
846        for i in start..end {
847            for c in 0..channels {
848                let x = band_log_e[c][i];
849                let old_e = self.energy.old_ebands[c][i].max(-9.0);
850                let f = x - coef * old_e - prev[c];
851                let mut qi = (0.5 + f).floor() as i32;
852                let decay_bound = self.energy.old_ebands[c][i].max(-28.0) - max_decay;
853                // Prevent energy from dropping too fast.
854                if qi < 0 && x < decay_bound {
855                    qi += (decay_bound - x) as i32;
856                    if qi > 0 {
857                        qi = 0;
858                    }
859                }
860                let tell = enc.tell();
861                let bits_left = budget as i32 - tell as i32 - 3 * channels as i32 * (end - i) as i32;
862                if i != start && bits_left < 30 {
863                    if bits_left < 24 {
864                        qi = qi.min(1);
865                    }
866                    if bits_left < 16 {
867                        qi = qi.max(-1);
868                    }
869                }
870                // Signed, like the reference: in hybrid mode `tell` can already
871                // exceed `budget` (CELT continues after SILK), so an unsigned
872                // `budget - tell` would underflow.
873                let avail = i64::from(budget) - i64::from(tell);
874                let qi = if avail >= 15 {
875                    let pi = 2 * i.min(20);
876                    ec_laplace_encode(enc, qi, u32::from(prob[pi]) << 7, u32::from(prob[pi + 1]) << 6)
877                } else if avail >= 2 {
878                    let qi = qi.clamp(-1, 1);
879                    const SMALL_ENERGY_ICDF: [u8; 3] = [2, 1, 0];
880                    enc.encode_icdf(((2 * qi) ^ -i32::from(qi < 0)) as usize, &SMALL_ENERGY_ICDF, 2);
881                    qi
882                } else if avail >= 1 {
883                    let qi = qi.min(0);
884                    enc.encode_bit_logp(qi != 0, 1);
885                    qi
886                } else {
887                    -1
888                };
889                error[c][i] = f - qi as f32;
890                let q = qi as f32;
891                self.energy.old_ebands[c][i] = coef * old_e + prev[c] + q;
892                prev[c] = prev[c] + q - beta * q;
893            }
894        }
895        if channels == 1 {
896            self.energy.old_ebands[1] = self.energy.old_ebands[0];
897        }
898    }
899
900    #[allow(
901        clippy::needless_range_loop,
902        reason = "channel indices mirror the reference's c loop"
903    )]
904    fn quant_fine_energy(
905        &mut self,
906        enc: &mut RangeEncoder,
907        start: usize,
908        end: usize,
909        error: &mut [[f32; NB_EBANDS]; 2],
910        fine_quant: &[i32; NB_EBANDS],
911    ) {
912        let channels = self.channels;
913        for i in start..end {
914            if fine_quant[i] <= 0 {
915                continue;
916            }
917            let frac = 1 << fine_quant[i];
918            for c in 0..channels {
919                let q2 = (((error[c][i] + 0.5) * frac as f32).floor() as i32).clamp(0, frac - 1);
920                enc.encode_raw_bits(q2 as u32, fine_quant[i] as u32);
921                let offset = (q2 as f32 + 0.5) * (1 << (14 - fine_quant[i])) as f32 / 16384.0 - 0.5;
922                self.energy.old_ebands[c][i] += offset;
923                error[c][i] -= offset;
924            }
925        }
926        if channels == 1 {
927            self.energy.old_ebands[1] = self.energy.old_ebands[0];
928        }
929    }
930
931    #[allow(clippy::too_many_arguments, reason = "mirrors quant_energy_finalise")]
932    #[allow(
933        clippy::needless_range_loop,
934        reason = "channel indices mirror the reference's c loop"
935    )]
936    fn quant_energy_finalise(
937        &mut self,
938        enc: &mut RangeEncoder,
939        start: usize,
940        end: usize,
941        error: &mut [[f32; NB_EBANDS]; 2],
942        fine_quant: &[i32; NB_EBANDS],
943        fine_priority: &[bool; NB_EBANDS],
944        mut bits_left: i32,
945    ) {
946        let channels = self.channels;
947        for prio in [false, true] {
948            let mut i = start;
949            while i < end && bits_left >= channels as i32 {
950                if fine_quant[i] >= MAX_FINE_BITS || fine_priority[i] != prio {
951                    i += 1;
952                    continue;
953                }
954                for c in 0..channels {
955                    let q2 = i32::from(error[c][i] >= 0.0);
956                    enc.encode_raw_bits(q2 as u32, 1);
957                    let offset = (q2 as f32 - 0.5) * (1 << (14 - fine_quant[i] - 1)) as f32 / 16384.0;
958                    self.energy.old_ebands[c][i] += offset;
959                    error[c][i] -= offset;
960                    bits_left -= 1;
961                }
962                i += 1;
963            }
964        }
965        if channels == 1 {
966            self.energy.old_ebands[1] = self.energy.old_ebands[0];
967        }
968    }
969}
970
971/// `transient_analysis` (float build): high-pass the input, apply forward
972/// (6.7 dB/ms) and backward (13.9 dB/ms) masking decays, and compare the
973/// frame energy against the harmonic mean of the masked energy - a
974/// bitrate-normalised temporal noise-to-mask ratio. `inputs` is the planar
975/// per-channel pre-emphasised signal including the overlap (`len` samples
976/// per channel). Returns `(is_transient, tf_estimate, tf_chan)`: the
977/// transient flag, an arbitrary VBR/trim metric, and the channel with the
978/// strongest transient (the one `tf_analysis` examines).
979fn transient_analysis(inputs: &[f32], len: usize, channels: usize) -> (bool, f32, usize) {
980    /// `inv_table`: 6*64/x, trained on real data to minimise average error.
981    const INV_TABLE: [u8; 128] = [
982        255, 255, 156, 110, 86, 70, 59, 51, 45, 40, 37, 33, 31, 28, 26, 25, 23, 22, 21, 20, 19, 18, 17, 16, 16, 15, 15,
983        14, 13, 13, 12, 12, 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 6, 6, 6,
984        6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
985        4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2,
986    ];
987    const EPSILON: f32 = 1e-15;
988    // Forward masking: 6.7 dB/ms.
989    const FORWARD_DECAY: f32 = 0.0625;
990
991    let len2 = len / 2;
992    let mut mask_metric = 0i32;
993    let mut tf_chan = 0usize;
994    let mut tmp = vec![0.0f32; len];
995    for c in 0..channels {
996        let input = &inputs[c * len..(c + 1) * len];
997
998        // High-pass filter: (1 - 2z^-1 + z^-2) / (1 - z^-1 + 0.5 z^-2).
999        let (mut mem0, mut mem1) = (0.0f32, 0.0f32);
1000        for (t, &x) in tmp.iter_mut().zip(input.iter()) {
1001            let y = mem0 + x;
1002            let mem00 = mem0;
1003            mem0 = mem0 - x + 0.5 * mem1;
1004            mem1 = x - mem00;
1005            *t = y;
1006        }
1007        // The first few samples are bad: the memory is not propagated.
1008        tmp[..12].fill(0.0);
1009
1010        // Forward pass for the post-echo threshold, grouping by two.
1011        let mut mean = 0.0f32;
1012        let mut mem = 0.0f32;
1013        for i in 0..len2 {
1014            let x2 = tmp[2 * i] * tmp[2 * i] + tmp[2 * i + 1] * tmp[2 * i + 1];
1015            mean += x2;
1016            mem = x2 + (1.0 - FORWARD_DECAY) * mem;
1017            tmp[i] = FORWARD_DECAY * mem;
1018        }
1019
1020        // Backward pass for the pre-echo threshold.
1021        let mut mem = 0.0f32;
1022        let mut max_e = 0.0f32;
1023        for i in (0..len2).rev() {
1024            mem = tmp[i] + 0.875 * mem;
1025            tmp[i] = 0.125 * mem;
1026            max_e = max_e.max(0.125 * mem);
1027        }
1028
1029        // Frame energy: the geometric mean of the energy and half the max.
1030        let mean = (mean * max_e * 0.5 * len2 as f32).sqrt();
1031        let norm = len2 as f32 / (EPSILON + mean);
1032        // Harmonic mean over 1/4 of the samples, away from the boundaries.
1033        let mut unmask = 0i32;
1034        let mut i = 12;
1035        while i + 5 < len2 {
1036            let id = (64.0 * norm * (tmp[i] + EPSILON)).floor().clamp(0.0, 127.0) as usize;
1037            unmask += i32::from(INV_TABLE[id]);
1038            i += 4;
1039        }
1040        // Normalise for the 1/4 sampling and the factor 6 in the table.
1041        let unmask = 64 * unmask * 4 / (6 * (len2 as i32 - 17));
1042        if unmask > mask_metric {
1043            tf_chan = c;
1044            mask_metric = unmask;
1045        }
1046    }
1047    let is_transient = mask_metric > 200;
1048    // Arbitrary metric for VBR boost and trim (float build).
1049    let tf_max = 0.0f32.max((27.0 * mask_metric as f32).sqrt() - 42.0);
1050    let tf_estimate = 0.0f32.max(0.0069 * 163.0f32.min(tf_max) - 0.139).sqrt();
1051    (is_transient, tf_estimate, tf_chan)
1052}
1053
1054/// The pre-filter comb (`comb_filter`, encoder direction): a 3-tap FIR
1055/// whitening `dst[i] = src[base+i] + g·(taps of src around base+i-T)`,
1056/// cross-fading from `(t0, g0, tapset0)` to `(t1, g1, tapset1)` over the
1057/// MDCT overlap (windowed), then constant. `src` is the *unfiltered* history
1058/// plus the new samples, so this is non-recursive (unlike the decoder's
1059/// in-place post-filter). Gains are passed negated by the caller.
1060#[allow(clippy::too_many_arguments, reason = "mirrors the reference comb-filter signature")]
1061fn comb_filter_prefilter(
1062    dst: &mut [f32],
1063    src: &[f32],
1064    base: usize,
1065    t0: usize,
1066    t1: usize,
1067    n: usize,
1068    g0: f32,
1069    g1: f32,
1070    tapset0: usize,
1071    tapset1: usize,
1072) {
1073    if g0 == 0.0 && g1 == 0.0 {
1074        dst[..n].copy_from_slice(&src[base..base + n]);
1075        return;
1076    }
1077    let t0 = t0.max(COMBFILTER_MINPERIOD);
1078    let t1 = t1.max(COMBFILTER_MINPERIOD);
1079    let g00 = g0 * COMB_GAINS[tapset0][0];
1080    let g01 = g0 * COMB_GAINS[tapset0][1];
1081    let g02 = g0 * COMB_GAINS[tapset0][2];
1082    let g10 = g1 * COMB_GAINS[tapset1][0];
1083    let g11 = g1 * COMB_GAINS[tapset1][1];
1084    let g12 = g1 * COMB_GAINS[tapset1][2];
1085
1086    // No cross-fade needed when nothing changed.
1087    let overlap = if g0 == g1 && t0 == t1 && tapset0 == tapset1 {
1088        0
1089    } else {
1090        OVERLAP.min(n)
1091    };
1092    let mut x1 = src[base + 1 - t1];
1093    let mut x2 = src[base - t1];
1094    let mut x3 = src[base - t1 - 1];
1095    let mut x4 = src[base - t1 - 2];
1096
1097    let mut i = 0usize;
1098    while i < overlap {
1099        let x0 = src[base + i + 2 - t1];
1100        let f = WINDOW120[i] * WINDOW120[i];
1101        dst[i] = src[base + i]
1102            + (1.0 - f) * g00 * src[base + i - t0]
1103            + (1.0 - f) * g01 * (src[base + i + 1 - t0] + src[base + i - 1 - t0])
1104            + (1.0 - f) * g02 * (src[base + i + 2 - t0] + src[base + i - 2 - t0])
1105            + f * g10 * x2
1106            + f * g11 * (x1 + x3)
1107            + f * g12 * (x0 + x4);
1108        x4 = x3;
1109        x3 = x2;
1110        x2 = x1;
1111        x1 = x0;
1112        i += 1;
1113    }
1114    if g1 == 0.0 {
1115        dst[overlap..n].copy_from_slice(&src[base + overlap..base + n]);
1116        return;
1117    }
1118    while i < n {
1119        dst[i] = src[base + i]
1120            + g10 * src[base + i - t1]
1121            + g11 * (src[base + i + 1 - t1] + src[base + i - 1 - t1])
1122            + g12 * (src[base + i + 2 - t1] + src[base + i - 2 - t1]);
1123        i += 1;
1124    }
1125}
1126
1127/// `dynalloc_analysis` (float build, non-LFE, no surround, no analysis
1128/// module): computes per-band boost *targets* (the count of dynalloc
1129/// increments to code) from a follower of the band energies. `band_log_e`
1130/// is the current frame's per-channel log energy, `band_log_e2` the
1131/// long-block variant (equal to `band_log_e` for non-transient frames),
1132/// and `old_ebands` the previous frame's energies. Boosts only kick in
1133/// once the budget is large enough.
1134/// `compute_vbr` (float build, no analysis/surround/lfe): the per-frame bit
1135/// target in 8th bits, boosted by dynalloc and transients, reduced by the
1136/// stereo-saving estimate, then floored and capped at twice the base. This
1137/// is the unconstrained-VBR target; the reservoir/drift logic is only used
1138/// for constrained VBR, which this encoder does not offer yet.
1139#[allow(clippy::too_many_arguments, reason = "mirrors the reference signature")]
1140fn compute_vbr(
1141    base_target: i32,
1142    lm: usize,
1143    channels: usize,
1144    bitrate: i32,
1145    last_coded_bands: usize,
1146    intensity: usize,
1147    stereo_saving: f32,
1148    tot_boost: i32,
1149    tf_estimate: f32,
1150    max_depth: f32,
1151    temporal_vbr: f32,
1152) -> i32 {
1153    let coded_bands = if last_coded_bands == 0 {
1154        NB_EBANDS
1155    } else {
1156        last_coded_bands
1157    };
1158    let mut coded_bins = i64::from(i32::from(EBANDS[coded_bands]) << lm);
1159    if channels == 2 {
1160        coded_bins += i64::from(i32::from(EBANDS[intensity.min(coded_bands)]) << lm);
1161    }
1162    let mut target = i64::from(base_target);
1163
1164    // Stereo savings (a smaller target when the channels are coherent).
1165    if channels == 2 {
1166        let coded_stereo_bands = intensity.min(coded_bands);
1167        let coded_stereo_dof = i64::from(i32::from(EBANDS[coded_stereo_bands]) << lm) - coded_stereo_bands as i64;
1168        let max_frac = 0.8 * coded_stereo_dof as f32 / coded_bins as f32;
1169        let stereo_saving = stereo_saving.min(1.0);
1170        let reduce = (max_frac * target as f32).min((stereo_saving - 0.1) * (coded_stereo_dof << BITRES) as f32);
1171        target -= reduce as i64;
1172    }
1173
1174    // Dynalloc boost (minus the average for calibration) and transient boost.
1175    target += i64::from(tot_boost - (19 << lm));
1176    let tf_calibration = 0.044f32;
1177    target += (2.0 * (tf_estimate - tf_calibration) * target as f32) as i64;
1178
1179    // Rate floor from the band depth (rarely binds at sane bitrates).
1180    let bins = i64::from(i32::from(EBANDS[NB_EBANDS - 2]) << lm);
1181    let mut floor_depth = (((channels as i64 * bins) << BITRES) as f32 * max_depth) as i64;
1182    floor_depth = floor_depth.max(target >> 2);
1183    target = target.min(floor_depth);
1184
1185    // Temporal-VBR boost at lower rates (off when temporal_vbr is 0).
1186    if tf_estimate < 0.2 {
1187        let amount = 0.000_003_1 * (96_000 - bitrate).clamp(0, 32_000) as f32;
1188        target += (temporal_vbr * amount * target as f32) as i64;
1189    }
1190
1191    // Never more than double the base rate.
1192    target.min(2 * i64::from(base_target)) as i32
1193}
1194
1195/// The per-band analysis outputs shared with the allocator and the
1196/// spreading/tf decisions.
1197struct Dynalloc {
1198    /// Per-band boost *targets* (count of dynalloc increments to code).
1199    offsets: [i32; NB_EBANDS],
1200    /// Per-band perceptual importance (`importance`), for `tf_analysis`.
1201    importance: [i32; NB_EBANDS],
1202    /// Per-band spreading weight (`spread_weight`), for `spreading_decision`.
1203    spread_weight: [i32; NB_EBANDS],
1204    /// Total dynalloc boost in 8th bits (`tot_boost`), for VBR.
1205    tot_boost: i32,
1206    /// Maximum band depth above the noise floor (`maxDepth`), for the VBR
1207    /// rate floor.
1208    max_depth: f32,
1209}
1210
1211#[allow(clippy::too_many_arguments, reason = "mirrors the reference signature")]
1212#[allow(clippy::needless_range_loop, reason = "band indices mirror the reference loops")]
1213fn dynalloc_analysis(
1214    band_log_e: &[[f32; NB_EBANDS]; 2],
1215    band_log_e2: &[[f32; NB_EBANDS]; 2],
1216    old_ebands: &[[f32; NB_EBANDS]; 2],
1217    start: usize,
1218    end: usize,
1219    channels: usize,
1220    lm: usize,
1221    effective_bytes: usize,
1222    is_transient: bool,
1223) -> Dynalloc {
1224    /// `lsb_depth` for float input.
1225    const LSB_DEPTH: f32 = 24.0;
1226    let mut offsets = [0i32; NB_EBANDS];
1227    let mut importance = [13i32; NB_EBANDS];
1228    let mut spread_weight = [32i32; NB_EBANDS];
1229
1230    // Noise floor: eMeans, depth, band width and the pre-emphasis tilt.
1231    let mut noise_floor = [0.0f32; NB_EBANDS];
1232    for (i, nf) in noise_floor.iter_mut().enumerate().take(end) {
1233        *nf = 0.0625 * f32::from(LOG_N[i]) + 0.5 + (9.0 - LSB_DEPTH) - E_MEANS[i] + 0.0062 * ((i + 5) * (i + 5)) as f32;
1234    }
1235
1236    // The depth of the loudest band above the noise floor.
1237    let mut max_depth = -31.9f32;
1238    for c in 0..channels {
1239        for i in 0..end {
1240            max_depth = max_depth.max(band_log_e[c][i] - noise_floor[i]);
1241        }
1242    }
1243
1244    // A simple masking model giving each band a spreading weight, so the
1245    // spreading decision ignores fully masked bands.
1246    {
1247        let mut mask = [0.0f32; NB_EBANDS];
1248        let mut sig = [0.0f32; NB_EBANDS];
1249        for i in 0..end {
1250            mask[i] = band_log_e[0][i] - noise_floor[i];
1251            if channels == 2 {
1252                mask[i] = mask[i].max(band_log_e[1][i] - noise_floor[i]);
1253            }
1254            sig[i] = mask[i];
1255        }
1256        for i in 1..end {
1257            mask[i] = mask[i].max(mask[i - 1] - 2.0);
1258        }
1259        for i in (0..end - 1).rev() {
1260            mask[i] = mask[i].max(mask[i + 1] - 3.0);
1261        }
1262        for i in 0..end {
1263            // SMR is never more than 72 dB below the peak nor below the floor.
1264            let smr = sig[i] - 0.0f32.max(max_depth - 12.0).max(mask[i]);
1265            let shift = (-(0.5 + smr).floor() as i32).clamp(0, 5);
1266            spread_weight[i] = 32 >> shift;
1267        }
1268    }
1269
1270    // The gate: enable at ~24 kb/s for 20 ms, ~96 kb/s for 2.5 ms.
1271    if effective_bytes < 30 + 5 * lm {
1272        return Dynalloc {
1273            offsets,
1274            importance,
1275            spread_weight,
1276            tot_boost: 0,
1277            max_depth,
1278        };
1279    }
1280
1281    let mut follower = [[0.0f32; NB_EBANDS]; 2];
1282    for c in 0..channels {
1283        let mut e3 = [0.0f32; NB_EBANDS];
1284        e3[..end].copy_from_slice(&band_log_e2[c][..end]);
1285        if lm == 0 {
1286            // 2.5 ms: the first 8 bands have one bin (unreliable); take the
1287            // max with the previous energy so 2 bins contribute.
1288            for i in 0..end.min(8) {
1289                e3[i] = band_log_e2[c][i].max(old_ebands[c][i]);
1290            }
1291        }
1292        let f = &mut follower[c];
1293        f[0] = e3[0];
1294        let mut last = 0usize;
1295        for i in 1..end {
1296            // The last band at least 0.5 dB above the previous is the last
1297            // we consider (avoids problems on band-limited signals).
1298            if e3[i] > e3[i - 1] + 0.5 {
1299                last = i;
1300            }
1301            f[i] = (f[i - 1] + 1.5).min(e3[i]);
1302        }
1303        for i in (0..last).rev() {
1304            f[i] = f[i].min((f[i + 1] + 2.0).min(e3[i]));
1305        }
1306        // A median filter avoids triggering dynalloc unnecessarily.
1307        const OFFSET: f32 = 1.0;
1308        for i in 2..end.saturating_sub(2) {
1309            f[i] = f[i].max(median_of_5(&e3[i - 2..i + 3]) - OFFSET);
1310        }
1311        let tmp = median_of_3(&e3[0..3]) - OFFSET;
1312        f[0] = f[0].max(tmp);
1313        f[1] = f[1].max(tmp);
1314        let tmp = median_of_3(&e3[end - 3..end]) - OFFSET;
1315        f[end - 2] = f[end - 2].max(tmp);
1316        f[end - 1] = f[end - 1].max(tmp);
1317        for i in 0..end {
1318            f[i] = f[i].max(noise_floor[i]);
1319        }
1320    }
1321
1322    if channels == 2 {
1323        for i in start..end {
1324            // Consider 24 dB of cross-talk between channels.
1325            let (l, r) = (follower[0][i], follower[1][i]);
1326            follower[1][i] = r.max(l - 4.0);
1327            follower[0][i] = l.max(r - 4.0);
1328            follower[0][i] =
1329                0.5 * (0.0f32.max(band_log_e[0][i] - follower[0][i]) + 0.0f32.max(band_log_e[1][i] - follower[1][i]));
1330        }
1331    } else {
1332        for i in start..end {
1333            follower[0][i] = 0.0f32.max(band_log_e[0][i] - follower[0][i]);
1334        }
1335    }
1336
1337    // Perceptual importance (before the dynalloc-specific scaling below).
1338    for i in start..end {
1339        importance[i] = (0.5 + 13.0 * celt_exp2(follower[0][i].min(4.0))).floor() as i32;
1340    }
1341
1342    // For non-transient CBR frames, halve the dynalloc contribution.
1343    if !is_transient {
1344        for i in start..end {
1345            follower[0][i] *= 0.5;
1346        }
1347    }
1348    for i in start..end {
1349        if i < 8 {
1350            follower[0][i] *= 2.0;
1351        }
1352        if i >= 12 {
1353            follower[0][i] *= 0.5;
1354        }
1355    }
1356
1357    let mut tot_boost = 0i32;
1358    let caps = init_caps(lm, channels);
1359    for i in start..end {
1360        follower[0][i] = follower[0][i].min(4.0);
1361        let width = (channels as i32 * i32::from(EBANDS[i + 1] - EBANDS[i])) << lm;
1362        let (boost, boost_bits) = if width < 6 {
1363            let boost = follower[0][i] as i32;
1364            (boost, (boost * width) << BITRES)
1365        } else if width > 48 {
1366            let boost = (follower[0][i] * 8.0) as i32;
1367            (boost, ((boost * width) << BITRES) / 8)
1368        } else {
1369            let boost = (follower[0][i] * width as f32 / 6.0) as i32;
1370            (boost, (boost * 6) << BITRES)
1371        };
1372        // For CBR, limit dynalloc to 2/3 of the bits.
1373        if (tot_boost + boost_bits) >> BITRES >> 3 > 2 * effective_bytes as i32 / 3 {
1374            let cap = (2 * effective_bytes as i32 / 3) << BITRES << 3;
1375            offsets[i] = cap - tot_boost;
1376            break;
1377        }
1378        offsets[i] = boost.min(caps[i]);
1379        tot_boost += boost_bits;
1380    }
1381    Dynalloc {
1382        offsets,
1383        importance,
1384        spread_weight,
1385        tot_boost,
1386        max_depth,
1387    }
1388}
1389
1390/// Polynomial 2^x approximation (float build),
1391/// reproduced so `importance` matches the reference bit-for-bit-ish.
1392#[allow(clippy::excessive_precision, reason = "verbatim reference polynomial constants")]
1393fn celt_exp2(x: f32) -> f32 {
1394    let integer = x.floor();
1395    if integer < -50.0 {
1396        return 0.0;
1397    }
1398    let frac = x - integer;
1399    let res = 0.999_925_22 + frac * (0.695_833_54 + frac * (0.226_067_16 + 0.078_024_52 * frac));
1400    // Scale by 2^integer via the IEEE-754 exponent field.
1401    let bits = (res.to_bits() as i32 + ((integer as i32) << 23)) & 0x7fff_ffff;
1402    f32::from_bits(bits as u32)
1403}
1404
1405/// `median_of_5` (encoder helper).
1406fn median_of_5(x: &[f32]) -> f32 {
1407    let (t0, t1) = if x[0] > x[1] { (x[1], x[0]) } else { (x[0], x[1]) };
1408    let (t3, t4) = if x[3] > x[4] { (x[4], x[3]) } else { (x[3], x[4]) };
1409    let (_t0, t1, t3, t4) = if t0 > t3 { (t3, t4, t0, t1) } else { (t0, t1, t3, t4) };
1410    let t2 = x[2];
1411    if t2 > t1 {
1412        if t1 < t3 { t2.min(t3) } else { t4.min(t1) }
1413    } else if t2 < t3 {
1414        t1.min(t3)
1415    } else {
1416        t2.min(t4)
1417    }
1418}
1419
1420/// `median_of_3` (encoder helper).
1421fn median_of_3(x: &[f32]) -> f32 {
1422    let (t0, t1) = if x[0] > x[1] { (x[1], x[0]) } else { (x[0], x[1]) };
1423    let t2 = x[2];
1424    if t1 < t2 {
1425        t1
1426    } else if t0 < t2 {
1427        t2
1428    } else {
1429        t0
1430    }
1431}
1432
1433/// `alloc_trim_analysis` (float build, mono, no analysis module): tilts the
1434/// allocation by spectral slope, transient estimate and bitrate. Returns
1435/// the trim index 0..=10.
1436#[allow(clippy::needless_range_loop, reason = "band indices mirror the reference loop")]
1437fn alloc_trim_analysis(
1438    band_log_e: &[[f32; NB_EBANDS]; 2],
1439    end: usize,
1440    channels: usize,
1441    tf_estimate: f32,
1442    equiv_rate: i32,
1443) -> i32 {
1444    // At low bitrate, a lower trim helps.
1445    let mut trim: f32 = if equiv_rate < 64_000 {
1446        4.0
1447    } else if equiv_rate < 80_000 {
1448        4.0 + (1.0 / 16.0) * ((equiv_rate - 64_000) >> 10) as f32
1449    } else {
1450        5.0
1451    };
1452
1453    // Spectral tilt across the bands.
1454    let mut diff = 0.0f32;
1455    for c in 0..channels {
1456        for i in 0..end - 1 {
1457            diff += band_log_e[c][i] * (2 + 2 * i as i32 - end as i32) as f32;
1458        }
1459    }
1460    diff /= (channels * (end - 1)) as f32;
1461    trim -= (-2.0f32).max(2.0f32.min((diff + 1.0) / 6.0));
1462    trim -= 2.0 * tf_estimate;
1463
1464    (trim + 0.5).floor().clamp(0.0, 10.0) as i32
1465}
1466
1467/// `hysteresis_decision`: picks the threshold band for `val`, sticking with
1468/// `prev` while within its hysteresis to avoid chatter.
1469fn hysteresis_decision(val: f32, thresholds: &[f32], hysteresis: &[f32], prev: usize) -> usize {
1470    let n = thresholds.len();
1471    let mut i = n;
1472    for (k, &t) in thresholds.iter().enumerate() {
1473        if val < t {
1474            i = k;
1475            break;
1476        }
1477    }
1478    if i > prev && val < thresholds[prev] + hysteresis[prev] {
1479        i = prev;
1480    }
1481    if i < prev && val > thresholds[prev - 1] - hysteresis[prev - 1] {
1482        i = prev;
1483    }
1484    i
1485}
1486
1487/// `stereo_analysis`: an L1-norm comparison of the L/R versus mid/side
1488/// entropy over the low bands, deciding whether dual stereo is worthwhile.
1489#[allow(
1490    clippy::approx_constant,
1491    reason = "verbatim reference constant 0.707107, not 1/sqrt(2)"
1492)]
1493fn stereo_analysis(x: &[f32], n0: usize, lm: usize) -> bool {
1494    const EPSILON: f32 = 1e-15;
1495    let mut sum_lr = EPSILON;
1496    let mut sum_ms = EPSILON;
1497    for i in 0..13 {
1498        for j in (EBANDS[i] as usize) << lm..(EBANDS[i + 1] as usize) << lm {
1499            let l = x[j];
1500            let r = x[n0 + j];
1501            sum_lr += l.abs() + r.abs();
1502            sum_ms += (l + r).abs() + (l - r).abs();
1503        }
1504    }
1505    sum_ms *= 0.707_107;
1506    let mut thetas = 13i32;
1507    if lm <= 1 {
1508        thetas -= 8;
1509    }
1510    let w = (i32::from(EBANDS[13]) << (lm + 1)) as f32;
1511    (w + thetas as f32) * sum_ms > w * sum_lr
1512}
1513
1514/// `spreading_decision` (float build, no analysis module, `update_hf`
1515/// disabled since there is no post-filter): chooses the PVQ spreading from a
1516/// rough CDF of the normalised band shapes, weighted by `spread_weight`, and
1517/// recursively averaged in `tonal_average` with hysteresis from
1518/// `last_decision`. `x` is the planar normalised spectrum.
1519#[allow(clippy::too_many_arguments, reason = "mirrors the reference signature")]
1520fn spreading_decision(
1521    x: &[f32],
1522    n0: usize,
1523    tonal_average: &mut i32,
1524    last_decision: i32,
1525    end: usize,
1526    channels: usize,
1527    m: usize,
1528    spread_weight: &[i32; NB_EBANDS],
1529) -> i32 {
1530    if m * (EBANDS[end] - EBANDS[end - 1]) as usize <= 8 {
1531        return Spread::None as i32;
1532    }
1533    let mut sum = 0i32;
1534    let mut nb_bands = 0i32;
1535    for c in 0..channels {
1536        for i in 0..end {
1537            let lo = c * n0 + m * EBANDS[i] as usize;
1538            let n = m * (EBANDS[i + 1] - EBANDS[i]) as usize;
1539            if n <= 8 {
1540                continue;
1541            }
1542            let band = &x[lo..lo + n];
1543            // Rough CDF of |x[j]| via three energy thresholds.
1544            let mut tcount = [0i32; 3];
1545            for &v in band {
1546                let x2n = v * v * n as f32;
1547                tcount[0] += i32::from(x2n < 0.25);
1548                tcount[1] += i32::from(x2n < 0.0625);
1549                tcount[2] += i32::from(x2n < 0.015_625);
1550            }
1551            let nn = n as i32;
1552            let tmp = i32::from(2 * tcount[2] >= nn) + i32::from(2 * tcount[1] >= nn) + i32::from(2 * tcount[0] >= nn);
1553            sum += tmp * spread_weight[i];
1554            nb_bands += spread_weight[i];
1555        }
1556    }
1557    let nb_bands = nb_bands.max(1);
1558    let mut sum = (sum << 8) / nb_bands;
1559    // Recursive averaging, then hysteresis around the previous decision.
1560    sum = (sum + *tonal_average) >> 1;
1561    *tonal_average = sum;
1562    sum = (3 * sum + (((3 - last_decision) << 7) + 64) + 2) >> 2;
1563    if sum < 80 {
1564        Spread::Aggressive as i32
1565    } else if sum < 256 {
1566        Spread::Normal as i32
1567    } else if sum < 384 {
1568        Spread::Light as i32
1569    } else {
1570        Spread::None as i32
1571    }
1572}
1573
1574/// L1 norm of a band, biased toward good frequency resolution (`l1_metric`).
1575fn l1_metric(tmp: &[f32], lm: i32, bias: f32) -> f32 {
1576    let l1: f32 = tmp.iter().map(|v| v.abs()).sum();
1577    l1 + l1 * (lm as f32 * bias)
1578}
1579
1580/// `tf_analysis` (float build): per band, find the time/frequency split that
1581/// minimises the L1 metric (`metric`), then a Viterbi pass weighted by
1582/// `importance` and the switching cost `lambda` chooses the per-band tf
1583/// resolution flags and `tf_select`. Returns `(tf_res, tf_select)` with the
1584/// raw 0/1 flags (the caller maps them through `TF_SELECT_TABLE`).
1585#[allow(clippy::too_many_arguments, reason = "mirrors the reference signature")]
1586#[allow(clippy::needless_range_loop, reason = "band indices mirror the reference loops")]
1587fn tf_analysis(
1588    end: usize,
1589    is_transient: bool,
1590    lambda: i32,
1591    x: &[f32],
1592    n0: usize,
1593    lm: usize,
1594    tf_estimate: f32,
1595    tf_chan: usize,
1596    importance: &[i32; NB_EBANDS],
1597) -> ([i32; NB_EBANDS], usize) {
1598    let bias = 0.04 * (-0.25f32).max(0.5 - tf_estimate);
1599    let mut metric = [0i32; NB_EBANDS];
1600    let mut path0 = [0i32; NB_EBANDS];
1601    let mut path1 = [0i32; NB_EBANDS];
1602
1603    for i in 0..end {
1604        let n = (EBANDS[i + 1] - EBANDS[i]) as usize * (1 << lm);
1605        let narrow = (EBANDS[i + 1] - EBANDS[i]) == 1;
1606        let lo = tf_chan * n0 + (EBANDS[i] as usize) * (1 << lm);
1607        let mut tmp = x[lo..lo + n].to_vec();
1608        let mut best_l1 = l1_metric(&tmp, if is_transient { lm as i32 } else { 0 }, bias);
1609        let mut best_level = 0i32;
1610        // The -1 (recombine) case for transients.
1611        if is_transient && !narrow {
1612            let mut tmp1 = tmp.clone();
1613            haar1(&mut tmp1, n >> lm, 1 << lm);
1614            let l1 = l1_metric(&tmp1, lm as i32 + 1, bias);
1615            if l1 < best_l1 {
1616                best_l1 = l1;
1617                best_level = -1;
1618            }
1619        }
1620        let levels = lm + usize::from(!(is_transient || narrow));
1621        for k in 0..levels {
1622            let b = if is_transient {
1623                lm as i32 - k as i32 - 1
1624            } else {
1625                k as i32 + 1
1626            };
1627            haar1(&mut tmp, n >> k, 1 << k);
1628            let l1 = l1_metric(&tmp, b, bias);
1629            if l1 < best_l1 {
1630                best_l1 = l1;
1631                best_level = k as i32 + 1;
1632            }
1633        }
1634        // Q1 metric so a narrow band can sit at the -0.5 mid-point.
1635        metric[i] = if is_transient { 2 * best_level } else { -2 * best_level };
1636        if narrow && (metric[i] == 0 || metric[i] == -2 * lm as i32) {
1637            metric[i] -= 1;
1638        }
1639    }
1640
1641    let tf_tab = &TF_SELECT_TABLE[lm];
1642    let base = 4 * usize::from(is_transient);
1643    let cost = |sel: usize, flag: usize, i: usize| -> i32 {
1644        importance[i] * (metric[i] - 2 * tf_tab[base + 2 * sel + flag]).abs()
1645    };
1646
1647    // Pick tf_select by comparing the two candidate tables' total cost.
1648    let mut selcost = [0i32; 2];
1649    for sel in 0..2 {
1650        let mut cost0 = cost(sel, 0, 0);
1651        let mut cost1 = cost(sel, 1, 0) + if is_transient { 0 } else { lambda };
1652        for i in 1..end {
1653            let curr0 = cost0.min(cost1 + lambda);
1654            let curr1 = (cost0 + lambda).min(cost1);
1655            cost0 = curr0 + cost(sel, 0, i);
1656            cost1 = curr1 + cost(sel, 1, i);
1657        }
1658        selcost[sel] = cost0.min(cost1);
1659    }
1660    // Only allow tf_select=1 for transients (the reference's conservatism).
1661    let tf_select = usize::from(selcost[1] < selcost[0] && is_transient);
1662
1663    // Viterbi forward pass recording the back-pointers.
1664    let mut cost0 = cost(tf_select, 0, 0);
1665    let mut cost1 = cost(tf_select, 1, 0) + if is_transient { 0 } else { lambda };
1666    for i in 1..end {
1667        let (curr0, p0) = if cost0 < cost1 + lambda {
1668            (cost0, 0)
1669        } else {
1670            (cost1 + lambda, 1)
1671        };
1672        let (curr1, p1) = if cost0 + lambda < cost1 {
1673            (cost0 + lambda, 0)
1674        } else {
1675            (cost1, 1)
1676        };
1677        path0[i] = p0;
1678        path1[i] = p1;
1679        cost0 = curr0 + cost(tf_select, 0, i);
1680        cost1 = curr1 + cost(tf_select, 1, i);
1681    }
1682    let mut tf_res = [0i32; NB_EBANDS];
1683    tf_res[end - 1] = i32::from(cost0 >= cost1);
1684    for i in (0..end - 1).rev() {
1685        tf_res[i] = if tf_res[i + 1] == 1 { path1[i + 1] } else { path0[i + 1] };
1686    }
1687    (tf_res, tf_select)
1688}