Skip to main content

rusty_opus/
celt.rs

1use crate::bands::{
2    SPREAD_NONE, SPREAD_NORMAL, compute_band_energies, denormalise_bands, haar1, log2amp,
3    normalise_bands, quant_all_bands, spreading_decision,
4};
5use crate::modes::{CeltMode, SPREAD_ICDF, TAPSET_ICDF, TF_SELECT_TABLE, TRIM_ICDF};
6use crate::quant_bands::{
7    quant_coarse_energy_advanced, quant_energy_finalise, quant_fine_energy, unquant_coarse_energy,
8    unquant_energy_finalise, unquant_fine_energy,
9};
10use crate::range_coder::RangeCoder;
11use crate::rate::{BITRES, clt_compute_allocation};
12
13#[cfg(target_arch = "aarch64")]
14use std::arch::aarch64::*;
15
16#[cfg(target_arch = "aarch64")]
17#[inline(always)]
18#[allow(unsafe_op_in_unsafe_fn)]
19unsafe fn sum_abs_neon(x: &[f32], n: usize) -> f32 {
20    let mut sum_vec = vdupq_n_f32(0.0);
21    let mut i = 0;
22
23    while i + 16 <= n {
24        let x0 = vld1q_f32(x.as_ptr().add(i));
25        let x1 = vld1q_f32(x.as_ptr().add(i + 4));
26        let x2 = vld1q_f32(x.as_ptr().add(i + 8));
27        let x3 = vld1q_f32(x.as_ptr().add(i + 12));
28
29        sum_vec = vfmaq_f32(sum_vec, vabsq_f32(x0), vdupq_n_f32(1.0));
30        sum_vec = vfmaq_f32(sum_vec, vabsq_f32(x1), vdupq_n_f32(1.0));
31        sum_vec = vfmaq_f32(sum_vec, vabsq_f32(x2), vdupq_n_f32(1.0));
32        sum_vec = vfmaq_f32(sum_vec, vabsq_f32(x3), vdupq_n_f32(1.0));
33
34        i += 16;
35    }
36
37    while i + 8 <= n {
38        let x0 = vld1q_f32(x.as_ptr().add(i));
39        let x1 = vld1q_f32(x.as_ptr().add(i + 4));
40        sum_vec = vfmaq_f32(sum_vec, vabsq_f32(x0), vdupq_n_f32(1.0));
41        sum_vec = vfmaq_f32(sum_vec, vabsq_f32(x1), vdupq_n_f32(1.0));
42        i += 8;
43    }
44
45    while i + 4 <= n {
46        let x0 = vld1q_f32(x.as_ptr().add(i));
47        sum_vec = vfmaq_f32(sum_vec, vabsq_f32(x0), vdupq_n_f32(1.0));
48        i += 4;
49    }
50
51    let mut sum = vaddvq_f32(sum_vec);
52
53    for j in i..n {
54        sum += x[j].abs();
55    }
56
57    sum
58}
59
60#[inline(always)]
61fn sum_abs(x: &[f32]) -> f32 {
62    #[cfg(target_arch = "x86_64")]
63    unsafe {
64        if std::arch::is_x86_feature_detected!("avx") {
65            return sum_abs_avx(x, x.len());
66        }
67    }
68    #[cfg(target_arch = "aarch64")]
69    unsafe {
70        sum_abs_neon(x, x.len())
71    }
72    #[cfg(not(target_arch = "aarch64"))]
73    {
74        x.iter().map(|&v| v.abs()).sum()
75    }
76}
77
78const MAX_FRAME_SIZE: usize = 2880;
79
80const DECODE_BUFFER_SIZE: usize = 3072;
81/// CELT packet-loss-concealment constants (celt_decoder.c).
82const PLC_LPC_ORDER: usize = 24;
83const PLC_PITCH_LAG_MAX: usize = 720;
84const PLC_PITCH_LAG_MIN: usize = 100;
85
86const INV_TABLE: [u8; 128] = [
87    255, 255, 156, 110, 86, 70, 59, 51, 45, 40, 37, 33, 31, 28, 26, 25, 23, 22, 21, 20, 19, 18, 17,
88    16, 16, 15, 15, 14, 13, 13, 12, 12, 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 9, 9, 9, 8, 8, 8,
89    8, 8, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
90    5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3,
91    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2,
92];
93
94const MAX_TRANSIENT_LEN: usize = 3000;
95
96#[derive(Debug, Clone, Copy)]
97pub struct AnalysisInfo {
98    pub valid: bool,
99    pub tonality: f32,
100    pub tonality_slope: f32,
101    pub noisiness: f32,
102    pub activity: f32,
103    pub music_prob: f32,
104    pub music_prob_min: f32,
105    pub music_prob_max: f32,
106    pub bandwidth: i32,
107    pub activity_probability: f32,
108    pub max_pitch_ratio: f32,
109    pub leak_boost: [u8; 19], // LEAK_BANDS = 19
110}
111
112impl Default for AnalysisInfo {
113    fn default() -> Self {
114        Self {
115            valid: false,
116            tonality: 0.0,
117            tonality_slope: 0.0,
118            noisiness: 0.0,
119            activity: 0.0,
120            music_prob: 0.0,
121            music_prob_min: 0.0,
122            music_prob_max: 0.0,
123            bandwidth: 0,
124            activity_probability: 0.0,
125            max_pitch_ratio: 1.0,
126            leak_boost: [0; 19],
127        }
128    }
129}
130
131#[allow(clippy::too_many_arguments)]
132fn transient_analysis(
133    input: &[f32],
134    len: usize,
135    channels: usize,
136    tf_estimate: &mut f32,
137    tf_chan: &mut usize,
138    allow_weak_transients: bool,
139    weak_transient: &mut bool,
140    _tone_freq: f32,
141    toneishness: f32,
142    tmp: &mut [f32],
143    tmp2: &mut [f32],
144) -> bool {
145    let _prof = crate::prof::scope(crate::prof::Stage::CeltTransient);
146    let mut mask_metric = 0.0f32;
147    let mut forward_decay = 0.0625f32;
148
149    *weak_transient = false;
150    if allow_weak_transients {
151        forward_decay = 0.03125f32;
152    }
153
154    let len2 = len / 2;
155    debug_assert!(len <= MAX_TRANSIENT_LEN);
156
157    for c in 0..channels {
158        let mut mem0 = 0.0f32;
159        let mut mem1 = 0.0f32;
160
161        for i in 0..len {
162            let x = input[c * len + i];
163            let y = mem0 + x;
164            let mem00 = mem0;
165            mem0 = mem0 - x + 0.5 * mem1;
166            mem1 = x - mem00;
167            tmp[i] = y;
168        }
169
170        tmp[..12].fill(0.0);
171
172        let mut mean = 0.0f32;
173        mem0 = 0.0f32;
174        for i in 0..len2 {
175            let x2 = (tmp[2 * i] * tmp[2 * i] + tmp[2 * i + 1] * tmp[2 * i + 1]) / 16.0;
176            mean += x2 / 4096.0;
177            mem0 = x2 + (1.0 - forward_decay) * mem0;
178            tmp2[i] = forward_decay * mem0;
179        }
180
181        mem0 = 0.0f32;
182        let mut max_e = 0.0f32;
183        for i in (0..len2).rev() {
184            mem0 = tmp2[i] + 0.875 * mem0;
185            tmp2[i] = 0.125 * mem0;
186            if tmp2[i] > max_e {
187                max_e = tmp2[i];
188            }
189        }
190
191        mean = (mean * max_e * 0.5 * (len2 as f32)).sqrt();
192        let norm = (len2 as f32) / (1e-10 + mean);
193
194        let mut unmask = 0.0f32;
195        for i in (12..(len2 - 5)).step_by(4) {
196            let id = (64.0 * norm * (tmp2[i] + 1e-10)).floor() as i32;
197            let id = id.clamp(0, 127) as usize;
198            unmask += INV_TABLE[id] as f32;
199        }
200
201        unmask = 64.0 * unmask * 4.0 / (6.0 * (len2 as f32 - 17.0));
202        if unmask > mask_metric {
203            *tf_chan = c;
204            mask_metric = unmask;
205        }
206    }
207
208    let mut is_transient = mask_metric > 200.0;
209
210    if toneishness > 0.98 && _tone_freq < 0.026 {
211        is_transient = false;
212        mask_metric = 0.0;
213    }
214
215    *tf_estimate = (mask_metric - 150.0).clamp(0.0, 1.0);
216
217    is_transient
218}
219
220fn l1_metric(tmp: &[f32], n: usize, lm: i32, bias: f32) -> f32 {
221    #[cfg(target_arch = "x86_64")]
222    unsafe {
223        if n >= 16 && std::arch::is_x86_feature_detected!("avx") {
224            return l1_metric_avx(tmp, n, lm, bias);
225        }
226    }
227    #[cfg(target_arch = "aarch64")]
228    {
229        if n >= 16 {
230            return unsafe { l1_metric_neon(tmp, n, lm, bias) };
231        }
232    }
233
234    let mut l1 = 0.0f32;
235    for &tv in tmp[..n].iter() {
236        l1 += tv.abs();
237    }
238    l1 + (lm as f32) * bias * l1
239}
240
241#[cfg(target_arch = "x86_64")]
242#[target_feature(enable = "avx")]
243unsafe fn sum_abs_avx(x: &[f32], n: usize) -> f32 {
244    use std::arch::x86_64::*;
245
246    let mut sum0 = _mm256_setzero_ps();
247    let mut sum1 = _mm256_setzero_ps();
248    let mut i = 0usize;
249    let sign_mask = _mm256_set1_ps(-0.0);
250
251    while i + 16 <= n {
252        let v0 = _mm256_loadu_ps(x.as_ptr().add(i));
253        let v1 = _mm256_loadu_ps(x.as_ptr().add(i + 8));
254        sum0 = _mm256_add_ps(sum0, _mm256_andnot_ps(sign_mask, v0));
255        sum1 = _mm256_add_ps(sum1, _mm256_andnot_ps(sign_mask, v1));
256        i += 16;
257    }
258
259    while i + 8 <= n {
260        let v = _mm256_loadu_ps(x.as_ptr().add(i));
261        sum0 = _mm256_add_ps(sum0, _mm256_andnot_ps(sign_mask, v));
262        i += 8;
263    }
264
265    let sum = _mm256_add_ps(sum0, sum1);
266    let hi = _mm256_extractf128_ps(sum, 1);
267    let lo = _mm256_castps256_ps128(sum);
268    let s4 = _mm_add_ps(lo, hi);
269    let t1 = _mm_movehl_ps(s4, s4);
270    let s2 = _mm_add_ps(s4, t1);
271    let t2 = _mm_shuffle_ps(s2, s2, 0x55);
272    let mut out = _mm_cvtss_f32(_mm_add_ss(s2, t2));
273
274    for j in i..n {
275        out += x[j].abs();
276    }
277
278    out
279}
280
281#[cfg(target_arch = "x86_64")]
282#[target_feature(enable = "avx")]
283unsafe fn l1_metric_avx(tmp: &[f32], n: usize, lm: i32, bias: f32) -> f32 {
284    let l1 = sum_abs_avx(tmp, n);
285    l1 + (lm as f32) * bias * l1
286}
287
288#[cfg(target_arch = "aarch64")]
289#[target_feature(enable = "neon")]
290unsafe fn l1_metric_neon(tmp: &[f32], n: usize, lm: i32, bias: f32) -> f32 {
291    unsafe {
292        let mut sum4 = vdupq_n_f32(0.0);
293        let mut i = 0;
294
295        while i + 15 < n {
296            let v0 = vld1q_f32(tmp.as_ptr().add(i));
297            let v1 = vld1q_f32(tmp.as_ptr().add(i + 4));
298            let v2 = vld1q_f32(tmp.as_ptr().add(i + 8));
299            let v3 = vld1q_f32(tmp.as_ptr().add(i + 12));
300
301            sum4 = vaddq_f32(sum4, vabsq_f32(v0));
302            sum4 = vaddq_f32(sum4, vabsq_f32(v1));
303            sum4 = vaddq_f32(sum4, vabsq_f32(v2));
304            sum4 = vaddq_f32(sum4, vabsq_f32(v3));
305
306            i += 16;
307        }
308
309        while i + 3 < n {
310            let v = vld1q_f32(tmp.as_ptr().add(i));
311            sum4 = vaddq_f32(sum4, vabsq_f32(v));
312            i += 4;
313        }
314
315        let sum2 = vpaddq_f32(sum4, sum4);
316        let sum1 = vpaddq_f32(sum2, sum2);
317        let mut l1 = vgetq_lane_f32(sum1, 0);
318
319        while i < n {
320            l1 += tmp[i].abs();
321            i += 1;
322        }
323
324        l1 + (lm as f32) * bias * l1
325    }
326}
327
328const MAX_NB_EBANDS: usize = 21;
329
330const MAX_TF_TMP: usize = 176;
331
332#[allow(clippy::too_many_arguments)]
333fn tf_analysis(
334    mode: &CeltMode,
335    len: usize,
336    is_transient: bool,
337    tf_res: &mut [i32],
338    lambda: i32,
339    x: &[f32],
340    n0: usize,
341    lm: i32,
342    tf_estimate: f32,
343    tf_chan: usize,
344    importance: &[f32],
345) -> i32 {
346    let _prof = crate::prof::scope(crate::prof::Stage::CeltTf);
347    debug_assert!(len <= MAX_NB_EBANDS);
348    let mut metric = [0i32; MAX_NB_EBANDS];
349    let mut tmp = [0.0f32; MAX_TF_TMP];
350    let mut tmp_1 = [0.0f32; MAX_TF_TMP];
351
352    let bias = 0.04 * (-0.25f32).max(0.5 - tf_estimate);
353
354    for (i, metric_i) in metric[..len].iter_mut().enumerate() {
355        let n = ((mode.e_bands[i + 1] - mode.e_bands[i]) as usize) << lm;
356        let narrow = (mode.e_bands[i + 1] - mode.e_bands[i]) == 1;
357        let offset = tf_chan * n0 + ((mode.e_bands[i] as usize) << lm);
358        tmp[..n].copy_from_slice(&x[offset..offset + n]);
359
360        let mut l1 = l1_metric(&tmp[..n], n, if is_transient { lm } else { 0 }, bias);
361        let mut best_l1 = l1;
362        let mut best_level = 0;
363
364        if is_transient && !narrow {
365            tmp_1[..n].copy_from_slice(&tmp[..n]);
366            haar1(&mut tmp_1[..n], n >> lm, 1 << lm);
367            l1 = l1_metric(&tmp_1[..n], n, lm + 1, bias);
368            if l1 < best_l1 {
369                best_l1 = l1;
370                best_level = -1;
371            }
372        }
373
374        for k in 0..(lm + if is_transient || narrow { 0 } else { 1 }) {
375            let b = if is_transient { lm - k - 1 } else { k + 1 };
376
377            haar1(&mut tmp[..n], n >> k, 1 << k);
378            l1 = l1_metric(&tmp[..n], n, b, bias);
379
380            if l1 < best_l1 {
381                best_l1 = l1;
382                best_level = k + 1;
383            }
384        }
385
386        if is_transient {
387            *metric_i = 2 * best_level;
388        } else {
389            *metric_i = -2 * best_level;
390        }
391
392        if narrow && (*metric_i == 0 || *metric_i == -2 * lm) {
393            *metric_i -= 1;
394        }
395    }
396
397    let mut tf_select = 0;
398    let mut selcost = [0.0f32; 2];
399
400    for sel in 0..2 {
401        let mut cost0 = importance[0]
402            * ((metric[0]
403                - 2 * TF_SELECT_TABLE[lm as usize][4 * (is_transient as usize) + 2 * sel] as i32)
404                as f32)
405                .abs();
406        let mut cost1 = importance[0]
407            * ((metric[0]
408                - 2 * TF_SELECT_TABLE[lm as usize][4 * (is_transient as usize) + 2 * sel + 1]
409                    as i32) as f32)
410                .abs()
411            + (if is_transient { 0.0 } else { lambda as f32 });
412
413        for i in 1..len {
414            let curr0 = cost0.min(cost1 + lambda as f32);
415            let curr1 = (cost0 + lambda as f32).min(cost1);
416            cost0 = curr0
417                + importance[i]
418                    * ((metric[i]
419                        - 2 * TF_SELECT_TABLE[lm as usize][4 * (is_transient as usize) + 2 * sel]
420                            as i32) as f32)
421                        .abs();
422            cost1 = curr1
423                + importance[i]
424                    * ((metric[i]
425                        - 2 * TF_SELECT_TABLE[lm as usize]
426                            [4 * (is_transient as usize) + 2 * sel + 1]
427                            as i32) as f32)
428                        .abs();
429        }
430        selcost[sel] = cost0.min(cost1);
431    }
432
433    // C: tf_select=1 is only allowed on transients (celt_encoder.c:108).
434    if selcost[1] < selcost[0] && is_transient {
435        tf_select = 1;
436    }
437
438    let mut cost0 = importance[0]
439        * ((metric[0]
440            - 2 * TF_SELECT_TABLE[lm as usize][4 * (is_transient as usize) + 2 * tf_select] as i32)
441            as f32)
442            .abs();
443    let mut cost1 = importance[0]
444        * ((metric[0]
445            - 2 * TF_SELECT_TABLE[lm as usize][4 * (is_transient as usize) + 2 * tf_select + 1]
446                as i32) as f32)
447            .abs()
448        + (if is_transient { 0.0 } else { lambda as f32 });
449
450    tf_res[0] = if cost0 < cost1 { 0 } else { 1 };
451
452    for i in 1..len {
453        let curr0 = cost0.min(cost1 + lambda as f32);
454        let curr1 = (cost0 + lambda as f32).min(cost1);
455        cost0 = curr0
456            + importance[i]
457                * ((metric[i]
458                    - 2 * TF_SELECT_TABLE[lm as usize][4 * (is_transient as usize) + 2 * tf_select]
459                        as i32) as f32)
460                    .abs();
461        cost1 = curr1
462            + importance[i]
463                * ((metric[i]
464                    - 2 * TF_SELECT_TABLE[lm as usize]
465                        [4 * (is_transient as usize) + 2 * tf_select + 1]
466                        as i32) as f32)
467                    .abs();
468        tf_res[i] = if cost0 < cost1 { 0 } else { 1 };
469    }
470
471    tf_select as i32
472}
473
474fn tf_encode(
475    start: usize,
476    end: usize,
477    is_transient: bool,
478    tf_res: &mut [i32],
479    lm: i32,
480    mut tf_select: i32,
481    rc: &mut RangeCoder,
482) -> i32 {
483    let mut curr = 0;
484    let mut tf_changed = 0;
485    let mut logp = if is_transient { 2 } else { 4 };
486    let mut budget = rc.storage as i32 * 8;
487    let mut tell = rc.tell();
488
489    let tf_select_rsv = if lm > 0 && tell + logp < budget { 1 } else { 0 };
490    budget -= tf_select_rsv;
491
492    for tf_res_i in tf_res[start..end].iter_mut() {
493        if tell + logp <= budget {
494            rc.encode_bit_logp(*tf_res_i ^ curr != 0, logp as u32);
495            tell = rc.tell();
496            curr = *tf_res_i;
497            tf_changed |= curr;
498        } else {
499            *tf_res_i = curr;
500        }
501        logp = if is_transient { 4 } else { 5 };
502    }
503
504    if tf_select_rsv != 0
505        && TF_SELECT_TABLE[lm as usize][4 * (is_transient as usize) + (tf_changed as usize)]
506            != TF_SELECT_TABLE[lm as usize][4 * (is_transient as usize) + 2 + (tf_changed as usize)]
507    {
508        rc.encode_bit_logp(tf_select != 0, 1);
509    } else {
510        tf_select = 0;
511    }
512
513    for tf_res_i in tf_res[start..end].iter_mut() {
514        *tf_res_i = TF_SELECT_TABLE[lm as usize]
515            [4 * (is_transient as usize) + 2 * (tf_select as usize) + (*tf_res_i as usize)]
516            as i32;
517    }
518
519    tf_changed
520}
521
522fn tf_decode(
523    start: usize,
524    end: usize,
525    is_transient: bool,
526    tf_res: &mut [i32],
527    lm: i32,
528    rc: &mut RangeCoder,
529) {
530    let mut curr = 0;
531    let mut tf_changed = 0;
532    let mut logp = if is_transient { 2 } else { 4 };
533    let budget = rc.storage as i32 * 8;
534    let mut tell = rc.tell();
535
536    let tf_select_rsv = if lm > 0 && tell + logp < budget { 1 } else { 0 };
537    let budget = budget - tf_select_rsv;
538
539    for tf_res_i in tf_res[start..end].iter_mut() {
540        if tell + logp <= budget {
541            curr ^= if rc.decode_bit_logp(logp as u32) {
542                1
543            } else {
544                0
545            };
546            tell = rc.tell();
547            tf_changed |= curr;
548        }
549        *tf_res_i = curr;
550        logp = if is_transient { 4 } else { 5 };
551    }
552
553    let mut tf_select = 0;
554    let _budget = budget + tf_select_rsv;
555    if tf_select_rsv > 0
556        && TF_SELECT_TABLE[lm as usize][4 * (is_transient as usize) + (tf_changed as usize)]
557            != TF_SELECT_TABLE[lm as usize][4 * (is_transient as usize) + 2 + (tf_changed as usize)]
558    {
559        tf_select = if rc.decode_bit_logp(1) { 1 } else { 0 };
560    }
561
562    for tf_res_i in tf_res[start..end].iter_mut() {
563        *tf_res_i = TF_SELECT_TABLE[lm as usize]
564            [4 * (is_transient as usize) + 2 * (tf_select as usize) + (*tf_res_i as usize)]
565            as i32;
566    }
567}
568
569fn stereo_analysis(m: &CeltMode, x: &[f32], lm: i32, n0: usize) -> bool {
570    let mut sum_lr = 1e-9f32;
571    let mut sum_ms = 1e-9f32;
572
573    for i in 0..13 {
574        let start = (m.e_bands[i] as usize) << lm;
575        let end = (m.e_bands[i + 1] as usize) << lm;
576        for j in start..end {
577            let l = x[j];
578            let r = x[n0 + j];
579            let m_val = l + r;
580            let s_val = l - r;
581            sum_lr += l.abs() + r.abs();
582            sum_ms += m_val.abs() + s_val.abs();
583        }
584    }
585
586    sum_ms *= std::f32::consts::FRAC_1_SQRT_2;
587    let mut thetas = 13;
588    if lm <= 1 {
589        thetas -= 8;
590    }
591
592    let left = (((m.e_bands[13] as usize) << (lm + 1)) + thetas) as f32 * sum_ms;
593    let right = ((m.e_bands[13] as usize) << (lm + 1)) as f32 * sum_lr;
594
595    left > right
596}
597
598const COMBFILTER_MINPERIOD: usize = 15;
599const COMBFILTER_MAXPERIOD: usize = 1024;
600
601const PREFILTER_GAINS: [[f32; 3]; 3] = [
602    [0.306_640_6, 0.217_041, 0.129_638_7],
603    [0.463_867_2, 0.268_066_4, 0.0],
604    [0.799_804_7, 0.100_097_7, 0.0],
605];
606
607#[allow(clippy::too_many_arguments)]
608fn comb_filter_const(
609    y: &mut [f32],
610    x: &[f32],
611    y_idx: usize,
612    x_idx: usize,
613    t: usize,
614    n: usize,
615    g10: f32,
616    g11: f32,
617    g12: f32,
618) {
619    #[cfg(target_arch = "aarch64")]
620    {
621        comb_filter_const_neon(y, x, y_idx, x_idx, t, n, g10, g11, g12);
622    }
623    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
624    unsafe {
625        if std::arch::is_x86_feature_detected!("avx") {
626            comb_filter_const_avx(y, x, y_idx, x_idx, t, n, g10, g11, g12);
627            return;
628        }
629    }
630    #[cfg(all(target_arch = "x86_64", target_feature = "sse"))]
631    unsafe {
632        comb_filter_const_sse(y, x, y_idx, x_idx, t, n, g10, g11, g12);
633        #[allow(clippy::needless_return)]
634        return;
635    }
636    #[cfg(not(any(
637        target_arch = "aarch64",
638        all(target_arch = "x86_64", target_feature = "sse")
639    )))]
640    {
641        comb_filter_const_scalar(y, x, y_idx, x_idx, t, n, g10, g11, g12);
642    }
643}
644
645#[inline]
646#[allow(dead_code)]
647fn comb_filter_const_scalar(
648    y: &mut [f32],
649    x: &[f32],
650    y_idx: usize,
651    x_idx: usize,
652    t: usize,
653    n: usize,
654    g10: f32,
655    g11: f32,
656    g12: f32,
657) {
658    let mut x1;
659    let mut x2;
660    let mut x3;
661    let mut x4;
662    let mut x0;
663
664    x4 = x[x_idx - t - 2];
665    x3 = x[x_idx - t - 1];
666    x2 = x[x_idx - t];
667    x1 = x[x_idx - t + 1];
668
669    for i in 0..n {
670        x0 = x[x_idx + i - t + 2];
671        y[y_idx + i] = x[x_idx + i] + g10 * x2 + g11 * (x1 + x3) + g12 * (x0 + x4);
672        x4 = x3;
673        x3 = x2;
674        x2 = x1;
675        x1 = x0;
676    }
677}
678
679#[cfg(target_arch = "aarch64")]
680fn comb_filter_const_neon(
681    y: &mut [f32],
682    x: &[f32],
683    y_idx: usize,
684    x_idx: usize,
685    t: usize,
686    n: usize,
687    g10: f32,
688    g11: f32,
689    g12: f32,
690) {
691    unsafe { comb_filter_const_neon_impl(y, x, y_idx, x_idx, t, n, g10, g11, g12) }
692}
693
694#[cfg(target_arch = "aarch64")]
695#[inline(always)]
696#[allow(unsafe_op_in_unsafe_fn)]
697unsafe fn comb_filter_const_neon_impl(
698    y: &mut [f32],
699    x: &[f32],
700    y_idx: usize,
701    x_idx: usize,
702    t: usize,
703    n: usize,
704    g10: f32,
705    g11: f32,
706    g12: f32,
707) {
708    use std::arch::aarch64::*;
709
710    let g10v = vdupq_n_f32(g10);
711    let g11v = vdupq_n_f32(g11);
712    let g12v = vdupq_n_f32(g12);
713
714    let xbase = x.as_ptr().add(x_idx);
715    let ybase = y.as_mut_ptr().add(y_idx);
716
717    let mut x0v = vld1q_f32(xbase.sub(t + 2));
718
719    let mut i = 0;
720    while i + 4 <= n {
721        let x4v = vld1q_f32(xbase.add(i).sub(t - 2));
722
723        let x2v = vextq_f32(x0v, x4v, 2);
724
725        let x1v = vextq_f32(x0v, x4v, 1);
726
727        let x3v = vextq_f32(x0v, x4v, 3);
728
729        let xi = vld1q_f32(xbase.add(i));
730
731        let mut yi = xi;
732        yi = vfmaq_f32(yi, g10v, x2v);
733        yi = vfmaq_f32(yi, g11v, vaddq_f32(x1v, x3v));
734        yi = vfmaq_f32(yi, g12v, vaddq_f32(x4v, x0v));
735        vst1q_f32(ybase.add(i), yi);
736
737        x0v = x4v;
738        i += 4;
739    }
740
741    let x0v_arr: [f32; 4] = std::mem::transmute(x0v);
742    let mut sx4 = x0v_arr[0];
743    let mut sx3 = x0v_arr[1];
744    let mut sx2 = x0v_arr[2];
745    let mut sx1 = x0v_arr[3];
746
747    while i < n {
748        let sx0 = x[x_idx + i - t + 2];
749        y[y_idx + i] = x[x_idx + i] + g10 * sx2 + g11 * (sx1 + sx3) + g12 * (sx0 + sx4);
750        sx4 = sx3;
751        sx3 = sx2;
752        sx2 = sx1;
753        sx1 = sx0;
754        i += 1;
755    }
756}
757
758#[cfg(all(target_arch = "x86_64", target_feature = "sse"))]
759#[inline(always)]
760#[allow(unsafe_op_in_unsafe_fn)]
761unsafe fn comb_filter_const_sse(
762    y: &mut [f32],
763    x: &[f32],
764    y_idx: usize,
765    x_idx: usize,
766    t: usize,
767    n: usize,
768    g10: f32,
769    g11: f32,
770    g12: f32,
771) {
772    use std::arch::x86_64::*;
773
774    let g10v = _mm_set1_ps(g10);
775    let g11v = _mm_set1_ps(g11);
776    let g12v = _mm_set1_ps(g12);
777
778    let xbase = x.as_ptr().add(x_idx);
779    let ybase = y.as_mut_ptr().add(y_idx);
780    let mut x0v = _mm_loadu_ps(xbase.sub(t + 2));
781
782    let mut i = 0;
783    while i + 4 <= n {
784        let x4v = _mm_loadu_ps(xbase.add(i).sub(t - 2));
785
786        let x2v = _mm_shuffle_ps(x0v, x4v, 0x4e);
787
788        let x1v = _mm_shuffle_ps(x0v, x2v, 0x99);
789
790        let x3v = _mm_shuffle_ps(x2v, x4v, 0x99);
791
792        let xi = _mm_loadu_ps(xbase.add(i));
793
794        let mut yi = xi;
795        yi = _mm_add_ps(yi, _mm_mul_ps(g10v, x2v));
796        let yi2 = _mm_add_ps(
797            _mm_mul_ps(g11v, _mm_add_ps(x3v, x1v)),
798            _mm_mul_ps(g12v, _mm_add_ps(x4v, x0v)),
799        );
800        yi = _mm_add_ps(yi, yi2);
801        _mm_storeu_ps(ybase.add(i), yi);
802
803        x0v = x4v;
804        i += 4;
805    }
806
807    let x0v_arr: [f32; 4] = std::mem::transmute(x0v);
808    let mut sx4 = x0v_arr[0];
809    let mut sx3 = x0v_arr[1];
810    let mut sx2 = x0v_arr[2];
811    let mut sx1 = x0v_arr[3];
812
813    while i < n {
814        let sx0 = x[x_idx + i - t + 2];
815        y[y_idx + i] = x[x_idx + i] + g10 * sx2 + g11 * (sx1 + sx3) + g12 * (sx0 + sx4);
816        sx4 = sx3;
817        sx3 = sx2;
818        sx2 = sx1;
819        sx1 = sx0;
820        i += 1;
821    }
822}
823
824#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
825#[target_feature(enable = "avx,fma")]
826#[allow(unsafe_op_in_unsafe_fn)]
827unsafe fn comb_filter_const_avx(
828    y: &mut [f32],
829    x: &[f32],
830    y_idx: usize,
831    x_idx: usize,
832    t: usize,
833    n: usize,
834    g10: f32,
835    g11: f32,
836    g12: f32,
837) {
838    use std::arch::x86_64::*;
839
840    let g10v = _mm256_set1_ps(g10);
841    let g11v = _mm256_set1_ps(g11);
842    let g12v = _mm256_set1_ps(g12);
843
844    let xbase = x.as_ptr().add(x_idx);
845    let ybase = y.as_mut_ptr().add(y_idx);
846
847    let mut i = 0;
848
849    while i + 16 <= n {
850        let xi_a = _mm256_loadu_ps(xbase.add(i));
851        let x0_a = _mm256_loadu_ps(xbase.add(i).sub(t + 2));
852        let x4_a = _mm256_loadu_ps(xbase.add(i).sub(t - 2));
853
854        let x2_a = _mm256_loadu_ps(xbase.add(i).sub(t));
855        let x1x3_a = _mm256_add_ps(
856            _mm256_loadu_ps(xbase.add(i).sub(t + 1)),
857            _mm256_loadu_ps(xbase.add(i).sub(t - 1)),
858        );
859        let x0x4_a = _mm256_add_ps(x0_a, x4_a);
860
861        let mut yi_a = xi_a;
862        yi_a = _mm256_fmadd_ps(g10v, x2_a, yi_a);
863        yi_a = _mm256_fmadd_ps(g11v, x1x3_a, yi_a);
864        yi_a = _mm256_fmadd_ps(g12v, x0x4_a, yi_a);
865        _mm256_storeu_ps(ybase.add(i), yi_a);
866
867        let j = i + 8;
868        let xi_b = _mm256_loadu_ps(xbase.add(j));
869        let x0_b = _mm256_loadu_ps(xbase.add(j).sub(t + 2));
870        let x4_b = _mm256_loadu_ps(xbase.add(j).sub(t - 2));
871        let x2_b = _mm256_loadu_ps(xbase.add(j).sub(t));
872        let x1x3_b = _mm256_add_ps(
873            _mm256_loadu_ps(xbase.add(j).sub(t + 1)),
874            _mm256_loadu_ps(xbase.add(j).sub(t - 1)),
875        );
876        let x0x4_b = _mm256_add_ps(x0_b, x4_b);
877
878        let mut yi_b = xi_b;
879        yi_b = _mm256_fmadd_ps(g10v, x2_b, yi_b);
880        yi_b = _mm256_fmadd_ps(g11v, x1x3_b, yi_b);
881        yi_b = _mm256_fmadd_ps(g12v, x0x4_b, yi_b);
882        _mm256_storeu_ps(ybase.add(j), yi_b);
883
884        i += 16;
885    }
886
887    while i + 8 <= n {
888        let xi = _mm256_loadu_ps(xbase.add(i));
889        let x0 = _mm256_loadu_ps(xbase.add(i).sub(t + 2));
890        let x4 = _mm256_loadu_ps(xbase.add(i).sub(t - 2));
891        let x2 = _mm256_loadu_ps(xbase.add(i).sub(t));
892        let x1x3 = _mm256_add_ps(
893            _mm256_loadu_ps(xbase.add(i).sub(t + 1)),
894            _mm256_loadu_ps(xbase.add(i).sub(t - 1)),
895        );
896        let x0x4 = _mm256_add_ps(x0, x4);
897
898        let mut yi = xi;
899        yi = _mm256_fmadd_ps(g10v, x2, yi);
900        yi = _mm256_fmadd_ps(g11v, x1x3, yi);
901        yi = _mm256_fmadd_ps(g12v, x0x4, yi);
902        _mm256_storeu_ps(ybase.add(i), yi);
903
904        i += 8;
905    }
906
907    if i + 4 <= n {
908        comb_filter_const_sse_fma(y, x, y_idx + i, x_idx + i, t, n - i, g10, g11, g12);
909        return;
910    }
911
912    let mut sx4 = x[x_idx + i - t - 2];
913    let mut sx3 = x[x_idx + i - t - 1];
914    let mut sx2 = x[x_idx + i - t];
915    let mut sx1 = x[x_idx + i - t + 1];
916    while i < n {
917        let sx0 = x[x_idx + i - t + 2];
918        y[y_idx + i] = x[x_idx + i] + g10 * sx2 + g11 * (sx1 + sx3) + g12 * (sx0 + sx4);
919        sx4 = sx3;
920        sx3 = sx2;
921        sx2 = sx1;
922        sx1 = sx0;
923        i += 1;
924    }
925}
926
927#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
928#[target_feature(enable = "avx,fma")]
929#[allow(unsafe_op_in_unsafe_fn)]
930unsafe fn comb_filter_const_sse_fma(
931    y: &mut [f32],
932    x: &[f32],
933    y_idx: usize,
934    x_idx: usize,
935    t: usize,
936    n: usize,
937    g10: f32,
938    g11: f32,
939    g12: f32,
940) {
941    use std::arch::x86_64::*;
942
943    let g10v = _mm_set1_ps(g10);
944    let g11v = _mm_set1_ps(g11);
945    let g12v = _mm_set1_ps(g12);
946
947    let xbase = x.as_ptr().add(x_idx);
948    let ybase = y.as_mut_ptr().add(y_idx);
949    let mut x0v = _mm_loadu_ps(xbase.sub(t + 2));
950
951    let mut i = 0;
952    while i + 4 <= n {
953        let x4v = _mm_loadu_ps(xbase.add(i).sub(t - 2));
954        let x2v = _mm_shuffle_ps(x0v, x4v, 0x4e);
955        let x1v = _mm_shuffle_ps(x0v, x2v, 0x99);
956        let x3v = _mm_shuffle_ps(x2v, x4v, 0x99);
957        let xi = _mm_loadu_ps(xbase.add(i));
958
959        let mut yi = xi;
960        yi = _mm_fmadd_ps(g10v, x2v, yi);
961        yi = _mm_fmadd_ps(g11v, _mm_add_ps(x1v, x3v), yi);
962        yi = _mm_fmadd_ps(g12v, _mm_add_ps(x0v, x4v), yi);
963        _mm_storeu_ps(ybase.add(i), yi);
964
965        x0v = x4v;
966        i += 4;
967    }
968
969    let x0v_arr: [f32; 4] = std::mem::transmute(x0v);
970    let mut sx4 = x0v_arr[0];
971    let mut sx3 = x0v_arr[1];
972    let mut sx2 = x0v_arr[2];
973    let mut sx1 = x0v_arr[3];
974    while i < n {
975        let sx0 = x[x_idx + i - t + 2];
976        y[y_idx + i] = x[x_idx + i] + g10 * sx2 + g11 * (sx1 + sx3) + g12 * (sx0 + sx4);
977        sx4 = sx3;
978        sx3 = sx2;
979        sx2 = sx1;
980        sx1 = sx0;
981        i += 1;
982    }
983}
984
985#[allow(clippy::too_many_arguments)]
986fn comb_filter(
987    y: &mut [f32],
988    x: &[f32],
989    y_idx: usize,
990    x_idx: usize,
991    t0: usize,
992    t1: usize,
993    n: usize,
994    g0: f32,
995    g1: f32,
996    tapset0: i32,
997    tapset1: i32,
998    window: &[f32],
999    overlap: usize,
1000) {
1001    if g0 == 0.0 && g1 == 0.0 {
1002        if x_idx != y_idx || !std::ptr::eq(x.as_ptr(), y.as_ptr()) {
1003            y[y_idx..y_idx + n].copy_from_slice(&x[x_idx..x_idx + n]);
1004        }
1005        return;
1006    }
1007
1008    let t0 = t0.clamp(
1009        COMBFILTER_MINPERIOD,
1010        x_idx.saturating_sub(2).max(COMBFILTER_MINPERIOD),
1011    );
1012    let t1 = t1.clamp(
1013        COMBFILTER_MINPERIOD,
1014        x_idx.saturating_sub(2).max(COMBFILTER_MINPERIOD),
1015    );
1016
1017    let g00 = g0 * PREFILTER_GAINS[tapset0 as usize][0];
1018    let g01 = g0 * PREFILTER_GAINS[tapset0 as usize][1];
1019    let g02 = g0 * PREFILTER_GAINS[tapset0 as usize][2];
1020
1021    let g10 = g1 * PREFILTER_GAINS[tapset1 as usize][0];
1022    let g11 = g1 * PREFILTER_GAINS[tapset1 as usize][1];
1023    let g12 = g1 * PREFILTER_GAINS[tapset1 as usize][2];
1024
1025    let mut x1 = x[x_idx - t1 + 1];
1026    let mut x2 = x[x_idx - t1];
1027    let mut x3 = x[x_idx - t1 - 1];
1028    let mut x4 = x[x_idx - t1 - 2];
1029
1030    let mut inner_overlap = overlap;
1031    if g0 == g1 && t0 == t1 && tapset0 == tapset1 {
1032        inner_overlap = 0;
1033    }
1034
1035    let mut i = 0;
1036    while i < inner_overlap && i < n {
1037        let x0 = x[x_idx + i - t1 + 2];
1038        let f = window[i] * window[i];
1039        y[y_idx + i] = x[x_idx + i]
1040            + (1.0 - f)
1041                * (g00 * x[x_idx + i - t0]
1042                    + g01 * (x[x_idx + i - t0 + 1] + x[x_idx + i - t0 - 1])
1043                    + g02 * (x[x_idx + i - t0 + 2] + x[x_idx + i - t0 - 2]))
1044            + f * (g10 * x2 + g11 * (x1 + x3) + g12 * (x0 + x4));
1045
1046        x4 = x3;
1047        x3 = x2;
1048        x2 = x1;
1049        x1 = x0;
1050        i += 1;
1051    }
1052
1053    if i < n {
1054        if g1 == 0.0 {
1055            y[y_idx + i..y_idx + n].copy_from_slice(&x[x_idx + i..x_idx + n]);
1056        } else {
1057            comb_filter_const(y, x, y_idx + i, x_idx + i, t1, n - i, g10, g11, g12);
1058        }
1059    }
1060}
1061
1062/// In-place comb filter: buf[y_idx..y_idx+n] is both input and output.
1063/// Reference samples at buf[y_idx + i - T + offset] may already be filtered
1064/// if T < i, matching C libopus's in-place comb_filter(out, out, ...) behavior.
1065fn comb_filter_inplace(
1066    buf: &mut [f32],
1067    y_idx: usize,
1068    t0: usize,
1069    t1: usize,
1070    n: usize,
1071    g0: f32,
1072    g1: f32,
1073    tapset0: i32,
1074    tapset1: i32,
1075    window: &[f32],
1076    overlap: usize,
1077) {
1078    if g0 == 0.0 && g1 == 0.0 {
1079        // nothing to do; buf[y_idx..] already holds the input
1080        return;
1081    }
1082
1083    let t0 = t0.clamp(COMBFILTER_MINPERIOD, y_idx - 2);
1084    let t1 = t1.clamp(COMBFILTER_MINPERIOD, y_idx - 2);
1085
1086    let g00 = g0 * PREFILTER_GAINS[tapset0 as usize][0];
1087    let g01 = g0 * PREFILTER_GAINS[tapset0 as usize][1];
1088    let g02 = g0 * PREFILTER_GAINS[tapset0 as usize][2];
1089
1090    let g10 = g1 * PREFILTER_GAINS[tapset1 as usize][0];
1091    let g11 = g1 * PREFILTER_GAINS[tapset1 as usize][1];
1092    let g12 = g1 * PREFILTER_GAINS[tapset1 as usize][2];
1093
1094    let mut inner_overlap = overlap;
1095    if g0 == g1 && t0 == t1 && tapset0 == tapset1 {
1096        inner_overlap = 0;
1097    }
1098
1099    let mut i = 0;
1100    while i < inner_overlap && i < n {
1101        let idx = y_idx + i;
1102        let f = window[i] * window[i];
1103        let s = buf[idx]; // original input (not yet overwritten at idx)
1104        let r0 = buf[idx - t0];
1105        let r0p1 = buf[idx - t0 + 1];
1106        let r0m1 = buf[idx - t0 - 1];
1107        let r0p2 = buf[idx - t0 + 2];
1108        let r0m2 = buf[idx - t0 - 2];
1109        let r1 = buf[idx - t1];
1110        let r1p1 = buf[idx - t1 + 1];
1111        let r1m1 = buf[idx - t1 - 1];
1112        let r1p2 = buf[idx - t1 + 2];
1113        let r1m2 = buf[idx - t1 - 2];
1114        buf[idx] = s
1115            + (1.0 - f) * (g00 * r0 + g01 * (r0p1 + r0m1) + g02 * (r0p2 + r0m2))
1116            + f * (g10 * r1 + g11 * (r1p1 + r1m1) + g12 * (r1p2 + r1m2));
1117        i += 1;
1118    }
1119
1120    // Constant region: only new filter (t1, g1). The feedback delay t1 >=
1121    // COMBFILTER_MINPERIOD (15) >= 10, so an 8-wide vector at [idx, idx+8) never
1122    // reads its own writes: the batch's read span [idx-t1-2, idx-t1+9] is disjoint
1123    // from the write span [idx, idx+8) iff t1 >= 10 — the past outputs it reads are
1124    // already finalized, exactly as the scalar loop sees them.
1125    #[cfg(target_arch = "x86_64")]
1126    {
1127        if i + 8 <= n && t1 >= 10 && std::arch::is_x86_feature_detected!("avx2") {
1128            unsafe {
1129                i = comb_filter_const_avx2(buf, y_idx, i, n, t1, g10, g11, g12);
1130            }
1131        }
1132    }
1133    while i < n {
1134        let idx = y_idx + i;
1135        let s = buf[idx];
1136        let r1 = buf[idx - t1];
1137        let r1p1 = buf[idx - t1 + 1];
1138        let r1m1 = buf[idx - t1 - 1];
1139        let r1p2 = buf[idx - t1 + 2];
1140        let r1m2 = buf[idx - t1 - 2];
1141        buf[idx] = s + g10 * r1 + g11 * (r1p1 + r1m1) + g12 * (r1p2 + r1m2);
1142        i += 1;
1143    }
1144}
1145
1146/// AVX2 comb-filter constant region: 8 samples/iter, bit-exact vs the scalar
1147/// tail below. Uses separate mul+add (NOT FMA) in the scalar op order
1148/// `s + g10*r1 + g11*(r1p1+r1m1) + g12*(r1p2+r1m2)` so every rounding matches.
1149/// Requires `t1 >= 10` (the batch reads [idx-t1-2, idx-t1+9] stay clear of the
1150/// [idx, idx+8) writes). Returns the index `i` where the scalar tail resumes.
1151#[cfg(target_arch = "x86_64")]
1152#[target_feature(enable = "avx2")]
1153unsafe fn comb_filter_const_avx2(
1154    buf: &mut [f32],
1155    y_idx: usize,
1156    mut i: usize,
1157    n: usize,
1158    t1: usize,
1159    g10: f32,
1160    g11: f32,
1161    g12: f32,
1162) -> usize {
1163    use std::arch::x86_64::*;
1164    let vg10 = _mm256_set1_ps(g10);
1165    let vg11 = _mm256_set1_ps(g11);
1166    let vg12 = _mm256_set1_ps(g12);
1167    let p = buf.as_mut_ptr();
1168    while i + 8 <= n {
1169        let idx = y_idx + i;
1170        let base = idx - t1; // >= 2 (t1 <= idx-2, and idx >= y_idx >= t1+2)
1171        let s = _mm256_loadu_ps(p.add(idx));
1172        let r1 = _mm256_loadu_ps(p.add(base));
1173        let r1p1 = _mm256_loadu_ps(p.add(base + 1));
1174        let r1m1 = _mm256_loadu_ps(p.add(base - 1));
1175        let r1p2 = _mm256_loadu_ps(p.add(base + 2));
1176        let r1m2 = _mm256_loadu_ps(p.add(base - 2));
1177        let a = _mm256_add_ps(r1p1, r1m1);
1178        let b = _mm256_add_ps(r1p2, r1m2);
1179        // out = ((s + g10*r1) + g11*a) + g12*b  (left-to-right, two-rounding, no FMA)
1180        let mut out = _mm256_add_ps(s, _mm256_mul_ps(vg10, r1));
1181        out = _mm256_add_ps(out, _mm256_mul_ps(vg11, a));
1182        out = _mm256_add_ps(out, _mm256_mul_ps(vg12, b));
1183        _mm256_storeu_ps(p.add(idx), out);
1184        i += 8;
1185    }
1186    i
1187}
1188
1189fn run_prefilter(
1190    in_buf: &mut [f32],
1191    prefilter_mem: &mut [f32],
1192    prefilter_period: usize,
1193    prefilter_gain: f32,
1194    prefilter_tapset: i32,
1195    tapset_decision: i32,
1196    window: &[f32],
1197    channels: usize,
1198    frame_size: usize,
1199    overlap: usize,
1200
1201    pre: &mut [f32],
1202    pitch_buf: &mut [f32],
1203
1204    analysis: &AnalysisInfo,
1205    loss_rate: i32,
1206    nb_available_bytes: i32,
1207) -> (bool, f32, usize) {
1208    let _prof = crate::prof::scope(crate::prof::Stage::CeltPrefilter);
1209    let max_period = COMBFILTER_MAXPERIOD;
1210    let min_period = COMBFILTER_MINPERIOD;
1211    let buf_stride = frame_size + overlap;
1212    let pre_size = max_period + frame_size;
1213
1214    for c in 0..channels {
1215        pre[c * pre_size..c * pre_size + max_period]
1216            .copy_from_slice(&prefilter_mem[c * max_period..(c + 1) * max_period]);
1217        pre[c * pre_size + max_period..c * pre_size + pre_size].copy_from_slice(
1218            &in_buf[c * buf_stride + overlap..c * buf_stride + overlap + frame_size],
1219        );
1220    }
1221
1222    let pitch_buf_len = (max_period + frame_size) >> 1;
1223    {
1224        let pre_slices: Vec<&[f32]> = (0..channels)
1225            .map(|c| &pre[c * pre_size..c * pre_size + pre_size])
1226            .collect();
1227        crate::pitch::pitch_downsample(&pre_slices, pitch_buf, pitch_buf_len, channels, 2);
1228    }
1229
1230    let search_max = max_period - 3 * min_period;
1231    let pitch_result = crate::pitch::pitch_search(
1232        &pitch_buf[max_period >> 1..],
1233        pitch_buf,
1234        frame_size,
1235        search_max,
1236    );
1237    let mut pitch_index = (max_period - pitch_result).min(max_period - 2);
1238
1239    let gain1_raw = crate::pitch::remove_doubling(
1240        pitch_buf,
1241        max_period,
1242        min_period,
1243        frame_size,
1244        &mut pitch_index,
1245        prefilter_period,
1246        prefilter_gain,
1247    );
1248    let mut gain1 = gain1_raw * 0.7;
1249
1250    // Loss-rate ladder (matches celt_encoder.c: halve >2%, halve again >4%,
1251    // zero >8%).
1252    if loss_rate > 2 {
1253        gain1 *= 0.5;
1254    }
1255    if loss_rate > 4 {
1256        gain1 *= 0.5;
1257    }
1258    if loss_rate > 8 {
1259        gain1 = 0.0;
1260    }
1261
1262    // Apply max_pitch_ratio from analysis if available
1263    if analysis.valid {
1264        gain1 *= analysis.max_pitch_ratio;
1265    }
1266
1267    let mut pf_threshold = 0.2f32;
1268    if (pitch_index as i32 - prefilter_period as i32).unsigned_abs() as usize * 10 > pitch_index {
1269        pf_threshold += 0.2;
1270    }
1271    // Rate-based bumps (celt_encoder.c): the ~7 pf bits are not worth it on
1272    // starved frames.
1273    if nb_available_bytes < 25 {
1274        pf_threshold += 0.1;
1275    }
1276    if nb_available_bytes < 35 {
1277        pf_threshold += 0.1;
1278    }
1279    if prefilter_gain > 0.4 {
1280        pf_threshold -= 0.1;
1281    }
1282    if prefilter_gain > 0.55 {
1283        pf_threshold -= 0.1;
1284    }
1285    pf_threshold = pf_threshold.max(0.2);
1286
1287    let pf_on;
1288    if gain1 < pf_threshold {
1289        gain1 = 0.0;
1290        pf_on = false;
1291    } else {
1292        if (gain1 - prefilter_gain).abs() < 0.1 {
1293            gain1 = prefilter_gain;
1294        }
1295        let qg = ((gain1 * 32.0 / 3.0 + 0.5).floor() as i32 - 1).clamp(0, 7);
1296        gain1 = 0.09375 * (qg + 1) as f32;
1297        pf_on = true;
1298    }
1299
1300    // Standard Opus modes have shortMdctSize == overlap (120), so C's
1301    // `offset = mode->shortMdctSize - overlap` is always 0 here.
1302    let offset = 0usize;
1303    let prev_period = prefilter_period.clamp(COMBFILTER_MINPERIOD, max_period - 2);
1304
1305    for c in 0..channels {
1306        if offset > 0 {
1307            let pre_c = &pre[c * pre_size..];
1308            comb_filter(
1309                in_buf,
1310                pre_c,
1311                c * buf_stride + overlap,
1312                max_period,
1313                prev_period,
1314                prev_period,
1315                offset,
1316                -prefilter_gain,
1317                -prefilter_gain,
1318                prefilter_tapset,
1319                prefilter_tapset,
1320                window,
1321                0,
1322            );
1323        }
1324
1325        {
1326            let pre_c = &pre[c * pre_size..];
1327            comb_filter(
1328                in_buf,
1329                pre_c,
1330                c * buf_stride + overlap + offset,
1331                max_period + offset,
1332                prev_period,
1333                pitch_index,
1334                frame_size - offset,
1335                -prefilter_gain,
1336                -gain1,
1337                prefilter_tapset,
1338                tapset_decision,
1339                window,
1340                overlap,
1341            );
1342        }
1343    }
1344
1345    for c in 0..channels {
1346        if frame_size >= max_period {
1347            prefilter_mem[c * max_period..(c + 1) * max_period].copy_from_slice(
1348                &pre[c * pre_size + frame_size..c * pre_size + frame_size + max_period],
1349            );
1350        } else {
1351            let shift = max_period - frame_size;
1352            prefilter_mem.copy_within(
1353                c * max_period + frame_size..(c + 1) * max_period,
1354                c * max_period,
1355            );
1356            prefilter_mem[c * max_period + shift..(c + 1) * max_period].copy_from_slice(
1357                &pre[c * pre_size + max_period..c * pre_size + max_period + frame_size],
1358            );
1359        }
1360    }
1361
1362    (pf_on, gain1, pitch_index)
1363}
1364
1365const STRIDE_ACCESS_PAD: usize = crate::pvq::MAX_PVQ_N * 8;
1366
1367/// libopus celt_encoder.c `compute_vbr` (float build), minus the pieces that
1368/// need the tonality analysis / surround masking / LFE / temporal-VBR inputs we
1369/// don't compute (their boosts are quality refinements, not conformance).
1370/// All quantities in eighth-bits per frame.
1371#[allow(clippy::too_many_arguments)]
1372fn compute_vbr_target(
1373    mode: &CeltMode,
1374    base_target: i32,
1375    lm: i32,
1376    last_coded_bands: i32,
1377    channels: i32,
1378    intensity: i32,
1379    constrained_vbr: bool,
1380    stereo_saving: f32,
1381    tot_boost: i32,
1382    tf_estimate: f32,
1383    max_depth: f32,
1384    analysis: &AnalysisInfo,
1385    tonal_boost: bool,
1386) -> i32 {
1387    let nb_ebands = mode.nb_ebands as i32;
1388    let e_bands = mode.e_bands;
1389    let coded_bands = if last_coded_bands != 0 { last_coded_bands } else { nb_ebands };
1390    let mut coded_bins = (e_bands[coded_bands as usize] as i32) << lm;
1391    if channels == 2 {
1392        coded_bins += (e_bands[intensity.min(coded_bands) as usize] as i32) << lm;
1393    }
1394
1395    let mut target = base_target;
1396
1397    // Stereo savings.
1398    if channels == 2 {
1399        let coded_stereo_bands = intensity.min(coded_bands);
1400        let coded_stereo_dof =
1401            ((e_bands[coded_stereo_bands as usize] as i32) << lm) - coded_stereo_bands;
1402        // Maximum fraction of the bits we could save if the signal were mono.
1403        let max_frac = 0.8f32 * coded_stereo_dof as f32 / coded_bins as f32;
1404        let ss = stereo_saving.min(1.0);
1405        target -= ((max_frac * target as f32) as i32)
1406            .min((((ss - 0.1) * ((coded_stereo_dof << BITRES) as f32)) as i32).max(i32::MIN));
1407    }
1408    // Boost according to dynalloc (minus the average for calibration).
1409    target += tot_boost - (19 << lm);
1410    // Transient boost, compensating for the average.
1411    let tf_calibration = 0.044f32;
1412    target += (2.0 * (tf_estimate - tf_calibration) * target as f32) as i32;
1413
1414    // Tonality boost (celt_encoder.c compute_vbr, the `analysis->valid && !lfe`
1415    // block). `tonality` is computed by the analysis module every frame and was
1416    // previously plumbed into the CELT layer and dropped — this is libopus's
1417    // own VBR lever, restored. The `pitch_change` term of the C is omitted: we
1418    // do not track that signal, so this is a faithful SUBSET, never an
1419    // invention. Off unless `tonal_boost`, so the default stays byte-identical
1420    // until the ladder clears it.
1421    if tonal_boost && analysis.valid {
1422        let tonal = (analysis.tonality - 0.15).max(0.0) - 0.12;
1423        target += ((coded_bins << BITRES) as f32 * 1.2 * tonal) as i32;
1424        // Low-activity reduction (same block in the C, guarded by activity<0.4):
1425        //   target -= (coded_bins<<BITRES) * (0.4 - activity)
1426        // `activity` was the third analysis signal computed every frame and
1427        // consumed by nothing.
1428        if analysis.activity < 0.4 {
1429            target -= ((coded_bins << BITRES) as f32 * (0.4 - analysis.activity)) as i32;
1430        }
1431    }
1432
1433    // Don't allocate more than 8 bits above the "depth" of the signal.
1434    {
1435        let bins = (e_bands[nb_ebands as usize - 2] as i32) << lm;
1436        let mut floor_depth = ((channels * bins << BITRES) as f32 * max_depth) as i32;
1437        floor_depth = floor_depth.max(target >> 2);
1438        target = target.min(floor_depth);
1439    }
1440
1441    // Constrained VBR can't sustain large swings.
1442    if constrained_vbr {
1443        target = base_target + (0.67 * (target - base_target) as f32) as i32;
1444    }
1445
1446    // Never more than double the base rate.
1447    target.min(2 * base_target)
1448}
1449
1450pub struct CeltEncoder {
1451    mode: &'static CeltMode,
1452    channels: usize,
1453    pub complexity: i32,
1454    syn_mem: Vec<f32>,
1455    enc_decode_mem: Vec<f32>,
1456    old_band_e: Vec<f32>,
1457    preemph_mem: Vec<f32>,
1458    tonal_average: i32,
1459    hf_average: i32,
1460    tapset_decision: i32,
1461    spread_decision: i32,
1462    intensity: i32,
1463    last_coded_bands: i32,
1464    /// Input bit depth for the dynalloc noise floors (opus lsb_depth).
1465    pub lsb_depth: i32,
1466    /// VBR target in eighth-bits per frame (0 = hard CBR). libopus vbr_rate.
1467    pub vbr_rate: i32,
1468    /// Constrained VBR (libopus default): reservoir-limited drift around target.
1469    pub constrained_vbr: bool,
1470    vbr_reservoir: i32,
1471    vbr_drift: i32,
1472    vbr_offset: i32,
1473    vbr_count: i32,
1474    prefilter_mem: Vec<f32>,
1475    prefilter_period: usize,
1476    prefilter_gain: f32,
1477    prefilter_tapset: i32,
1478    old_band_e2: Vec<f32>,
1479    old_band_e3: Vec<f32>,
1480    last_band_log_e: Vec<f32>,
1481    delayed_intra: f32,
1482
1483    w_in_buf: Vec<f32>,
1484    w_freq: Vec<f32>,
1485    w_band_e: Vec<f32>,
1486    w_x: Vec<f32>,
1487    w_band_log_e: Vec<f32>,
1488    w_band_log_e2: Vec<f32>,
1489    w_error: Vec<f32>,
1490    w_tf_res: Vec<i32>,
1491    w_cap: Vec<i32>,
1492    w_offsets: Vec<i32>,
1493    w_pulses: Vec<i32>,
1494    w_ebits: Vec<i32>,
1495    w_fine_priority: Vec<i32>,
1496    w_collapse_masks: Vec<u32>,
1497    w_band_amp_synth: Vec<f32>,
1498    w_freq_synth: Vec<f32>,
1499    consec_transient: i32,
1500
1501    w_prefilter_pre: Vec<f32>,
1502    w_prefilter_pitch_buf: Vec<f32>,
1503
1504    w_transient_tmp: Vec<f32>,
1505    w_transient_tmp2: Vec<f32>,
1506
1507    pub(crate) analysis: AnalysisInfo,
1508    /// Expected packet loss %, plumbed from `OpusEncoder.packet_loss_perc`
1509    /// each frame (was never assigned — census 2026-08-07). Drives the
1510    /// prefilter loss ladder and coarse-energy intra bias.
1511    pub(crate) loss_rate: i32,
1512    /// Enable libopus's tonality VBR boost in `compute_vbr_target`
1513    /// (`RUSTY_OPUS_TONAL_VBR`). Opt-in until the per-class ladder clears it;
1514    /// off = byte-identical.
1515    pub(crate) tonal_vbr: bool,
1516    /// Emit the CELT per-frame silence flag. **Default ON** since 2026-08-07;
1517    /// `RUSTY_OPUS_SILENCE_FLAG=0` restores the previous byte-identical
1518    /// behaviour. Without it we spend ~69% of the active-frame rate coding
1519    /// digital silence where libopus spends ~3% (docs/great-gate.md §5.5).
1520    ///
1521    /// Gated by: 13-class rate-matched BD (mean +0.198, **worst class exactly
1522    /// +0.000**, silence_dtx +1.647), an independent libopus decode at equal
1523    /// quality for 28% fewer bits, and a CBR run that keeps packet length exact.
1524    pub(crate) silence_flag: bool,
1525    /// Peak |sample| of the previous frame's overlap tail — the `st->overlap_max`
1526    /// of celt_encoder.c, needed so silence is only declared once the region the
1527    /// MDCT folds is silent as well.
1528    overlap_max: f32,
1529}
1530
1531const INTEN_THRESHOLDS: [i32; 21] = [
1532    1, 2, 3, 4, 5, 6, 7, 8, 16, 24, 36, 44, 50, 56, 62, 67, 72, 79, 88, 106, 134,
1533];
1534const INTEN_HYSTERESIS: [i32; 21] = [
1535    1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 5, 6, 8, 8,
1536];
1537
1538fn hysteresis_decision(val: i32, thresholds: &[i32], hysteresis: &[i32], prev: i32) -> i32 {
1539    let mut i = 0;
1540    while i < thresholds.len() {
1541        if val < thresholds[i] {
1542            break;
1543        }
1544        i += 1;
1545    }
1546    let mut res = i as i32;
1547    if res > prev && val < thresholds[prev as usize] + hysteresis[prev as usize] {
1548        res = prev;
1549    }
1550    if res < prev && res > 0 && val > thresholds[prev as usize - 1] - hysteresis[prev as usize - 1]
1551    {
1552        res = prev;
1553    }
1554    res
1555}
1556
1557#[allow(clippy::too_many_arguments)]
1558fn alloc_trim_analysis(
1559    mode: &CeltMode,
1560    x: &[f32],
1561    band_log_e: &[f32],
1562    end: usize,
1563    lm: i32,
1564    channels: usize,
1565    n0: usize,
1566    stereo_saving: &mut f32,
1567    tf_estimate: f32,
1568    intensity: i32,
1569    surround_trim: f32,
1570    equiv_rate: i32,
1571    analysis: &AnalysisInfo,
1572    tonal_boost: bool,
1573) -> i32 {
1574    let _prof = crate::prof::scope(crate::prof::Stage::CeltAlloc);
1575    let mut trim = 5.0f32;
1576    if equiv_rate < 64000 {
1577        trim = 4.0;
1578    } else if equiv_rate < 80000 {
1579        let frac = (equiv_rate - 64000) as f32 / 1024.0;
1580        trim = 4.0 + (1.0 / 16.0) * frac;
1581    }
1582
1583    if channels == 2 {
1584        let mut sum = 0.0f32;
1585        for i in 0..8 {
1586            let offset = (mode.e_bands[i] as usize) << lm;
1587            let n = ((mode.e_bands[i + 1] - mode.e_bands[i]) as usize) << lm;
1588            let mut partial = 0.0f32;
1589            for j in 0..n {
1590                partial += x[offset + j] * x[n0 + offset + j];
1591            }
1592            sum += partial;
1593        }
1594        sum = (sum / 8.0).abs().min(1.0);
1595        let mut min_xc = sum;
1596        for i in 8..intensity as usize {
1597            let offset = (mode.e_bands[i] as usize) << lm;
1598            let n = ((mode.e_bands[i + 1] - mode.e_bands[i]) as usize) << lm;
1599            let mut partial = 0.0f32;
1600            for j in 0..n {
1601                partial += x[offset + j] * x[n0 + offset + j];
1602            }
1603            min_xc = min_xc.min(partial.abs());
1604        }
1605        min_xc = min_xc.min(1.0);
1606
1607        let log_xc = (1.001 - sum * sum).log2();
1608        let log_xc2 = (log_xc * 0.5).max((1.001 - min_xc * min_xc).log2());
1609
1610        trim += (-4.0f32).max(0.75 * log_xc);
1611        *stereo_saving = (*stereo_saving + 0.25).min(-0.5 * log_xc2);
1612    }
1613
1614    let mut diff = 0.0f32;
1615    for c in 0..channels {
1616        for i in 0..end - 1 {
1617            diff += band_log_e[c * mode.nb_ebands + i] * (2 + 2 * i as i32 - end as i32) as f32;
1618        }
1619    }
1620    diff /= (channels * (end - 1)) as f32;
1621    trim -= (-2.0f32).max(2.0f32.min((diff + 1.0) / 6.0));
1622    trim -= surround_trim;
1623    trim -= 2.0 * tf_estimate;
1624
1625    // Spectral-tilt trim from the analysis (celt_encoder.c alloc_trim_analysis):
1626    //   trim -= clamp(-2, 2, 2*(tonality_slope + 0.05))
1627    // `tonality_slope` was the last of the analysis signals computed every frame
1628    // and read by nothing. Shares the `tonal_vbr` opt-in so the whole
1629    // orphaned-signal group flips together and OFF stays byte-identical.
1630    if tonal_boost && analysis.valid {
1631        trim -= (-2.0f32).max(2.0f32.min(2.0 * (analysis.tonality_slope + 0.05)));
1632    }
1633
1634    // Stereo-music LF tilt (PEAQ-tuned, opt-out via env NO_STEREO_TRIM). Our
1635    // per-output analysis lands the trim slightly lower than is perceptually ideal
1636    // for coupled stereo music — tilting a little more toward LF (where our coding
1637    // is strongest) recovers ~0.03–0.10 ODG on stereo music across 64–192 kbps with
1638    // no regressions (a +2 tilt was stronger at mid rates but starved HF at 64k under
1639    // VBR rate overlap; +1 is the safe, monotonic choice). Mono is untouched, and
1640    // trim is transmitted so encoder/decoder stay in sync — fully conformant.
1641    let _ = equiv_rate;
1642    // Env read cached once (was a per-stereo-frame var() inside a profiled
1643    // stage — Great Gate census 2026-08-07 hygiene batch).
1644    static STEREO_TRIM_OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1645    if channels == 2
1646        && !*STEREO_TRIM_OFF.get_or_init(|| std::env::var_os("NO_STEREO_TRIM").is_some())
1647    {
1648        trim += 1.0;
1649    }
1650
1651    let trim_index = (trim + 0.5).floor() as i32;
1652    trim_index.clamp(0, 10)
1653}
1654
1655#[inline(always)]
1656fn median3(a: f32, b: f32, c: f32) -> f32 {
1657    let mut v = [a, b, c];
1658    v.sort_by(|x, y| x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal));
1659    v[1]
1660}
1661
1662#[inline(always)]
1663fn median5(v: &[f32]) -> f32 {
1664    let mut x = [v[0], v[1], v[2], v[3], v[4]];
1665    x.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1666    x[2]
1667}
1668
1669/// Full port of celt_encoder.c dynalloc_analysis: per-band boosts (offsets),
1670/// the tf importance weights, the spreading-decision SMR weights, and maxDepth
1671/// (the signal depth over the noise floor, used as the VBR ceiling). Consumes
1672/// the pre-transient band logs (band_log_e2) and the analysis leak_boost.
1673#[allow(clippy::too_many_arguments)]
1674fn dynalloc_analysis(
1675    mode: &CeltMode,
1676    band_log_e: &[f32],
1677    band_log_e2: &[f32],
1678    start: usize,
1679    end: usize,
1680    channels: usize,
1681    offsets: &mut [i32],
1682    lsb_depth: i32,
1683    is_transient: bool,
1684    vbr: bool,
1685    constrained_vbr: bool,
1686    lm: usize,
1687    effective_bytes: usize,
1688    analysis: &AnalysisInfo,
1689    importance: &mut [f32],
1690    spread_weight: &mut [i32],
1691) -> f32 {
1692    let _prof = crate::prof::scope(crate::prof::Stage::CeltAlloc);
1693    let nb = mode.nb_ebands;
1694    offsets.fill(0);
1695
1696    // Noise floor: eMeans, depth, band width (logN) and the preemphasis tilt
1697    // (~ square of the bark band index).
1698    let mut noise_floor = [0.0f32; MAX_NB_EBANDS];
1699    for i in 0..end {
1700        noise_floor[i] = 0.0625 * mode.log_n[i] as f32 + 0.5 + (9 - lsb_depth) as f32
1701            - mode.e_means[i]
1702            + 0.0062 * ((i + 5) * (i + 5)) as f32;
1703    }
1704    let mut max_depth = -31.9f32;
1705    for c in 0..channels {
1706        for i in 0..end {
1707            max_depth = max_depth.max(band_log_e[c * nb + i] - noise_floor[i]);
1708        }
1709    }
1710
1711    // Simple masking model for the spreading decision: ignore fully masked bands.
1712    {
1713        let mut mask = [0.0f32; MAX_NB_EBANDS];
1714        let mut sig = [0.0f32; MAX_NB_EBANDS];
1715        for i in 0..end {
1716            mask[i] = band_log_e[i] - noise_floor[i];
1717        }
1718        if channels == 2 {
1719            for i in 0..end {
1720                mask[i] = mask[i].max(band_log_e[nb + i] - noise_floor[i]);
1721            }
1722        }
1723        sig[..end].copy_from_slice(&mask[..end]);
1724        for i in 1..end {
1725            mask[i] = mask[i].max(mask[i - 1] - 2.0);
1726        }
1727        for i in (0..end.saturating_sub(1)).rev() {
1728            mask[i] = mask[i].max(mask[i + 1] - 3.0);
1729        }
1730        for i in 0..end {
1731            // SMR: mask never more than 72 dB below the peak, never below floor.
1732            let smr = sig[i] - (0.0f32.max(max_depth - 12.0)).max(mask[i]);
1733            let shift = 5.min(0.max(-((0.5 + smr).floor() as i32)));
1734            spread_weight[i] = 32 >> shift;
1735        }
1736    }
1737
1738    // Make sure dynamic allocation can't bust the budget.
1739    if effective_bytes > 50 && lm >= 1 {
1740        let mut follower = [0.0f32; 2 * MAX_NB_EBANDS];
1741        let mut last = 0usize;
1742        for c in 0..channels {
1743            let base = c * nb;
1744            follower[base] = band_log_e2[base];
1745            for i in 1..end {
1746                // The last band at least .5 dB higher than the previous one is
1747                // the last we'll consider (band-limited signals).
1748                if band_log_e2[base + i] > band_log_e2[base + i - 1] + 0.5 {
1749                    last = i;
1750                }
1751                follower[base + i] =
1752                    (follower[base + i - 1] + 1.5).min(band_log_e2[base + i]);
1753            }
1754            for i in (0..last).rev() {
1755                follower[base + i] = follower[base + i]
1756                    .min((follower[base + i + 1] + 2.0).min(band_log_e2[base + i]));
1757            }
1758
1759            // Median filter so dynalloc doesn't trigger unnecessarily.
1760            let offset = 1.0f32;
1761            if end >= 5 {
1762                for i in 2..end - 2 {
1763                    follower[base + i] = follower[base + i]
1764                        .max(median5(&band_log_e2[base + i - 2..base + i + 3]) - offset);
1765                }
1766            }
1767            if end >= 3 {
1768                let tmp = median3(
1769                    band_log_e2[base],
1770                    band_log_e2[base + 1],
1771                    band_log_e2[base + 2],
1772                ) - offset;
1773                follower[base] = follower[base].max(tmp);
1774                follower[base + 1] = follower[base + 1].max(tmp);
1775                let tmp = median3(
1776                    band_log_e2[base + end - 3],
1777                    band_log_e2[base + end - 2],
1778                    band_log_e2[base + end - 1],
1779                ) - offset;
1780                follower[base + end - 2] = follower[base + end - 2].max(tmp);
1781                follower[base + end - 1] = follower[base + end - 1].max(tmp);
1782            }
1783
1784            for i in 0..end {
1785                follower[base + i] = follower[base + i].max(noise_floor[i]);
1786            }
1787        }
1788        if channels == 2 {
1789            for i in start..end {
1790                // Consider 24 dB "cross-talk".
1791                follower[nb + i] = follower[nb + i].max(follower[i] - 4.0);
1792                follower[i] = follower[i].max(follower[nb + i] - 4.0);
1793                follower[i] = 0.5
1794                    * ((band_log_e[i] - follower[i]).max(0.0)
1795                        + (band_log_e[nb + i] - follower[nb + i]).max(0.0));
1796            }
1797        } else {
1798            for i in start..end {
1799                follower[i] = (band_log_e[i] - follower[i]).max(0.0);
1800            }
1801        }
1802        for i in start..end {
1803            importance[i] = (0.5 + 13.0 * (follower[i].min(4.0)).exp2()).floor();
1804        }
1805        // For non-transient CBR/CVBR frames, halve the dynalloc contribution.
1806        if (!vbr || constrained_vbr) && !is_transient {
1807            for f in follower.iter_mut().take(end).skip(start) {
1808                *f *= 0.5;
1809            }
1810        }
1811        for i in start..end {
1812            if i < 8 {
1813                follower[i] *= 2.0;
1814            }
1815            if i >= 12 {
1816                follower[i] *= 0.5;
1817            }
1818        }
1819        if analysis.valid {
1820            for i in start..end.min(19) {
1821                follower[i] += analysis.leak_boost[i] as f32 * (1.0 / 64.0);
1822            }
1823        }
1824        let mut tot_boost = 0i32;
1825        for i in start..end {
1826            follower[i] = follower[i].min(4.0);
1827
1828            let width =
1829                channels as i32 * (mode.e_bands[i + 1] - mode.e_bands[i]) as i32 * (1 << lm);
1830            let (boost, boost_bits) = if width < 6 {
1831                let b = follower[i] as i32;
1832                (b, (b * width) << BITRES)
1833            } else if width > 48 {
1834                let b = (follower[i] * 8.0) as i32;
1835                (b, ((b * width) << BITRES) / 8)
1836            } else {
1837                let b = (follower[i] * width as f32 / 6.0) as i32;
1838                (b, (b * 6) << BITRES)
1839            };
1840            // For CBR and non-transient CVBR frames, limit dynalloc to 2/3 of
1841            // the bits.
1842            if (!vbr || (constrained_vbr && !is_transient))
1843                && ((tot_boost + boost_bits) >> BITRES >> 3) > 2 * effective_bytes as i32 / 3
1844            {
1845                let cap = (2 * effective_bytes as i32 / 3) << BITRES << 3;
1846                offsets[i] = cap - tot_boost;
1847                break;
1848            } else {
1849                offsets[i] = boost;
1850                tot_boost += boost_bits;
1851            }
1852        }
1853    } else {
1854        for i in start..end {
1855            importance[i] = 13.0;
1856        }
1857    }
1858    max_depth
1859}
1860
1861impl CeltEncoder {
1862    pub fn new(mode: &'static CeltMode, channels: usize) -> Self {
1863        let overlap = mode.overlap;
1864        let channel_mem_size = 2048 + overlap;
1865        let syn_mem_size = channels * channel_mem_size;
1866        let nb_ebands = mode.nb_ebands;
1867        let nb_x_ch = nb_ebands * channels;
1868        let frame_x_ch = MAX_FRAME_SIZE * channels;
1869        let bufstride_x_ch = (MAX_FRAME_SIZE + overlap) * channels;
1870        Self {
1871            mode,
1872            channels,
1873            complexity: 9,
1874            syn_mem: vec![0.0; syn_mem_size],
1875            enc_decode_mem: vec![0.0; syn_mem_size],
1876            old_band_e: vec![0.0; nb_x_ch],
1877            preemph_mem: vec![0.0; channels],
1878            tonal_average: 256,
1879            hf_average: 0,
1880            tapset_decision: 0,
1881            spread_decision: SPREAD_NORMAL,
1882            intensity: 0,
1883            last_coded_bands: 0,
1884            lsb_depth: 24,
1885            vbr_rate: 0,
1886            constrained_vbr: true,
1887            vbr_reservoir: 0,
1888            vbr_drift: 0,
1889            vbr_offset: 0,
1890            vbr_count: 0,
1891            prefilter_mem: vec![0.0; channels * COMBFILTER_MAXPERIOD],
1892            prefilter_period: COMBFILTER_MINPERIOD,
1893            prefilter_gain: 0.0,
1894            prefilter_tapset: 0,
1895            old_band_e2: vec![0.0; nb_x_ch],
1896            old_band_e3: vec![0.0; nb_x_ch],
1897            last_band_log_e: vec![0.0; nb_x_ch],
1898            delayed_intra: 0.0,
1899
1900            w_in_buf: vec![0.0; bufstride_x_ch],
1901            w_freq: vec![0.0; frame_x_ch + 4],
1902            w_band_e: vec![0.0; nb_x_ch],
1903
1904            w_x: vec![0.0; frame_x_ch + STRIDE_ACCESS_PAD],
1905            w_band_log_e: vec![0.0; nb_x_ch],
1906            w_band_log_e2: vec![0.0; nb_x_ch],
1907            w_error: vec![0.0; nb_x_ch],
1908            w_tf_res: vec![0; nb_ebands],
1909            w_cap: vec![0; nb_ebands],
1910            w_offsets: vec![0; nb_ebands],
1911            w_pulses: vec![0; nb_ebands],
1912            w_ebits: vec![0; nb_x_ch],
1913            w_fine_priority: vec![0; nb_x_ch],
1914            w_collapse_masks: vec![0; nb_x_ch],
1915            w_band_amp_synth: vec![0.0; nb_x_ch],
1916            w_freq_synth: vec![0.0; frame_x_ch + 4],
1917
1918            w_prefilter_pre: vec![0.0; channels * (COMBFILTER_MAXPERIOD + MAX_FRAME_SIZE)],
1919            w_prefilter_pitch_buf: vec![0.0; (COMBFILTER_MAXPERIOD + MAX_FRAME_SIZE) >> 1],
1920            w_transient_tmp: vec![0.0; MAX_TRANSIENT_LEN],
1921            w_transient_tmp2: vec![0.0; MAX_TRANSIENT_LEN / 2],
1922            consec_transient: 0,
1923
1924            analysis: AnalysisInfo::default(),
1925            loss_rate: 0,
1926            tonal_vbr: std::env::var_os("RUSTY_OPUS_TONAL_VBR").is_some(),
1927            silence_flag: std::env::var("RUSTY_OPUS_SILENCE_FLAG")
1928                .map(|v| v != "0")
1929                .unwrap_or(true),
1930            overlap_max: 0.0,
1931        }
1932    }
1933
1934    pub fn encode(&mut self, pcm: &[f32], frame_size: usize, rc: &mut RangeCoder) {
1935        self.encode_impl(pcm, frame_size, rc, 0, self.mode.nb_ebands, None)
1936    }
1937
1938    pub fn encode_with_start_band(
1939        &mut self,
1940        pcm: &[f32],
1941        frame_size: usize,
1942        rc: &mut RangeCoder,
1943        start_band: usize,
1944    ) {
1945        self.encode_impl(pcm, frame_size, rc, start_band, self.mode.nb_ebands, None)
1946    }
1947
1948    pub fn encode_with_budget(
1949        &mut self,
1950        pcm: &[f32],
1951        frame_size: usize,
1952        rc: &mut RangeCoder,
1953        start_band: usize,
1954        end_band: usize,
1955        total_bits: i32,
1956    ) {
1957        self.encode_impl(pcm, frame_size, rc, start_band, end_band, Some(total_bits))
1958    }
1959
1960    fn encode_impl(
1961        &mut self,
1962        pcm: &[f32],
1963        frame_size: usize,
1964        rc: &mut RangeCoder,
1965        start_band: usize,
1966        end_band: usize,
1967        explicit_total_bits: Option<i32>,
1968    ) {
1969        debug_assert!(end_band > start_band && end_band <= self.mode.nb_ebands);
1970        let mode = self.mode;
1971        let channels = self.channels;
1972        let nb_ebands = mode.nb_ebands;
1973
1974        // ---- Digital-silence detection (celt_encoder.c) ----
1975        // sample_max spans this frame's non-overlap part PLUS the previous
1976        // frame's overlap tail, so a frame is only "silent" once the region the
1977        // MDCT will actually fold is silent too. Gated by `silence_flag` until
1978        // the per-class ladder clears it; off = byte-identical.
1979        let silence = if self.silence_flag {
1980            let ovl = mode.overlap.min(frame_size);
1981            let head = (frame_size - ovl) * channels;
1982            let maxabs = |s: &[f32]| s.iter().fold(0.0f32, |m, &v| m.max(v.abs()));
1983            let n = (frame_size * channels).min(pcm.len());
1984            let head_max = maxabs(&pcm[..head.min(n)]);
1985            let tail_max = maxabs(&pcm[head.min(n)..n]);
1986            let sample_max = self.overlap_max.max(head_max).max(tail_max);
1987            self.overlap_max = tail_max;
1988            sample_max <= 1.0 / (1i64 << self.lsb_depth) as f32
1989        } else {
1990            false
1991        };
1992        let overlap = mode.overlap;
1993        // Bits already in the coder at entry (the SILK part in hybrid mode) — used
1994        // by the VBR min-size guard so shrinking never truncates them.
1995        let tell0_frac = rc.tell_frac();
1996
1997        let mut lm = 0;
1998        while (mode.short_mdct_size << lm) != frame_size {
1999            lm += 1;
2000            if lm > mode.max_lm {
2001                break;
2002            }
2003        }
2004        if (mode.short_mdct_size << lm) != frame_size {
2005            lm = 0;
2006        }
2007
2008        let _prof_pre = crate::prof::scope(crate::prof::Stage::CeltPreemph);
2009        let syn_mem_size = 2048 + overlap;
2010        for c in 0..channels {
2011            let channel_offset = c * syn_mem_size;
2012
2013            self.syn_mem.copy_within(
2014                channel_offset + frame_size..channel_offset + syn_mem_size,
2015                channel_offset,
2016            );
2017
2018            let mut m = self.preemph_mem[c];
2019            let coef = mode.preemph[0];
2020            for i in 0..frame_size {
2021                let x = pcm[c * frame_size + i] * 32768.0;
2022                let val = x - m;
2023                self.syn_mem[channel_offset + syn_mem_size - frame_size + i] = val;
2024                m = x * coef;
2025            }
2026            self.preemph_mem[c] = m;
2027        }
2028
2029        let buf_stride = frame_size + overlap;
2030        let in_buf = &mut self.w_in_buf[..buf_stride * channels];
2031        for c in 0..channels {
2032            let channel_offset = c * syn_mem_size;
2033            let in_buf_offset = c * buf_stride;
2034
2035            let src_start = syn_mem_size - frame_size - overlap;
2036            in_buf[in_buf_offset..in_buf_offset + buf_stride].copy_from_slice(
2037                &self.syn_mem[channel_offset + src_start..channel_offset + syn_mem_size],
2038            );
2039        }
2040
2041        drop(_prof_pre);
2042
2043        // Encoder pitch prefilter (the inverse of the decoder postfilter).
2044        // Enable gate matches celt_encoder.c: enough bytes to be worth the ~7
2045        // bits, CELT-only (start_band == 0; the hybrid high band has no pf),
2046        // complexity >= 5. `CELT_PF_OFF` disables for A/B debugging.
2047        // (History: default-off until 2026-07-09 — the octave signalling was one
2048        // low for every pitch_index >= 31, so decoders reconstructed a garbage
2049        // period; fixed, sine round-trip 7.6 -> 45.7 dB.)
2050        let nb_available_bytes = (explicit_total_bits.unwrap_or((rc.buf.len() * 8) as i32) >> 3)
2051            - ((rc.tell() + 4) >> 3);
2052        // Env read cached once (was per-frame — census 2026-08-07 hygiene batch).
2053        static PF_OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2054        let pf_enabled = start_band == 0
2055            && self.complexity >= 5
2056            && nb_available_bytes > 12 * channels as i32
2057            && !*PF_OFF.get_or_init(|| std::env::var_os("CELT_PF_OFF").is_some());
2058        // Capture the tapset used for THIS frame's comb (C's `prefilter_tapset`
2059        // local): spreading_decision mutates self.tapset_decision later in the
2060        // frame, and the value applied+signalled here — not the mutated one —
2061        // must become next frame's "old" tapset.
2062        let prefilter_tapset = self.tapset_decision;
2063        let (pf_on, gain1, pitch_index) = if pf_enabled {
2064            run_prefilter(
2065                in_buf,
2066                &mut self.prefilter_mem,
2067                self.prefilter_period,
2068                self.prefilter_gain,
2069                self.prefilter_tapset,
2070                prefilter_tapset,
2071                mode.window,
2072                channels,
2073                frame_size,
2074                overlap,
2075                &mut self.w_prefilter_pre,
2076                &mut self.w_prefilter_pitch_buf,
2077                &self.analysis,
2078                self.loss_rate,
2079                nb_available_bytes,
2080            )
2081        } else {
2082            (false, 0.0f32, COMBFILTER_MINPERIOD)
2083        };
2084
2085        // Save the prefiltered overlap for the next frame.
2086        // In libopus, st->in_mem stores the overlap separately and run_prefilter
2087        // copies it to/from in[]. Here we emulate that by updating syn_mem with
2088        // the last overlap samples of in_buf (which were prefiltered in place).
2089        let syn_mem_size = 2048 + overlap;
2090        for c in 0..channels {
2091            let channel_offset = c * syn_mem_size;
2092            let in_buf_offset = c * buf_stride;
2093            self.syn_mem[channel_offset + syn_mem_size - overlap..channel_offset + syn_mem_size]
2094                .copy_from_slice(&in_buf[in_buf_offset + frame_size..in_buf_offset + buf_stride]);
2095        }
2096
2097        // Transient analysis runs on the PREFILTERED signal (celt_encoder.c
2098        // order) — the comb removes periodic energy so pitch pulses don't read
2099        // as transients.
2100        let mut tf_estimate = 0.0f32;
2101        let mut tf_chan = 0;
2102        let mut weak_transient = false;
2103        let is_transient = if self.complexity >= 1 {
2104            transient_analysis(
2105                in_buf,
2106                buf_stride,
2107                channels,
2108                &mut tf_estimate,
2109                &mut tf_chan,
2110                false,
2111                &mut weak_transient,
2112                0.0,
2113                0.0,
2114                &mut self.w_transient_tmp,
2115                &mut self.w_transient_tmp2,
2116            )
2117        } else {
2118            false
2119        };
2120
2121        let freq = &mut self.w_freq[..frame_size * channels];
2122        // The first MDCT pass is always LONG blocks: for non-transients it is
2123        // the coding transform; for transients it feeds bandLogE2 (the
2124        // pre-transient spectrum dynalloc smooths against, celt_encoder.c
2125        // secondMdct) and the short re-MDCT below produces the coding one.
2126        let (shift, b) = (mode.max_lm - lm, 1);
2127        let n = frame_size / b;
2128
2129        for c in 0..channels {
2130            let c_buf_offset = c * buf_stride;
2131
2132            if c == 0 && b == 1 && channels == 1 {
2133                let mut max_val = 0.0f32;
2134                let check_len = (frame_size + overlap).min(buf_stride);
2135                for j in 0..check_len {
2136                    max_val = max_val.max(in_buf[c_buf_offset + j].abs());
2137                }
2138            }
2139
2140            for i in 0..b {
2141                mode.mdct.forward(
2142                    &in_buf[c_buf_offset + i * n..],
2143                    &mut freq[c * frame_size + i..],
2144                    mode.window,
2145                    overlap,
2146                    shift,
2147                    b,
2148                );
2149            }
2150        }
2151
2152        let band_e = &mut self.w_band_e[..nb_ebands * channels];
2153        band_e.fill(0.0);
2154        compute_band_energies(mode, freq, band_e, end_band, channels, lm);
2155
2156        let x_pad_end = (frame_size * channels + STRIDE_ACCESS_PAD).min(self.w_x.len());
2157        let x = &mut self.w_x[..x_pad_end];
2158        normalise_bands(
2159            mode,
2160            freq,
2161            x,
2162            band_e,
2163            end_band,
2164            channels,
2165            (1 << lm) as usize,
2166        );
2167
2168        if channels == 1 {
2169            let _ = freq[0];
2170        }
2171
2172        let mut total_bits = explicit_total_bits.unwrap_or_else(|| (rc.buf.len() * 8) as i32);
2173        self.w_error[..nb_ebands * channels].fill(0.0);
2174        let error = &mut self.w_error[..nb_ebands * channels];
2175
2176        let tell = rc.tell();
2177        if tell == 1 {
2178            rc.encode_bit_logp(silence, 15);
2179        }
2180        if silence {
2181            // celt_encoder.c: on a silent frame send only the minimum. Clamp the
2182            // coder to the bytes already filled + 2, then tell the range coder
2183            // the rest is spoken for. Every downstream budget check (allocation,
2184            // prefilter, bands) then has nothing to spend and codes nothing,
2185            // while the whole pipeline still runs — which is what keeps the
2186            // encoder in lockstep with the decoder's mirror of this at
2187            // `rc.nbits_total += total_bits - rc.tell()`.
2188            //
2189            // CBR frames keep their full size (the packet length is fixed), so
2190            // the shrink is VBR-only, exactly as in the C.
2191            if self.vbr_rate > 0 {
2192                let filled = (rc.tell() + 7) >> 3;
2193                let nb_compressed = (total_bits >> 3).min(filled + 2).max(2);
2194                rc.shrink(nb_compressed as u32);
2195                total_bits = nb_compressed * 8;
2196            }
2197            rc.nbits_total += total_bits - rc.tell();
2198        }
2199
2200        if start_band == 0 && !silence && rc.tell() + 16 <= total_bits {
2201            rc.encode_bit_logp(pf_on, 1);
2202            if pf_on {
2203                let qg = (gain1 / 0.09375 - 1.0 + 0.5).floor() as i32;
2204                let qg = qg.clamp(0, 7);
2205                let pi = (pitch_index + 1) as u32;
2206                // octave = EC_ILOG(pi) - 5 (EC_ILOG = 32 - clz, the BIT COUNT of
2207                // pi, not floor(log2)). The old `31 - clz` was one octave low for
2208                // every pi >= 32, overflowing the 4+octave residual field -> the
2209                // decoder reconstructed a garbage period (the prefilter's AM/PM
2210                // sideband bug). pi >= MINPERIOD+1 = 16 keeps this >= 0.
2211                let octave = 32 - pi.leading_zeros() - 5;
2212                rc.enc_uint(octave, 6);
2213                rc.enc_bits(pi - (16 << octave), 4 + octave);
2214                rc.enc_bits(qg as u32, 3);
2215                rc.encode_icdf(prefilter_tapset, &TAPSET_ICDF, 2);
2216            }
2217        }
2218
2219        let mut short_blocks = false;
2220        if lm > 0 && rc.tell() + 3 <= total_bits {
2221            rc.encode_bit_logp(is_transient, 3);
2222            if is_transient {
2223                short_blocks = true;
2224            }
2225        }
2226
2227        // bandLogE2: the long-MDCT logs + 0.5*LM when we re-MDCT short
2228        // (celt_encoder.c secondMdct); else a copy of the final logs (set after
2229        // the final amp2log2 below).
2230        let mut second_mdct_logs = false;
2231        if short_blocks && self.complexity >= 8 {
2232            let band_log_e2 = &mut self.w_band_log_e2[..nb_ebands * channels];
2233            band_log_e2.fill(-14.0);
2234            crate::bands::amp2log2(mode, 0, end_band, band_e, band_log_e2, channels);
2235            for v in band_log_e2.iter_mut() {
2236                *v += 0.5 * lm as f32;
2237            }
2238            second_mdct_logs = true;
2239        }
2240        if short_blocks {
2241            let b = 1 << lm;
2242            let n = frame_size / b;
2243            for c in 0..channels {
2244                let c_offset = c * buf_stride;
2245                for i in 0..b {
2246                    mode.mdct.forward(
2247                        &in_buf[c_offset + i * n..c_offset + buf_stride],
2248                        &mut freq[c * frame_size + i..],
2249                        mode.window,
2250                        overlap,
2251                        mode.max_lm,
2252                        b,
2253                    );
2254                }
2255            }
2256
2257            compute_band_energies(mode, freq, band_e, end_band, channels, lm);
2258            normalise_bands(
2259                mode,
2260                freq,
2261                x,
2262                band_e,
2263                end_band,
2264                channels,
2265                (1 << lm) as usize,
2266            );
2267        }
2268
2269        // Final band logs come AFTER the (possibly short) coding MDCT — C order
2270        // (celt_encoder.c:1742). C computes real logs for ALL bands below end
2271        // (amp2Log2 effEnd==end), incl. below start in hybrid: dynalloc's noise
2272        // floor and the spreading mask read them.
2273        let band_log_e = &mut self.w_band_log_e[..nb_ebands * channels];
2274        band_log_e.fill(-14.0);
2275        crate::bands::amp2log2(mode, 0, end_band, band_e, band_log_e, channels);
2276        if !second_mdct_logs {
2277            self.w_band_log_e2[..nb_ebands * channels].copy_from_slice(band_log_e);
2278        }
2279
2280        let intra_ener = if self.complexity >= 4 {
2281            false
2282        } else {
2283            self.old_band_e[..nb_ebands * channels]
2284                .iter()
2285                .all(|&e| e <= -27.0)
2286        };
2287        quant_coarse_energy_advanced(
2288            mode,
2289            start_band,
2290            end_band,
2291            end_band,
2292            band_log_e,
2293            &mut self.old_band_e,
2294            total_bits as u32,
2295            error,
2296            rc,
2297            channels,
2298            lm,
2299            (total_bits / 8) as usize,
2300            is_transient || intra_ener,
2301            &mut self.delayed_intra,
2302            self.complexity >= 4,
2303            0,
2304            false,
2305        );
2306        // Dynalloc analysis runs BEFORE tf (celt_encoder.c order): its
2307        // importance[] weights the tf Viterbi costs and spread_weight[] feeds
2308        // the spreading decision. The boost FLAGS are still written later, in
2309        // bitstream order.
2310        let effective_bytes = ((total_bits / 8) as usize).max(1);
2311        let mut importance = [13.0f32; MAX_NB_EBANDS];
2312        let mut spread_weight = [32i32; MAX_NB_EBANDS];
2313        self.w_offsets[..nb_ebands].fill(0);
2314        let max_depth = {
2315            let band_log_e2 = &self.w_band_log_e2[..nb_ebands * channels];
2316            dynalloc_analysis(
2317                mode,
2318                band_log_e,
2319                band_log_e2,
2320                start_band,
2321                end_band,
2322                channels,
2323                &mut self.w_offsets[..nb_ebands],
2324                self.lsb_depth,
2325                is_transient,
2326                self.vbr_rate > 0,
2327                self.constrained_vbr,
2328                lm,
2329                effective_bytes,
2330                &self.analysis,
2331                &mut importance,
2332                &mut spread_weight,
2333            )
2334        };
2335
2336        self.w_tf_res[..nb_ebands].fill(0);
2337        let tf_res = &mut self.w_tf_res[..nb_ebands];
2338        let lambda = 80.max(20480 / effective_bytes + 2) as i32;
2339
2340        let tf_select = if self.complexity >= 2 && effective_bytes >= 15 * channels {
2341            tf_analysis(
2342                mode,
2343                end_band,
2344                is_transient,
2345                tf_res,
2346                lambda,
2347                x,
2348                frame_size,
2349                lm as i32,
2350                tf_estimate,
2351                tf_chan,
2352                &importance,
2353            )
2354        } else {
2355            0
2356        };
2357        tf_encode(
2358            start_band,
2359            end_band,
2360            is_transient,
2361            tf_res,
2362            lm as i32,
2363            tf_select,
2364            rc,
2365        );
2366
2367        let mut dual_stereo_val = if channels == 2 {
2368            stereo_analysis(mode, x, lm as i32, frame_size) as i32
2369        } else {
2370            0
2371        };
2372
2373        let mut stereo_saving = 0.0f32;
2374        let equiv_rate = (total_bits * 48000) / frame_size as i32;
2375        if channels == 2 {
2376            self.intensity = hysteresis_decision(
2377                equiv_rate / 1000,
2378                &INTEN_THRESHOLDS,
2379                &INTEN_HYSTERESIS,
2380                self.intensity,
2381            );
2382            // Clamp to [start, end], NOT [0, nb_ebands] (celt_encoder.c:2034).
2383            // clt_compute_allocation codes `intensity - start` in a field of
2384            // width `end + 1 - start`; a value below start (which happens in
2385            // stereo HYBRID, start_band = 17) underflowed that field and
2386            // desynced the range coder on the first stereo-hybrid frame.
2387            self.intensity = self.intensity.clamp(start_band as i32, end_band as i32);
2388        }
2389
2390        if self.complexity == 0 {
2391            self.spread_decision = SPREAD_NONE;
2392            if rc.tell() + 4 <= total_bits {
2393                rc.encode_icdf(self.spread_decision, &SPREAD_ICDF, 5);
2394            }
2395        } else if rc.tell() + 4 <= total_bits {
2396            if is_transient || self.complexity < 3 || effective_bytes < 10 * channels {
2397                self.spread_decision = SPREAD_NORMAL;
2398            } else {
2399                let update_hf = lm == mode.max_lm;
2400                self.spread_decision = spreading_decision(
2401                    mode,
2402                    x,
2403                    &mut self.tonal_average,
2404                    self.spread_decision,
2405                    &mut self.hf_average,
2406                    &mut self.tapset_decision,
2407                    update_hf,
2408                    end_band,
2409                    channels,
2410                    (1 << lm) as usize,
2411                    &spread_weight,
2412                );
2413            }
2414            rc.encode_icdf(self.spread_decision, &SPREAD_ICDF, 5);
2415        } else {
2416            self.spread_decision = SPREAD_NORMAL;
2417        }
2418
2419        self.w_cap[..nb_ebands].fill(0);
2420        let cap = &mut self.w_cap[..nb_ebands];
2421        for (i, cap_i) in cap.iter_mut().enumerate() {
2422            let n = (mode.e_bands[i + 1] - mode.e_bands[i]) << lm;
2423            *cap_i = ((mode.cache.caps[nb_ebands * (2 * lm + channels - 1) + i] as i32 + 64)
2424                * channels as i32
2425                * n as i32)
2426                >> 2;
2427        }
2428
2429        let offsets = &mut self.w_offsets[..nb_ebands];
2430
2431        let mut dynalloc_logp = 6i32;
2432        let total_bits_bitres = total_bits << BITRES;
2433        let mut total_boost = 0i32;
2434        let mut tell_frac = rc.tell_frac();
2435
2436        for i in start_band..end_band {
2437            let width =
2438                channels as i32 * (mode.e_bands[i + 1] - mode.e_bands[i]) as i32 * (1 << lm);
2439            let quanta = (width << BITRES).min((6 << BITRES).max(width));
2440            let mut dynalloc_loop_logp = dynalloc_logp;
2441            let mut boost = 0i32;
2442            let mut j = 0i32;
2443
2444            while tell_frac + (dynalloc_loop_logp << BITRES) < total_bits_bitres - total_boost
2445                && boost < cap[i]
2446            {
2447                let flag = j < offsets[i];
2448                rc.encode_bit_logp(flag, dynalloc_loop_logp as u32);
2449                tell_frac = rc.tell_frac();
2450                if !flag {
2451                    break;
2452                }
2453                boost += quanta;
2454                total_boost += quanta;
2455                dynalloc_loop_logp = 1;
2456                j += 1;
2457            }
2458
2459            if j > 0 {
2460                dynalloc_logp = 2.max(dynalloc_logp - 1);
2461            }
2462            offsets[i] = boost;
2463        }
2464
2465        let alloc_trim = alloc_trim_analysis(
2466            mode,
2467            x,
2468            band_log_e,
2469            end_band,
2470            lm as i32,
2471            channels,
2472            frame_size,
2473            &mut stereo_saving,
2474            tf_estimate,
2475            self.intensity,
2476            0.0,
2477            equiv_rate,
2478            &self.analysis,
2479            self.tonal_vbr,
2480        );
2481        // libopus celt_encoder.c: alloc_trim is 5 UNLESS there is room to code the
2482        // analysis value — the decoder falls back to 5 when the trim isn't coded,
2483        // so the encoder MUST use 5 in the allocation math too. Keeping the
2484        // analysis trim here made trim_offset (hence the allocation) differ from
2485        // every conformant decoder on tight-budget frames (e.g. 24 kbps hybrid),
2486        // desyncing the range coder on ~1% of packets.
2487        let alloc_trim = if rc.tell_frac() + (6 << BITRES) <= total_bits_bitres - total_boost {
2488            rc.encode_icdf(alloc_trim, &TRIM_ICDF, 7);
2489            alloc_trim
2490        } else {
2491            5
2492        };
2493
2494        // ---- VBR: pick this frame's size and shrink the coder to it ----
2495        // (libopus celt_encoder.c `if (vbr_rate>0)`; runs between the trim and the
2496        // allocation so the allocator sees the final budget.)
2497        let total_bits = if self.vbr_rate > 0 {
2498            let hybrid = start_band != 0;
2499            let lm_diff = mode.max_lm as i32 - lm as i32;
2500            let vbr_rate = self.vbr_rate;
2501            let mut base_target = if hybrid {
2502                0.max(vbr_rate - ((9 * channels as i32 + 4) << BITRES))
2503            } else {
2504                vbr_rate - ((40 * channels as i32 + 20) << BITRES)
2505            };
2506            if self.constrained_vbr {
2507                base_target += self.vbr_offset >> lm_diff;
2508            }
2509            let mut target = if hybrid {
2510                // (libopus also nudges by the SILK quantization offset; we don't
2511                // track silk_info yet — quality refinement, not conformance.)
2512                let mut t = base_target;
2513                t += ((tf_estimate - 0.25) * (50 << BITRES) as f32) as i32;
2514                if tf_estimate > 0.7 {
2515                    t = t.max(50 << BITRES);
2516                }
2517                t
2518            } else {
2519                compute_vbr_target(
2520                    mode,
2521                    base_target,
2522                    lm as i32,
2523                    self.last_coded_bands,
2524                    channels as i32,
2525                    self.intensity,
2526                    self.constrained_vbr,
2527                    stereo_saving,
2528                    total_boost,
2529                    tf_estimate,
2530                    max_depth,
2531                    &self.analysis,
2532                    self.tonal_vbr,
2533                )
2534            };
2535            let tell = rc.tell_frac();
2536            target += tell;
2537            // Never shrink below what's already coded (+2 bytes of margin); in
2538            // hybrid, keep >=37 bits after the SILK part so the redundancy
2539            // signalling space assumed by every decoder still exists.
2540            let mut min_allowed =
2541                ((tell + total_boost + (1 << (BITRES + 3)) - 1) >> (BITRES + 3)) + 2;
2542            if hybrid {
2543                min_allowed = min_allowed.max(
2544                    (tell0_frac + (37 << BITRES) + total_boost + (1 << (BITRES + 3)) - 1)
2545                        >> (BITRES + 3),
2546                );
2547            }
2548            let cap_bytes = (total_bits / 8).min(1275 >> (3 - lm as i32));
2549            let mut nb_available = (target + (1 << (BITRES + 2))) >> (BITRES + 3);
2550            nb_available = nb_available.max(min_allowed).min(cap_bytes);
2551
2552            // Reservoir/drift tracking (constrained VBR).
2553            let delta = target - vbr_rate;
2554            let target_q = nb_available << (BITRES + 3);
2555            if self.vbr_count < 970 {
2556                self.vbr_count += 1;
2557            }
2558            let alpha = if self.vbr_count < 970 {
2559                1.0f32 / (self.vbr_count as f32 + 20.0)
2560            } else {
2561                0.001f32
2562            };
2563            if self.constrained_vbr {
2564                self.vbr_reservoir += target_q - vbr_rate;
2565                self.vbr_drift += (alpha
2566                    * ((delta * (1 << lm_diff)) - self.vbr_offset - self.vbr_drift) as f32)
2567                    as i32;
2568                self.vbr_offset = -self.vbr_drift;
2569                if self.vbr_reservoir < 0 {
2570                    let adjust = (-self.vbr_reservoir) / (8 << BITRES);
2571                    nb_available += adjust;
2572                    self.vbr_reservoir = 0;
2573                }
2574            }
2575            let nb_compressed = cap_bytes.min(nb_available).max(2);
2576            rc.shrink(nb_compressed as u32);
2577            nb_compressed * 8
2578        } else {
2579            total_bits
2580        };
2581
2582        let mut intensity = self.intensity;
2583        self.w_pulses[..nb_ebands].fill(0);
2584        let pulses = &mut self.w_pulses[..nb_ebands];
2585
2586        let stereo = channels > 1;
2587        let ebands_stereo = if stereo {
2588            nb_ebands * channels
2589        } else {
2590            nb_ebands
2591        };
2592        self.w_fine_priority[..ebands_stereo].fill(0);
2593        let fine_priority = &mut self.w_fine_priority[..ebands_stereo];
2594        self.w_ebits[..ebands_stereo].fill(0);
2595        let ebits = &mut self.w_ebits[..ebands_stereo];
2596        let mut balance = 0;
2597
2598        // The anti-collapse bit reservation must be subtracted from the allocation
2599        // budget BEFORE compute_allocation (libopus celt_encoder.c: `total_bits -=
2600        // anti_collapse_rsv` precedes it) — the decoder reserves it there too.
2601        // Computing it only afterwards (as this code used to) let the encoder
2602        // allocate 1<<BITRES more than the decoder assumes on transient LM>=2
2603        // frames -> band budgets differ from band `start` -> range desync on
2604        // exactly those frames (caught by opus_demo -d's per-packet range check).
2605        // Same formula as the decoder for exact symmetry.
2606        let anti_collapse_rsv = if is_transient && lm >= 2 {
2607            let remaining = (total_bits << BITRES) - rc.tell_frac() - 1;
2608            if remaining >= ((lm as i32 + 2) << BITRES) {
2609                1i32 << BITRES
2610            } else {
2611                0
2612            }
2613        } else {
2614            0
2615        };
2616
2617        // signalBandwidth: end-1 by CHOICE (C uses the analysis bandwidth,
2618        // celt_encoder.c:2174, to let the allocator skip top bands — but that
2619        // narrowing loses ~0.7 ODG on music even with leak_boost live, and
2620        // libopus's own narrowed scores lose to our full-band ones). PEAQ-gated
2621        // out twice; do not re-enable without a corpus win.
2622        let signal_bandwidth = end_band as i32 - 1;
2623        let _ = equiv_rate;
2624
2625        self.last_coded_bands = clt_compute_allocation(
2626            mode,
2627            start_band,
2628            end_band,
2629            offsets,
2630            cap,
2631            alloc_trim,
2632            &mut intensity,
2633            &mut dual_stereo_val,
2634            (total_bits << BITRES) - rc.tell_frac() - 1 - anti_collapse_rsv,
2635            &mut balance,
2636            pulses,
2637            ebits,
2638            fine_priority,
2639            channels as i32,
2640            lm as i32,
2641            rc,
2642            true,
2643            0,
2644            signal_bandwidth,
2645        );
2646
2647        quant_fine_energy(
2648            mode,
2649            start_band,
2650            end_band,
2651            &mut self.old_band_e,
2652            error,
2653            ebits,
2654            rc,
2655            channels,
2656        );
2657
2658        self.w_collapse_masks[..nb_ebands * channels].fill(0);
2659        let collapse_masks = &mut self.w_collapse_masks[..nb_ebands * channels];
2660        let (x_split, y_split) = x.split_at_mut(frame_size);
2661        let y_opt = if channels == 2 { Some(y_split) } else { None };
2662
2663        let mut dual_stereo = dual_stereo_val != 0;
2664
2665        let theta_rdo = channels == 2 && !dual_stereo && self.complexity >= 8;
2666        let resynth = theta_rdo;
2667
2668        quant_all_bands(
2669            true,
2670            mode,
2671            start_band,
2672            end_band,
2673            x_split,
2674            y_opt,
2675            collapse_masks,
2676            band_e,
2677            pulses,
2678            short_blocks,
2679            self.spread_decision,
2680            &mut dual_stereo,
2681            intensity as usize,
2682            tf_res,
2683            (total_bits << BITRES) - anti_collapse_rsv,
2684            &mut balance,
2685            rc,
2686            lm as i32,
2687            self.last_coded_bands,
2688            resynth,
2689            false,
2690            &mut 0u32,
2691        );
2692
2693        if anti_collapse_rsv > 0 {
2694            let anti_collapse_on = if self.consec_transient < 2 {
2695                1u32
2696            } else {
2697                0u32
2698            };
2699            rc.enc_bits(anti_collapse_on, 1);
2700        }
2701
2702        quant_energy_finalise(
2703            mode,
2704            start_band,
2705            end_band,
2706            &mut self.old_band_e,
2707            error,
2708            ebits,
2709            fine_priority,
2710            total_bits - rc.tell(),
2711            rc,
2712            channels,
2713        );
2714
2715        if resynth {
2716            let _prof = crate::prof::scope(crate::prof::Stage::CeltSynth);
2717            let band_amp_synth = &mut self.w_band_amp_synth[..nb_ebands * channels];
2718            log2amp(mode, nb_ebands, band_amp_synth, &self.old_band_e, channels);
2719            self.w_freq_synth[..frame_size * channels].fill(0.0);
2720            let freq_synth = &mut self.w_freq_synth[..frame_size * channels];
2721            denormalise_bands(
2722                mode,
2723                x,
2724                freq_synth,
2725                band_amp_synth,
2726                start_band,
2727                end_band,
2728                channels,
2729                (1 << lm) as usize,
2730            );
2731            let (syn_shift, syn_b) = if is_transient {
2732                (mode.max_lm, 1 << lm)
2733            } else {
2734                (mode.max_lm - lm, 1)
2735            };
2736            let syn_n = frame_size / syn_b;
2737            let decode_buf_size = 2048;
2738
2739            for c in 0..channels {
2740                let co = c * syn_mem_size;
2741                self.enc_decode_mem
2742                    .copy_within(co + frame_size..co + decode_buf_size + overlap, co);
2743            }
2744
2745            for c in 0..channels {
2746                let co = c * syn_mem_size;
2747                let out_syn_idx = decode_buf_size - frame_size;
2748                for bi in 0..syn_b {
2749                    let syn_stride = if is_transient {
2750                        mode.short_mdct_size
2751                    } else {
2752                        syn_n
2753                    };
2754                    mode.mdct.backward(
2755                        &freq_synth[c * frame_size + bi..],
2756                        &mut self.enc_decode_mem[co + out_syn_idx + bi * syn_stride..],
2757                        mode.window,
2758                        overlap,
2759                        syn_shift,
2760                        syn_b,
2761                    );
2762                }
2763            }
2764        }
2765
2766        self.last_band_log_e.copy_from_slice(&self.old_band_e);
2767
2768        if !is_transient {
2769            self.old_band_e3.copy_from_slice(&self.old_band_e2);
2770            self.old_band_e2.copy_from_slice(&self.old_band_e);
2771        } else {
2772            for i in 0..channels * nb_ebands {
2773                self.old_band_e2[i] = self.old_band_e2[i].min(self.old_band_e[i]);
2774            }
2775        }
2776
2777        // "In case start or end were to change" (celt_encoder.c:2301): zero the
2778        // coarse-energy state outside [start, end) and floor the log history —
2779        // the decoder does the same every frame, and a later frame with a wider
2780        // end must predict those bands from the SAME (zeroed) base.
2781        for c in 0..channels {
2782            for i in 0..start_band {
2783                self.old_band_e[c * nb_ebands + i] = 0.0;
2784                self.old_band_e2[c * nb_ebands + i] = -28.0;
2785                self.old_band_e3[c * nb_ebands + i] = -28.0;
2786            }
2787            for i in end_band..nb_ebands {
2788                self.old_band_e[c * nb_ebands + i] = 0.0;
2789                self.old_band_e2[c * nb_ebands + i] = -28.0;
2790                self.old_band_e3[c * nb_ebands + i] = -28.0;
2791            }
2792        }
2793
2794        rc.pad_to_bits(total_bits);
2795
2796        if pf_on {
2797            self.prefilter_period = pitch_index;
2798            self.prefilter_gain = gain1;
2799        } else {
2800            self.prefilter_period = COMBFILTER_MINPERIOD;
2801            self.prefilter_gain = 0.0;
2802        }
2803        self.prefilter_tapset = prefilter_tapset;
2804
2805        if is_transient {
2806            self.consec_transient += 1;
2807        } else {
2808            self.consec_transient = 0;
2809        }
2810    }
2811}
2812
2813pub struct CeltDecoder {
2814    mode: &'static CeltMode,
2815    channels: usize,
2816    // Bitstream (coded) channels C; normally == channels (CC). A mono packet in a
2817    // stereo decoder sets this to 1 (C=1, CC=2) so the CELT inter-frame state stays
2818    // one continuous chain across mono<->stereo switches, matching libopus.
2819    stream_channels: usize,
2820    decode_mem: Vec<f32>,
2821    old_band_e: Vec<f32>,
2822    preemph_mem: Vec<f32>,
2823    prefilter_mem: Vec<f32>,
2824    prefilter_period: usize,
2825    prefilter_period_old: usize,
2826    prefilter_gain: f32,
2827    prefilter_gain_old: f32,
2828    prefilter_tapset: i32,
2829    prefilter_tapset_old: i32,
2830    old_band_e2: Vec<f32>,
2831    old_band_e3: Vec<f32>,
2832    rng: u32,
2833    /// Consecutive-loss counter for packet-loss concealment (celt_decode_lost).
2834    loss_count: u32,
2835    /// Pitch lag from the first lost frame, reused across a loss burst.
2836    last_pitch_index: i32,
2837    /// LPC coefficients (per channel, PLC_LPC_ORDER) computed at the first loss
2838    /// and reused for the rest of the burst (pitch-based PLC).
2839    plc_lpc: Vec<f32>,
2840
2841    w_tf_res: Vec<i32>,
2842    w_cap: Vec<i32>,
2843    w_offsets: Vec<i32>,
2844    w_pulses: Vec<i32>,
2845    w_ebits: Vec<i32>,
2846    w_fine_priority: Vec<i32>,
2847    w_x: Vec<f32>,
2848    w_collapse_masks: Vec<u32>,
2849    w_freq: Vec<f32>,
2850    w_band_amp: Vec<f32>,
2851    w_pcm_frame: Vec<f32>,
2852    w_post: Vec<f32>,
2853}
2854
2855impl CeltDecoder {
2856    pub fn new(mode: &'static CeltMode, channels: usize) -> Self {
2857        let overlap = mode.overlap;
2858        let nb_ebands = mode.nb_ebands;
2859        let nb_x_ch = nb_ebands * channels;
2860        let dec_frame_x_ch = DECODE_BUFFER_SIZE * channels;
2861        Self {
2862            mode,
2863            channels,
2864            stream_channels: channels,
2865            decode_mem: vec![0.0; channels * (DECODE_BUFFER_SIZE + overlap)],
2866            // libopus: oldBandE inits to 0 (OPUS_CLEAR); only oldLogE/oldLogE2 get
2867            // the -28 "very quiet" floor. Do NOT init old_band_e to -28 (it is the
2868            // coarse-energy prediction state; -28 makes the first frames too quiet).
2869            old_band_e: vec![0.0; nb_x_ch],
2870            preemph_mem: vec![0.0; channels],
2871            prefilter_mem: vec![0.0; channels * COMBFILTER_MAXPERIOD],
2872            prefilter_period: COMBFILTER_MINPERIOD,
2873            prefilter_period_old: COMBFILTER_MINPERIOD,
2874            prefilter_gain: 0.0,
2875            prefilter_gain_old: 0.0,
2876            prefilter_tapset: 0,
2877            prefilter_tapset_old: 0,
2878            // oldLogE / oldLogE2 in libopus: init -QCONST16(28,DB_SHIFT).
2879            old_band_e2: vec![-28.0; nb_x_ch],
2880            old_band_e3: vec![-28.0; nb_x_ch],
2881            rng: 0,
2882            loss_count: 0,
2883            last_pitch_index: 0,
2884            plc_lpc: vec![0.0; channels * PLC_LPC_ORDER],
2885
2886            w_tf_res: vec![0; nb_ebands],
2887            w_cap: vec![0; nb_ebands],
2888            w_offsets: vec![0; nb_ebands],
2889            w_pulses: vec![0; nb_ebands],
2890            w_ebits: vec![0; nb_x_ch],
2891            w_fine_priority: vec![0; nb_x_ch],
2892
2893            w_x: vec![0.0; dec_frame_x_ch + STRIDE_ACCESS_PAD],
2894            w_collapse_masks: vec![0; nb_x_ch],
2895            w_freq: vec![0.0; dec_frame_x_ch + 4], // +4: NEON backward pre-rotation reads up to 3 elements past n2
2896            w_band_amp: vec![0.0; nb_x_ch],
2897            w_pcm_frame: vec![0.0; DECODE_BUFFER_SIZE],
2898            w_post: vec![0.0; DECODE_BUFFER_SIZE + COMBFILTER_MAXPERIOD],
2899        }
2900    }
2901
2902    /// Seed this decoder's inter-frame state from another decoder (typically the
2903    /// auxiliary mono decoder), replicating its channel 0 into every channel of
2904    /// self. Used at a mono->stereo switch so the primary stereo CeltDecoder's
2905    /// overlap/energy/prefilter state is continuous with the preceding mono
2906    /// packets (which libopus keeps in one continuous decoder) — without this the
2907    /// first stereo frame's MDCT overlap-add starts from silence.
2908    pub fn seed_from(&mut self, src: &CeltDecoder) {
2909        let overlap = self.mode.overlap;
2910        let nb = self.mode.nb_ebands;
2911        let per_dm = DECODE_BUFFER_SIZE + overlap;
2912        let src_ch = src.channels.max(1);
2913        for c in 0..self.channels {
2914            let sc = c.min(src_ch - 1);
2915            self.decode_mem[c * per_dm..(c + 1) * per_dm]
2916                .copy_from_slice(&src.decode_mem[sc * per_dm..(sc + 1) * per_dm]);
2917            self.old_band_e[c * nb..(c + 1) * nb]
2918                .copy_from_slice(&src.old_band_e[sc * nb..(sc + 1) * nb]);
2919            self.old_band_e2[c * nb..(c + 1) * nb]
2920                .copy_from_slice(&src.old_band_e2[sc * nb..(sc + 1) * nb]);
2921            self.old_band_e3[c * nb..(c + 1) * nb]
2922                .copy_from_slice(&src.old_band_e3[sc * nb..(sc + 1) * nb]);
2923            self.preemph_mem[c] = src.preemph_mem[sc];
2924            self.prefilter_mem[c * COMBFILTER_MAXPERIOD..(c + 1) * COMBFILTER_MAXPERIOD]
2925                .copy_from_slice(
2926                    &src.prefilter_mem[sc * COMBFILTER_MAXPERIOD..(sc + 1) * COMBFILTER_MAXPERIOD],
2927                );
2928        }
2929        self.prefilter_period = src.prefilter_period;
2930        self.prefilter_period_old = src.prefilter_period_old;
2931        self.prefilter_gain = src.prefilter_gain;
2932        self.prefilter_gain_old = src.prefilter_gain_old;
2933        self.prefilter_tapset = src.prefilter_tapset;
2934        self.prefilter_tapset_old = src.prefilter_tapset_old;
2935        self.rng = src.rng;
2936    }
2937
2938    /// Channels coded in the next packet's bitstream (1 for a mono packet decoded
2939    /// by a stereo decoder — keeps 2-channel state continuous across switches).
2940    pub fn set_stream_channels(&mut self, sc: usize) {
2941        self.stream_channels = sc.clamp(1, self.channels);
2942    }
2943
2944    /// libopus OPUS_RESET_STATE for the decoder: clear everything from rng onward,
2945    /// then oldLogE/oldLogE2 = -28 (oldBandE stays 0).
2946    pub fn reset(&mut self) {
2947        self.decode_mem.fill(0.0);
2948        self.old_band_e.fill(0.0);
2949        self.old_band_e2.fill(-28.0);
2950        self.old_band_e3.fill(-28.0);
2951        self.preemph_mem.fill(0.0);
2952        self.prefilter_mem.fill(0.0);
2953        self.prefilter_period = COMBFILTER_MINPERIOD;
2954        self.prefilter_period_old = COMBFILTER_MINPERIOD;
2955        self.prefilter_gain = 0.0;
2956        self.prefilter_gain_old = 0.0;
2957        self.prefilter_tapset = 0;
2958        self.prefilter_tapset_old = 0;
2959        self.rng = 0;
2960    }
2961
2962    pub fn decode(&mut self, compressed: &[u8], frame_size: usize, pcm: &mut [f32]) -> usize {
2963        self.decode_impl(compressed, frame_size, pcm, 0, self.mode.nb_ebands)
2964    }
2965
2966    pub fn decode_with_start_band(
2967        &mut self,
2968        compressed: &[u8],
2969        frame_size: usize,
2970        pcm: &mut [f32],
2971        start_band: usize,
2972    ) -> usize {
2973        self.decode_impl(compressed, frame_size, pcm, start_band, self.mode.nb_ebands)
2974    }
2975
2976    pub fn decode_from_range_coder(
2977        &mut self,
2978        rc: &mut RangeCoder,
2979        total_bits: i32,
2980        frame_size: usize,
2981        pcm: &mut [f32],
2982        start_band: usize,
2983    ) -> usize {
2984        self.decode_impl_from_rc(
2985            rc,
2986            total_bits,
2987            frame_size,
2988            pcm,
2989            start_band,
2990            self.mode.nb_ebands,
2991        )
2992    }
2993
2994    pub fn decode_from_range_coder_with_band_range(
2995        &mut self,
2996        rc: &mut RangeCoder,
2997        total_bits: i32,
2998        frame_size: usize,
2999        pcm: &mut [f32],
3000        start_band: usize,
3001        end_band: usize,
3002    ) -> usize {
3003        self.decode_impl_from_rc(rc, total_bits, frame_size, pcm, start_band, end_band)
3004    }
3005
3006    fn decode_impl(
3007        &mut self,
3008        compressed: &[u8],
3009        frame_size: usize,
3010        pcm: &mut [f32],
3011        start_band: usize,
3012        end_band: usize,
3013    ) -> usize {
3014        let total_bits = (compressed.len() * 8) as i32;
3015        let mut rc = RangeCoder::new_decoder(compressed);
3016        self.decode_impl_from_rc(&mut rc, total_bits, frame_size, pcm, start_band, end_band)
3017    }
3018
3019    fn decode_impl_from_rc(
3020        &mut self,
3021        rc: &mut RangeCoder,
3022        total_bits: i32,
3023        frame_size: usize,
3024        pcm: &mut [f32],
3025        start_band: usize,
3026        end_band: usize,
3027    ) -> usize {
3028        let mode = self.mode;
3029        // CC = state/output channels; C (=`channels`) = channels coded in the
3030        // bitstream. Mono packet in a stereo decoder: C=1, CC=2 — energy/allocation/
3031        // bands/denormalise all use C; synthesis writes CC output channels reading
3032        // the single decoded channel.
3033        let cc = self.channels;
3034        let channels = self.stream_channels.clamp(1, cc);
3035        let nb_ebands = mode.nb_ebands;
3036        let end_band = end_band.min(nb_ebands).max(start_band);
3037        let overlap = mode.overlap;
3038
3039        let mut lm = 0;
3040        while (mode.short_mdct_size << lm) != frame_size {
3041            lm += 1;
3042            if lm > mode.max_lm {
3043                break;
3044            }
3045        }
3046        if (mode.short_mdct_size << lm) != frame_size {
3047            lm = 0;
3048        }
3049
3050        // libopus celt_decoder.c:953: `if (C==1) oldBandE[i]=MAX(oldBandE[i],
3051        // oldBandE[nbEBands+i])` before the coarse-energy decode — a mono packet in
3052        // a stereo decoder predicts its single channel from the MAX of both
3053        // channels' previous energy. (Only meaningful on the first mono frame after
3054        // stereo; after every mono frame ch0 is replicated to ch1 at frame end.)
3055        if channels == 1 && cc == 2 {
3056            for i in 0..nb_ebands {
3057                self.old_band_e[i] = self.old_band_e[i].max(self.old_band_e[nb_ebands + i]);
3058            }
3059        }
3060
3061        let tell = rc.tell();
3062        let mut silence = false;
3063        if tell >= total_bits {
3064            silence = true;
3065        } else if tell == 1 {
3066            silence = rc.decode_bit_logp(15);
3067        }
3068        if silence {
3069            // libopus: "Pretend we've read all the remaining bits" — every
3070            // downstream budget check then skips its entropy reads naturally, the
3071            // whole pipeline still runs (decode_mem shift, overlap fade-out via a
3072            // zeroed spectrum, postfilter/deemph, frame-end energy bookkeeping).
3073            // The old early-return left the decoder state one frame stale and the
3074            // energy prediction hot -> the next loud frame decoded ~2^15 too loud
3075            // and railed the output.
3076            rc.nbits_total += total_bits - rc.tell();
3077        }
3078
3079        let mut pf_on = false;
3080        let mut pitch_index = COMBFILTER_MINPERIOD;
3081        let mut gain1 = 0.0f32;
3082        let mut prefilter_tapset = 0;
3083
3084        if start_band == 0 && !silence && rc.tell() + 16 <= total_bits {
3085            pf_on = rc.decode_bit_logp(1);
3086            if pf_on {
3087                let octave = rc.dec_uint(6);
3088                pitch_index = ((16 << octave) + rc.dec_bits(4 + octave)) as usize - 1;
3089                let qg = rc.dec_bits(3);
3090                if rc.tell() + 2 <= total_bits {
3091                    prefilter_tapset = rc.decode_icdf(&TAPSET_ICDF, 2) as usize;
3092                }
3093                gain1 = 0.09375 * (qg as f32 + 1.0);
3094            }
3095        }
3096        if start_band != 0 {
3097            self.prefilter_gain = 0.0;
3098        }
3099
3100        let mut is_transient = false;
3101        if lm > 0 && rc.tell() + 3 <= total_bits {
3102            is_transient = rc.decode_bit_logp(3);
3103        }
3104        let short_blocks = is_transient;
3105
3106        let intra_ener = if rc.tell() + 3 <= total_bits {
3107            rc.decode_bit_logp(3)
3108        } else {
3109            false
3110        };
3111
3112        unquant_coarse_energy(
3113            mode,
3114            start_band,
3115            end_band,
3116            &mut self.old_band_e,
3117            intra_ener,
3118            rc,
3119            channels,
3120            lm,
3121        );
3122        self.w_tf_res[..nb_ebands].fill(0);
3123        let tf_res = &mut self.w_tf_res[..nb_ebands];
3124        tf_decode(start_band, end_band, is_transient, tf_res, lm as i32, rc);
3125
3126        let spread_decision = if rc.tell() + 4 <= total_bits {
3127            rc.decode_icdf(&SPREAD_ICDF, 5)
3128        } else {
3129            SPREAD_NORMAL
3130        };
3131
3132        self.w_cap[..nb_ebands].fill(0);
3133        let cap = &mut self.w_cap[..nb_ebands];
3134        for (i, cap_i) in cap.iter_mut().enumerate() {
3135            let n = (mode.e_bands[i + 1] - mode.e_bands[i]) << lm;
3136            *cap_i = ((mode.cache.caps[nb_ebands * (2 * lm + channels - 1) + i] as i32 + 64)
3137                * channels as i32
3138                * n as i32)
3139                >> 2;
3140        }
3141
3142        self.w_offsets[..nb_ebands].fill(0);
3143        let offsets = &mut self.w_offsets[..nb_ebands];
3144        let mut dynalloc_logp = 6i32;
3145        let mut total_bits_bitres = total_bits << BITRES;
3146        let mut tell_frac = rc.tell_frac();
3147        for i in start_band..end_band {
3148            let width =
3149                channels as i32 * (mode.e_bands[i + 1] - mode.e_bands[i]) as i32 * (1 << lm);
3150            let quanta = (width << BITRES).min((6i32 << BITRES).max(width));
3151            let mut dynalloc_loop_logp = dynalloc_logp;
3152            let mut boost = 0i32;
3153            while tell_frac + (dynalloc_loop_logp << BITRES) < total_bits_bitres && boost < cap[i] {
3154                let flag = rc.decode_bit_logp(dynalloc_loop_logp as u32);
3155                tell_frac = rc.tell_frac();
3156                if !flag {
3157                    break;
3158                }
3159                boost += quanta;
3160                total_bits_bitres -= quanta;
3161                dynalloc_loop_logp = 1;
3162            }
3163            offsets[i] = boost;
3164            if boost > 0 {
3165                dynalloc_logp = dynalloc_logp.max(2) - 1;
3166                dynalloc_logp = dynalloc_logp.max(2);
3167            }
3168        }
3169
3170        let alloc_trim = if rc.tell_frac() + (6 << BITRES) <= total_bits_bitres {
3171            rc.decode_icdf(&TRIM_ICDF, 7)
3172        } else {
3173            5
3174        };
3175        let anti_collapse_rsv = if is_transient && lm >= 2 {
3176            let remaining = (total_bits << BITRES) - rc.tell_frac() - 1;
3177            if remaining >= ((lm as i32 + 2) << BITRES) {
3178                1i32 << BITRES
3179            } else {
3180                0
3181            }
3182        } else {
3183            0
3184        };
3185
3186        let mut intensity = 0;
3187        let mut dual_stereo_val = if channels == 2 { 1 } else { 0 };
3188        let mut balance = 0;
3189        self.w_pulses[..nb_ebands].fill(0);
3190        let pulses = &mut self.w_pulses[..nb_ebands];
3191
3192        let ebands_stereo = if channels > 1 {
3193            nb_ebands * channels
3194        } else {
3195            nb_ebands
3196        };
3197        self.w_fine_priority[..ebands_stereo].fill(0);
3198        let fine_priority = &mut self.w_fine_priority[..ebands_stereo];
3199        self.w_ebits[..ebands_stereo].fill(0);
3200        let ebits = &mut self.w_ebits[..ebands_stereo];
3201
3202        let alloc_bits = (total_bits << BITRES) - rc.tell_frac() - 1 - anti_collapse_rsv;
3203        let coded_bands = clt_compute_allocation(
3204            mode,
3205            start_band,
3206            end_band,
3207            offsets,
3208            cap,
3209            alloc_trim,
3210            &mut intensity,
3211            &mut dual_stereo_val,
3212            alloc_bits,
3213            &mut balance,
3214            pulses,
3215            ebits,
3216            fine_priority,
3217            channels as i32,
3218            lm as i32,
3219            rc,
3220            false,
3221            0,
3222            end_band as i32 - 1,
3223        );
3224
3225        unquant_fine_energy(
3226            mode,
3227            start_band,
3228            end_band,
3229            &mut self.old_band_e,
3230            ebits,
3231            rc,
3232            channels,
3233        );
3234
3235        if frame_size > DECODE_BUFFER_SIZE + overlap {
3236            return 0;
3237        }
3238
3239        self.w_x[..frame_size * channels].fill(0.0);
3240
3241        let x_pad_end = (frame_size * channels + STRIDE_ACCESS_PAD).min(self.w_x.len());
3242        let x = &mut self.w_x[..x_pad_end];
3243        self.w_collapse_masks[..nb_ebands * channels].fill(0);
3244        let collapse_masks = &mut self.w_collapse_masks[..nb_ebands * channels];
3245
3246        let (x_split, y_split) = x.split_at_mut(frame_size);
3247        let y_opt = if channels == 2 { Some(y_split) } else { None };
3248
3249        let mut dual_stereo = dual_stereo_val != 0;
3250        self.w_band_amp[..nb_ebands * channels].fill(0.0);
3251        let band_amp = &mut self.w_band_amp[..nb_ebands * channels];
3252        log2amp(mode, nb_ebands, band_amp, &self.old_band_e, channels);
3253        quant_all_bands(
3254            false,
3255            mode,
3256            start_band,
3257            end_band,
3258            x_split,
3259            y_opt,
3260            collapse_masks,
3261            band_amp,
3262            pulses,
3263            short_blocks,
3264            spread_decision,
3265            &mut dual_stereo,
3266            intensity as usize,
3267            tf_res,
3268            (total_bits << BITRES) - anti_collapse_rsv,
3269            &mut balance,
3270            rc,
3271            lm as i32,
3272            coded_bands,
3273            true,
3274            false,
3275            &mut self.rng,
3276        );
3277        // Trace X values for comparison with C decoder
3278        let mut anti_collapse_on = false;
3279        if anti_collapse_rsv > 0 {
3280            anti_collapse_on = rc.dec_bits(1) != 0;
3281        }
3282
3283        unquant_energy_finalise(
3284            mode,
3285            start_band,
3286            end_band,
3287            &mut self.old_band_e,
3288            ebits,
3289            fine_priority,
3290            total_bits - rc.tell(),
3291            rc,
3292            channels,
3293        );
3294        if anti_collapse_on {
3295            // libopus passes `end`, not nbEBands: for narrower bandwidths (e.g.
3296            // SWB end=19) anti-collapsing the uncoded bands would burn PRNG draws
3297            // and desync the noise-fill seed for every subsequent frame.
3298            self.rng = crate::bands::anti_collapse(
3299                mode,
3300                x,
3301                collapse_masks,
3302                lm as i32,
3303                channels,
3304                frame_size,
3305                start_band,
3306                end_band,
3307                &self.old_band_e,
3308                &self.old_band_e2,
3309                &self.old_band_e3,
3310                pulses,
3311                self.rng,
3312            );
3313        }
3314
3315        // libopus celt_decoder.c:1107: silence floors the coded channels' energy to
3316        // -28 (so the next frame's inter prediction starts from "very quiet") and
3317        // renders a zero spectrum — the frame's output is just the MDCT overlap
3318        // fade-out of the previous frame.
3319        if silence {
3320            for i in 0..channels * nb_ebands {
3321                self.old_band_e[i] = -28.0;
3322            }
3323        }
3324
3325        // Recompute band_amp after unquant_energy_finalise, which adjusts old_band_e.
3326        // (Mirrors the encoder's resynth path: log2amp is called after quant_energy_finalise.)
3327        log2amp(mode, nb_ebands, band_amp, &self.old_band_e, channels);
3328        self.w_freq[..frame_size * channels].fill(0.0);
3329        let freq = &mut self.w_freq[..frame_size * channels];
3330        if !silence {
3331            denormalise_bands(
3332                mode,
3333                x,
3334                freq,
3335                band_amp,
3336                start_band,
3337                end_band,
3338                channels,
3339                (1 << lm) as usize,
3340            );
3341        }
3342        // Always trace freq and band_amp for comparison
3343
3344        let (shift, b) = if short_blocks {
3345            (mode.max_lm, 1 << lm)
3346        } else {
3347            (mode.max_lm - lm, 1)
3348        };
3349        let n = frame_size / b;
3350
3351        for c in 0..cc {
3352            // A mono packet (C=1) in a stereo decoder (CC=2) renders its single
3353            // decoded channel into both outputs: re-run the iMDCT reading channel
3354            // 0's freq (fc clamps to C-1). Re-synthesis (not a decode_mem copy) is
3355            // required so the per-channel postfilter/deemph below run exactly once
3356            // each; denormalise_bands leaves freq unmodified so this is exact.
3357            let fc = c.min(channels - 1);
3358            let channel_mem_offset = c * (DECODE_BUFFER_SIZE + overlap);
3359
3360            let mem_size = DECODE_BUFFER_SIZE + overlap;
3361            self.decode_mem.copy_within(
3362                channel_mem_offset + frame_size..channel_mem_offset + mem_size,
3363                channel_mem_offset,
3364            );
3365
3366            let out_syn_idx = DECODE_BUFFER_SIZE - frame_size;
3367
3368            for i in 0..b {
3369                let block_freq_idx = fc * frame_size + i;
3370                // Stride between short-block MDCT outputs is short_mdct_size (not n).
3371                // In libopus: out_syn[c] + NB*b, where NB = mode->shortMdctSize.
3372                // For non-transient b=1, i*n == 0 either way.
3373                let block_stride = if short_blocks {
3374                    mode.short_mdct_size
3375                } else {
3376                    n
3377                };
3378                let block_out_idx = channel_mem_offset + out_syn_idx + i * block_stride;
3379                let available_len = self.decode_mem.len() - block_out_idx;
3380                if available_len < n + overlap {
3381                    panic!(
3382                        "MDCT backward buffer too small: need {}, have {} (out_syn_idx={}, n={}, overlap={})",
3383                        n + overlap,
3384                        available_len,
3385                        out_syn_idx,
3386                        n,
3387                        overlap
3388                    );
3389                }
3390                self.mode.mdct.backward(
3391                    &freq[block_freq_idx..],
3392                    &mut self.decode_mem[block_out_idx..],
3393                    mode.window,
3394                    overlap,
3395                    shift,
3396                    b,
3397                );
3398            }
3399
3400            const SIG_SAT: f32 = 536870911.0;
3401            for i in 0..frame_size {
3402                let v = &mut self.decode_mem[channel_mem_offset + out_syn_idx + i];
3403                *v = v.clamp(-SIG_SAT, SIG_SAT);
3404            }
3405
3406            self.w_pcm_frame[..frame_size].fill(0.0);
3407            let pcm_frame = &mut self.w_pcm_frame[..frame_size];
3408
3409            pcm_frame.copy_from_slice(
3410                &self.decode_mem[channel_mem_offset + out_syn_idx
3411                    ..channel_mem_offset + out_syn_idx + frame_size],
3412            );
3413            if pf_on || self.prefilter_gain > 0.0 || self.prefilter_gain_old > 0.0 {
3414                // Set up w_post = [prefilter_mem | pcm_frame] for history access.
3415                // We apply combfilter in-place on w_post[COMBFILTER_MAXPERIOD..] so that
3416                // later samples can reference already-filtered earlier samples, matching C's
3417                // in-place comb_filter behavior.
3418                self.w_post[..COMBFILTER_MAXPERIOD].copy_from_slice(
3419                    &self.prefilter_mem[c * COMBFILTER_MAXPERIOD..(c + 1) * COMBFILTER_MAXPERIOD],
3420                );
3421                self.w_post[COMBFILTER_MAXPERIOD..COMBFILTER_MAXPERIOD + frame_size]
3422                    .copy_from_slice(pcm_frame);
3423
3424                let short_n = mode.short_mdct_size;
3425                // Call 1: first short_n samples, transition old→current params
3426                // Apply in-place on w_post[COMBFILTER_MAXPERIOD..], output overwrites input
3427                comb_filter_inplace(
3428                    &mut self.w_post,
3429                    COMBFILTER_MAXPERIOD,
3430                    self.prefilter_period_old,
3431                    self.prefilter_period,
3432                    short_n,
3433                    self.prefilter_gain_old,
3434                    self.prefilter_gain,
3435                    self.prefilter_tapset_old,
3436                    self.prefilter_tapset,
3437                    mode.window,
3438                    overlap,
3439                );
3440                if lm != 0 {
3441                    // Call 2: remaining N-short_n samples, transition current→new params
3442                    comb_filter_inplace(
3443                        &mut self.w_post,
3444                        COMBFILTER_MAXPERIOD + short_n,
3445                        self.prefilter_period,
3446                        pitch_index,
3447                        frame_size - short_n,
3448                        self.prefilter_gain,
3449                        gain1,
3450                        self.prefilter_tapset,
3451                        prefilter_tapset as i32,
3452                        mode.window,
3453                        overlap,
3454                    );
3455                }
3456
3457                pcm_frame.copy_from_slice(
3458                    &self.w_post[COMBFILTER_MAXPERIOD..COMBFILTER_MAXPERIOD + frame_size],
3459                );
3460
3461                self.decode_mem[channel_mem_offset + out_syn_idx
3462                    ..channel_mem_offset + out_syn_idx + frame_size]
3463                    .copy_from_slice(pcm_frame);
3464            }
3465            let mut new_mem = [0.0f32; COMBFILTER_MAXPERIOD];
3466            if frame_size >= COMBFILTER_MAXPERIOD {
3467                new_mem.copy_from_slice(&pcm_frame[frame_size - COMBFILTER_MAXPERIOD..frame_size]);
3468            } else {
3469                new_mem[..COMBFILTER_MAXPERIOD - frame_size].copy_from_slice(
3470                    &self.prefilter_mem
3471                        [c * COMBFILTER_MAXPERIOD + frame_size..(c + 1) * COMBFILTER_MAXPERIOD],
3472                );
3473                new_mem[COMBFILTER_MAXPERIOD - frame_size..].copy_from_slice(pcm_frame);
3474            }
3475            self.prefilter_mem[c * COMBFILTER_MAXPERIOD..(c + 1) * COMBFILTER_MAXPERIOD]
3476                .copy_from_slice(&new_mem);
3477
3478            let coef = mode.preemph[0];
3479            let mut m = self.preemph_mem[c];
3480            const VERY_SMALL: f32 = 1e-30f32;
3481            for i in 0..frame_size {
3482                let x = pcm_frame[i];
3483                let val = (x + VERY_SMALL + m).clamp(-SIG_SAT, SIG_SAT);
3484                pcm[c * frame_size + i] = val * (1.0 / 32768.0);
3485                m = val * coef;
3486            }
3487            self.preemph_mem[c] = m;
3488        }
3489
3490        self.prefilter_period_old = self.prefilter_period;
3491        self.prefilter_gain_old = self.prefilter_gain;
3492        self.prefilter_tapset_old = self.prefilter_tapset;
3493
3494        if pf_on {
3495            self.prefilter_period = pitch_index;
3496            self.prefilter_gain = gain1;
3497            self.prefilter_tapset = prefilter_tapset as i32;
3498        } else {
3499            self.prefilter_period = COMBFILTER_MINPERIOD;
3500            self.prefilter_gain = 0.0;
3501            self.prefilter_tapset = 0;
3502        }
3503
3504        if lm > 0 {
3505            self.prefilter_period_old = self.prefilter_period;
3506            self.prefilter_gain_old = self.prefilter_gain;
3507            self.prefilter_tapset_old = self.prefilter_tapset;
3508        }
3509
3510        // libopus celt_decoder.c:1140: after a mono frame in a stereo decoder,
3511        // replicate channel 0's coarse energy to channel 1 — this keeps ch1's
3512        // prediction state current through mono runs (and is what makes the
3513        // pre-decode MAX-merge a first-frame-only event).
3514        if channels == 1 && cc == 2 {
3515            let (ch0, ch1) = self.old_band_e.split_at_mut(nb_ebands);
3516            ch1[..nb_ebands].copy_from_slice(&ch0[..nb_ebands]);
3517        }
3518
3519        // oldLogE/oldLogE2 updates run over ALL state channels (2*nbEBands in
3520        // libopus), not just the coded ones.
3521        if !is_transient {
3522            self.old_band_e3.copy_from_slice(&self.old_band_e2);
3523            self.old_band_e2.copy_from_slice(&self.old_band_e);
3524        } else {
3525            for i in 0..cc * nb_ebands {
3526                self.old_band_e2[i] = self.old_band_e2[i].min(self.old_band_e[i]);
3527            }
3528        }
3529
3530        // "In case start or end were to change" (celt_decoder.c:1162-1174): zero
3531        // the coarse energy outside [start, end) and floor the log history, for
3532        // BOTH state channels. Matters for hybrid (start=17) and narrower
3533        // bandwidths (end<21) mixing with full-band frames in one stream.
3534        for c in 0..cc {
3535            for i in 0..start_band {
3536                self.old_band_e[c * nb_ebands + i] = 0.0;
3537                self.old_band_e2[c * nb_ebands + i] = -28.0;
3538                self.old_band_e3[c * nb_ebands + i] = -28.0;
3539            }
3540            for i in end_band..nb_ebands {
3541                self.old_band_e[c * nb_ebands + i] = 0.0;
3542                self.old_band_e2[c * nb_ebands + i] = -28.0;
3543                self.old_band_e3[c * nb_ebands + i] = -28.0;
3544            }
3545        }
3546
3547        self.rng = rc.rng;
3548        self.loss_count = 0;
3549
3550        frame_size
3551    }
3552
3553    /// Packet-loss concealment for a lost CELT frame — a port of libopus
3554    /// `celt_decode_lost` (celt_decoder.c). For the first few losses of a burst
3555    /// it uses the pitch-based branch (LPC-whitened excitation extrapolated at
3556    /// the last pitch period, resynthesized through the LPC filter — good for
3557    /// tonal/music content); once the burst runs long (`loss_count >= 5`) it
3558    /// falls back to the noise-based branch (spectrally-shaped, energy-decayed
3559    /// random excitation). Both fill the decode buffer, then this deemphasises
3560    /// to `pcm` (interleaved, /32768). Real attenuating audio instead of silence.
3561    pub fn conceal_lost(&mut self, frame_size: usize, pcm: &mut [f32]) {
3562        let n = frame_size;
3563        // start==0 for CELT-only; noise-based only once the burst is long.
3564        if self.loss_count >= 5 {
3565            self.conceal_fill_noise(n);
3566        } else {
3567            self.conceal_fill_pitch(n);
3568        }
3569
3570        // Deemphasise the concealed frame (decode_mem out_syn) to interleaved pcm.
3571        let mode = self.mode;
3572        let c = self.channels;
3573        let overlap = mode.overlap;
3574        let mem_size = DECODE_BUFFER_SIZE + overlap;
3575        let out_syn_idx = DECODE_BUFFER_SIZE - n;
3576        const SIG_SAT: f32 = 536870911.0;
3577        const VERY_SMALL: f32 = 1e-30f32;
3578        let coef = mode.preemph[0];
3579        for ch in 0..c {
3580            let out = ch * mem_size + out_syn_idx;
3581            let mut m = self.preemph_mem[ch];
3582            for i in 0..n {
3583                let x = self.decode_mem[out + i];
3584                let val = (x + VERY_SMALL + m).clamp(-SIG_SAT, SIG_SAT);
3585                pcm[i * c + ch] = val * (1.0 / 32768.0);
3586                m = val * coef;
3587            }
3588            self.preemph_mem[ch] = m;
3589        }
3590
3591        self.prefilter_period_old = self.prefilter_period;
3592        self.prefilter_gain_old = self.prefilter_gain;
3593        self.prefilter_period = COMBFILTER_MINPERIOD;
3594        self.prefilter_gain = 0.0;
3595        self.loss_count += 1;
3596    }
3597
3598    /// Noise-based concealment branch (celt_decode_lost, `noise_based`): fill the
3599    /// decode buffer's out_syn region with an energy-decayed random spectrum.
3600    fn conceal_fill_noise(&mut self, n: usize) {
3601        let mode = self.mode;
3602        let nb_ebands = mode.nb_ebands;
3603        let overlap = mode.overlap;
3604        let c = self.channels;
3605        let start = 0usize;
3606        let end = nb_ebands;
3607        let eff_end = end.min(mode.eff_ebands);
3608        let mem_size = DECODE_BUFFER_SIZE + overlap;
3609
3610        let mut lm = 0usize;
3611        while (mode.short_mdct_size << lm) != n && lm < mode.max_lm {
3612            lm += 1;
3613        }
3614
3615        let decay = if self.loss_count == 0 { 1.5f32 } else { 0.5f32 };
3616        for ch in 0..c {
3617            for i in start..end {
3618                let e = &mut self.old_band_e[ch * nb_ebands + i];
3619                *e = (*e - decay).max(-28.0);
3620            }
3621        }
3622
3623        let mut seed = self.rng;
3624        self.w_x[..n * c].fill(0.0);
3625        for ch in 0..c {
3626            for i in start..eff_end {
3627                let boffs = n * ch + ((mode.e_bands[i] as usize) << lm);
3628                let blen = ((mode.e_bands[i + 1] - mode.e_bands[i]) as usize) << lm;
3629                for j in 0..blen {
3630                    seed = crate::bands::celt_lcg_rand(seed);
3631                    self.w_x[boffs + j] = ((seed as i32) >> 20) as f32;
3632                }
3633                crate::bands::renormalise_vector(&mut self.w_x[boffs..boffs + blen], blen, 1.0);
3634            }
3635        }
3636        self.rng = seed;
3637
3638        for ch in 0..c {
3639            let base = ch * mem_size;
3640            self.decode_mem
3641                .copy_within(base + n..base + DECODE_BUFFER_SIZE + overlap / 2, base);
3642        }
3643
3644        self.w_band_amp[..nb_ebands * c].fill(0.0);
3645        let band_amp = &mut self.w_band_amp[..nb_ebands * c];
3646        log2amp(mode, nb_ebands, band_amp, &self.old_band_e, c);
3647        self.w_freq[..n * c].fill(0.0);
3648        let freq = &mut self.w_freq[..n * c];
3649        denormalise_bands(mode, &self.w_x, freq, band_amp, start, end, c, 1usize << lm);
3650
3651        let shift = mode.max_lm - lm;
3652        let out_syn_idx = DECODE_BUFFER_SIZE - n;
3653        const SIG_SAT: f32 = 536870911.0;
3654        for ch in 0..c {
3655            let out = ch * mem_size + out_syn_idx;
3656            self.mode.mdct.backward(
3657                &freq[ch * n..],
3658                &mut self.decode_mem[out..],
3659                mode.window,
3660                overlap,
3661                shift,
3662                1,
3663            );
3664            for i in 0..n {
3665                let v = &mut self.decode_mem[out + i];
3666                *v = v.clamp(-SIG_SAT, SIG_SAT);
3667            }
3668        }
3669    }
3670
3671    /// Pitch-based concealment branch (celt_decode_lost, pitch-based): extrapolate
3672    /// the LPC-whitened excitation at the last pitch period with per-period decay,
3673    /// resynthesize through the LPC filter, then TDAC-fold the overlap.
3674    fn conceal_fill_pitch(&mut self, n: usize) {
3675        let mode = self.mode;
3676        let overlap = mode.overlap;
3677        let c = self.channels;
3678        let mem_size = DECODE_BUFFER_SIZE + overlap;
3679        const MAX_PERIOD: usize = COMBFILTER_MAXPERIOD;
3680        let ord = PLC_LPC_ORDER;
3681        let out_syn_idx = DECODE_BUFFER_SIZE - n;
3682        const SIG_SAT: f32 = 536870911.0;
3683        let window = mode.window;
3684
3685        // Pitch lag: search on the first loss, reuse across the burst.
3686        let mut fade = 1.0f32;
3687        if self.loss_count == 0 {
3688            let mut lp = vec![0.0f32; DECODE_BUFFER_SIZE >> 1];
3689            let slices: Vec<&[f32]> = (0..c)
3690                .map(|ch| &self.decode_mem[ch * mem_size..ch * mem_size + DECODE_BUFFER_SIZE])
3691                .collect();
3692            crate::pitch::pitch_downsample(&slices, &mut lp, DECODE_BUFFER_SIZE >> 1, c, 2);
3693            let pr = crate::pitch::pitch_search(
3694                &lp[PLC_PITCH_LAG_MAX >> 1..],
3695                &lp,
3696                DECODE_BUFFER_SIZE - PLC_PITCH_LAG_MAX,
3697                PLC_PITCH_LAG_MAX - PLC_PITCH_LAG_MIN,
3698            );
3699            self.last_pitch_index = (PLC_PITCH_LAG_MAX - pr) as i32;
3700        } else {
3701            fade = 0.8;
3702        }
3703        let pitch_index = (self.last_pitch_index.max(1) as usize).min(MAX_PERIOD - 1);
3704        let exc_length = (2 * pitch_index).min(MAX_PERIOD);
3705
3706        let mut etmp = vec![0.0f32; overlap];
3707        for ch in 0..c {
3708            let base = ch * mem_size;
3709            // exc[k] = exc_buf[ord + k] for k in -ord..MAX_PERIOD.
3710            let mut exc_buf = vec![0.0f32; MAX_PERIOD + ord];
3711            for (i, v) in exc_buf.iter_mut().enumerate() {
3712                *v = self.decode_mem[base + DECODE_BUFFER_SIZE - MAX_PERIOD - ord + i];
3713            }
3714            if self.loss_count == 0 {
3715                let mut ac = vec![0.0f32; ord + 1];
3716                crate::celt_lpc::autocorr(
3717                    &exc_buf[ord..ord + MAX_PERIOD],
3718                    &mut ac,
3719                    Some(window),
3720                    overlap,
3721                    ord,
3722                    MAX_PERIOD,
3723                );
3724                ac[0] *= 1.0001; // -40 dB noise floor
3725                for i in 1..=ord {
3726                    ac[i] -= ac[i] * (0.008 * 0.008) * (i * i) as f32; // lag windowing
3727                }
3728                let mut lc = vec![0.0f32; ord];
3729                crate::celt_lpc::lpc(&mut lc, &ac, ord);
3730                self.plc_lpc[ch * ord..ch * ord + ord].copy_from_slice(&lc);
3731            }
3732            let lc: Vec<f32> = self.plc_lpc[ch * ord..ch * ord + ord].to_vec();
3733
3734            // Whiten the last exc_length excitation samples (celt_fir with history
3735            // — pass the ord preceding samples and read outputs at [ord..]).
3736            {
3737                let x = &exc_buf[MAX_PERIOD - exc_length..];
3738                let mut y = vec![0.0f32; ord + exc_length];
3739                crate::celt_lpc::celt_fir(x, &lc, &mut y, ord + exc_length, ord);
3740                for i in 0..exc_length {
3741                    exc_buf[ord + MAX_PERIOD - exc_length + i] = y[ord + i];
3742                }
3743            }
3744
3745            // Decay factor from the excitation energy ratio (avoid adding energy).
3746            let decay_length = exc_length >> 1;
3747            let mut e1 = 1.0f32;
3748            let mut e2 = 1.0f32;
3749            for i in 0..decay_length {
3750                let a = exc_buf[ord + MAX_PERIOD - decay_length + i];
3751                e1 += a * a;
3752                let b = exc_buf[ord + MAX_PERIOD - 2 * decay_length + i];
3753                e2 += b * b;
3754            }
3755            e1 = e1.min(e2);
3756            let decay = (e1 / e2).sqrt();
3757
3758            // Shift decode buffer one frame left.
3759            self.decode_mem
3760                .copy_within(base + n..base + DECODE_BUFFER_SIZE, base);
3761
3762            // Extrapolate at period `pitch_index`, attenuating each period.
3763            let extrapolation_offset = MAX_PERIOD - pitch_index;
3764            let extrapolation_len = n + overlap;
3765            let mut atten = fade * decay;
3766            let mut j = 0usize;
3767            let mut s1 = 0.0f32;
3768            for i in 0..extrapolation_len {
3769                if j >= pitch_index {
3770                    j -= pitch_index;
3771                    atten *= decay;
3772                }
3773                self.decode_mem[base + out_syn_idx + i] =
3774                    atten * exc_buf[ord + extrapolation_offset + j];
3775                let tmp = self.decode_mem
3776                    [base + (DECODE_BUFFER_SIZE - MAX_PERIOD - n) + extrapolation_offset + j];
3777                s1 += tmp * tmp;
3778                j += 1;
3779            }
3780
3781            // Resynthesize: excitation -> signal through the LPC synthesis filter.
3782            let mut lpc_mem = [0.0f32; PLC_LPC_ORDER];
3783            for (i, v) in lpc_mem.iter_mut().enumerate().take(ord) {
3784                *v = self.decode_mem[base + DECODE_BUFFER_SIZE - n - 1 - i];
3785            }
3786            let extrap: Vec<f32> = self.decode_mem
3787                [base + out_syn_idx..base + out_syn_idx + extrapolation_len]
3788                .to_vec();
3789            crate::celt_lpc::celt_iir(
3790                &extrap,
3791                &lc,
3792                &mut self.decode_mem[base + out_syn_idx..base + out_syn_idx + extrapolation_len],
3793                extrapolation_len,
3794                ord,
3795                &mut lpc_mem[..ord],
3796            );
3797            for i in 0..extrapolation_len {
3798                let v = &mut self.decode_mem[base + out_syn_idx + i];
3799                *v = v.clamp(-SIG_SAT, SIG_SAT);
3800            }
3801
3802            // Explosion / NaN guard (the !(S1 > .2*S2) test also catches IIR NaNs).
3803            let mut s2 = 0.0f32;
3804            for i in 0..extrapolation_len {
3805                let t = self.decode_mem[base + out_syn_idx + i];
3806                s2 += t * t;
3807            }
3808            if !(s1 > 0.2 * s2) {
3809                for i in 0..extrapolation_len {
3810                    self.decode_mem[base + out_syn_idx + i] = 0.0;
3811                }
3812            } else if s1 < s2 {
3813                let ratio = ((s1 + 1.0) / (s2 + 1.0)).sqrt();
3814                for i in 0..overlap {
3815                    let g = 1.0 - window[i] * (1.0 - ratio);
3816                    self.decode_mem[base + out_syn_idx + i] *= g;
3817                }
3818                for i in overlap..extrapolation_len {
3819                    self.decode_mem[base + out_syn_idx + i] *= ratio;
3820                }
3821            }
3822
3823            // Re-apply the postfilter to the overlap, then TDAC-fold so the
3824            // concealed audio blends with the next frame's MDCT.
3825            comb_filter(
3826                &mut etmp,
3827                &self.decode_mem,
3828                0,
3829                base + DECODE_BUFFER_SIZE,
3830                self.prefilter_period,
3831                self.prefilter_period,
3832                overlap,
3833                -self.prefilter_gain,
3834                -self.prefilter_gain,
3835                self.prefilter_tapset,
3836                self.prefilter_tapset,
3837                window,
3838                0,
3839            );
3840            for i in 0..overlap / 2 {
3841                self.decode_mem[base + DECODE_BUFFER_SIZE + i] =
3842                    window[i] * etmp[overlap - 1 - i] + window[overlap - 1 - i] * etmp[i];
3843            }
3844        }
3845    }
3846}
3847
3848#[cfg(test)]
3849mod tests {
3850    use super::*;
3851    use crate::{modes, range_coder::RangeCoder};
3852
3853    // Regression test: directly drive CeltEncoder with an invalid frame_size=48,
3854    // bypassing the OpusEncoder::encode() validation layer.
3855    //
3856    // This reproduces the crash that was reported against opus-rs 0.1.19 when
3857    // G.729-decoded PCM (8 kHz) reached the 48 kHz Opus encoder without correct
3858    // resampling, producing a 48-sample frame instead of 480.
3859    //
3860    // Root cause: the lm-search in encode_impl finds no valid match for frame_size=48
3861    // (valid sizes are 120, 240, 480, 960) and silently falls back to lm=0.
3862    // With lm=0 and shift=max_lm=3: n=1920>>3=240, n2=120, overlap2=60.
3863    // The in_buf slice has only frame_size+overlap=168 elements, but forward()
3864    // requires input.len() >= n2+overlap2 = 180, so it panics immediately.
3865    // In opus-rs 0.1.19 this assertion was absent and the crash reached the MDCT
3866    // output write: "index out of bounds: the len is 48 but the index is 119".
3867    //
3868    // Either way: the call panics, confirming the crash path is real.
3869    // The fix in OpusEncoder::encode() returns Err before reaching CeltEncoder.
3870    #[test]
3871    #[should_panic]
3872    fn test_celt_frame_size_48_panics_confirms_crash_path() {
3873        let mode = modes::default_mode();
3874        let mut enc = CeltEncoder::new(mode, 1);
3875        // frame_size=48: lm-search fails, falls back to lm=0.
3876        // forward() will panic — either on the input-size assertion (0.1.21+) or
3877        // on the output write (0.1.19): "len is 48 but the index is 119".
3878        let pcm = vec![0.0f32; 48 + mode.overlap]; // supply ≥ frame_size samples
3879        let mut rc = RangeCoder::new_encoder(100);
3880        enc.encode_with_budget(&pcm, 48, &mut rc, 0, 21, 800);
3881    }
3882
3883    // Prefilter/postfilter inversion, MDCT bypassed: run the real run_prefilter
3884    // per frame (with the real signalling quantization of gain/period), feed the
3885    // FILTERED stream straight into the decoder's postfilter sequence (call 1
3886    // old->current over shortMdctSize, call 2 current->new with the crossfade),
3887    // honoring the 120-sample MDCT delay. If the encoder applies exactly what it
3888    // signals with the timing the decoder inverts, the round trip is ~identity.
3889    #[test]
3890    fn prefilter_postfilter_inversion() {
3891        let mode = modes::default_mode();
3892        let n = 960usize;
3893        let overlap = mode.overlap; // 120
3894        let short_n = mode.short_mdct_size; // 120
3895        let frames = 100usize;
3896        let max_period = COMBFILTER_MAXPERIOD;
3897
3898        // Signal designed to TOGGLE the prefilter: alternating strongly periodic
3899        // stretches (varying pitch) and noise bursts.
3900        let total = frames * n;
3901        let mut x = vec![0.0f32; total];
3902        let mut rng = 0x12345678u32;
3903        let mut next = || {
3904            rng = rng.wrapping_mul(1664525).wrapping_add(1013904223);
3905            (rng >> 8) as f32 / (1 << 24) as f32 - 0.5
3906        };
3907        for (t, v) in x.iter_mut().enumerate() {
3908            let seg = t / (n * 10);
3909            let phase = t as f32;
3910            *v = match seg % 4 {
3911                0 => (phase * std::f32::consts::TAU / 147.0).sin() * 8000.0, // ~326 Hz
3912                1 => next() * 6000.0,
3913                2 => {
3914                    ((phase * std::f32::consts::TAU / 89.0).sin()
3915                        + 0.5 * (phase * std::f32::consts::TAU / 44.5).sin())
3916                        * 7000.0
3917                }
3918                _ => (phase * std::f32::consts::TAU / 480.0).sin() * 5000.0, // 100 Hz
3919            };
3920        }
3921
3922        // ---- encoder side ----
3923        let mut pre = vec![0.0f32; max_period + n];
3924        let mut pitch_buf = vec![0.0f32; (max_period + n) >> 1];
3925        let mut prefilter_mem = vec![0.0f32; max_period];
3926        let mut in_mem = vec![0.0f32; overlap];
3927        let (mut prev_t, mut prev_g) = (COMBFILTER_MINPERIOD, 0.0f32);
3928        let analysis = AnalysisInfo::default();
3929        let mut filtered = vec![0.0f32; total];
3930        let mut params = Vec::new(); // (pf_on, T, g) per frame
3931        let mut in_buf = vec![0.0f32; n + overlap];
3932        for k in 0..frames {
3933            in_buf[..overlap].copy_from_slice(&in_mem);
3934            in_buf[overlap..].copy_from_slice(&x[k * n..(k + 1) * n]);
3935            let (pf_on, g1, t1) = run_prefilter(
3936                &mut in_buf,
3937                &mut prefilter_mem,
3938                prev_t,
3939                prev_g,
3940                0, // prefilter_tapset (old)
3941                0, // tapset_decision (new)
3942                mode.window,
3943                1,
3944                n,
3945                overlap,
3946                &mut pre,
3947                &mut pitch_buf,
3948                &analysis,
3949                0,
3950                159,
3951            );
3952            filtered[k * n..(k + 1) * n].copy_from_slice(&in_buf[overlap..]);
3953            in_mem.copy_from_slice(&in_buf[n..]);
3954            params.push((pf_on, t1, g1));
3955            // encoder end-of-frame state update
3956            prev_t = if pf_on { t1 } else { COMBFILTER_MINPERIOD };
3957            prev_g = if pf_on { g1 } else { 0.0 };
3958        }
3959
3960        // ---- decoder side (postfilter only), 120-sample MDCT delay ----
3961        let mut delayed = vec![0.0f32; total];
3962        delayed[short_n..].copy_from_slice(&filtered[..total - short_n]);
3963        let mut w = vec![0.0f32; max_period + n];
3964        let mut post_mem = vec![0.0f32; max_period];
3965        let (mut d_t_old, mut d_g_old) = (COMBFILTER_MINPERIOD, 0.0f32);
3966        let (mut d_t, mut d_g) = (COMBFILTER_MINPERIOD, 0.0f32);
3967        let mut out = vec![0.0f32; total];
3968        for k in 0..frames {
3969            let (pf_on, sig_t, sig_g) = params[k];
3970            let (gain1, pitch_index) = if pf_on {
3971                (sig_g, sig_t)
3972            } else {
3973                (0.0, COMBFILTER_MINPERIOD)
3974            };
3975            w[..max_period].copy_from_slice(&post_mem);
3976            w[max_period..].copy_from_slice(&delayed[k * n..(k + 1) * n]);
3977            if pf_on || d_g > 0.0 || d_g_old > 0.0 {
3978                comb_filter_inplace(
3979                    &mut w, max_period, d_t_old, d_t, short_n, d_g_old, d_g, 0, 0, mode.window,
3980                    overlap,
3981                );
3982                comb_filter_inplace(
3983                    &mut w,
3984                    max_period + short_n,
3985                    d_t,
3986                    pitch_index,
3987                    n - short_n,
3988                    d_g,
3989                    gain1,
3990                    0,
3991                    0,
3992                    mode.window,
3993                    overlap,
3994                );
3995            }
3996            out[k * n..(k + 1) * n].copy_from_slice(&w[max_period..]);
3997            post_mem.copy_from_slice(&w[n..]);
3998            // decoder end-of-frame chain, then the lm > 0 override
3999            if pf_on {
4000                d_t = pitch_index;
4001                d_g = gain1;
4002            } else {
4003                d_t = COMBFILTER_MINPERIOD;
4004                d_g = 0.0;
4005            }
4006            d_t_old = d_t;
4007            d_g_old = d_g;
4008        }
4009
4010        // ---- compare out (delayed by short_n) against x ----
4011        let m = total - 2 * n;
4012        let mut se = 0.0f64;
4013        let mut sx = 0.0f64;
4014        for t in n..m {
4015            let e = (out[t + short_n] - x[t]) as f64;
4016            se += e * e;
4017            sx += (x[t] as f64) * (x[t] as f64);
4018        }
4019        let snr = 10.0 * (sx / se.max(1e-30)).log10();
4020        let engaged = params.iter().filter(|p| p.0).count();
4021        assert!(
4022            engaged > frames / 4,
4023            "prefilter never engaged ({engaged}/{frames}) — test signal too weak"
4024        );
4025        assert!(
4026            snr > 90.0,
4027            "prefilter/postfilter round trip not transparent: SNR={snr:.1} dB (engaged {engaged}/{frames})"
4028        );
4029    }
4030}