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    }
1425
1426    // Don't allocate more than 8 bits above the "depth" of the signal.
1427    {
1428        let bins = (e_bands[nb_ebands as usize - 2] as i32) << lm;
1429        let mut floor_depth = ((channels * bins << BITRES) as f32 * max_depth) as i32;
1430        floor_depth = floor_depth.max(target >> 2);
1431        target = target.min(floor_depth);
1432    }
1433
1434    // Constrained VBR can't sustain large swings.
1435    if constrained_vbr {
1436        target = base_target + (0.67 * (target - base_target) as f32) as i32;
1437    }
1438
1439    // Never more than double the base rate.
1440    target.min(2 * base_target)
1441}
1442
1443pub struct CeltEncoder {
1444    mode: &'static CeltMode,
1445    channels: usize,
1446    pub complexity: i32,
1447    syn_mem: Vec<f32>,
1448    enc_decode_mem: Vec<f32>,
1449    old_band_e: Vec<f32>,
1450    preemph_mem: Vec<f32>,
1451    tonal_average: i32,
1452    hf_average: i32,
1453    tapset_decision: i32,
1454    spread_decision: i32,
1455    intensity: i32,
1456    last_coded_bands: i32,
1457    /// Input bit depth for the dynalloc noise floors (opus lsb_depth).
1458    pub lsb_depth: i32,
1459    /// VBR target in eighth-bits per frame (0 = hard CBR). libopus vbr_rate.
1460    pub vbr_rate: i32,
1461    /// Constrained VBR (libopus default): reservoir-limited drift around target.
1462    pub constrained_vbr: bool,
1463    vbr_reservoir: i32,
1464    vbr_drift: i32,
1465    vbr_offset: i32,
1466    vbr_count: i32,
1467    prefilter_mem: Vec<f32>,
1468    prefilter_period: usize,
1469    prefilter_gain: f32,
1470    prefilter_tapset: i32,
1471    old_band_e2: Vec<f32>,
1472    old_band_e3: Vec<f32>,
1473    last_band_log_e: Vec<f32>,
1474    delayed_intra: f32,
1475
1476    w_in_buf: Vec<f32>,
1477    w_freq: Vec<f32>,
1478    w_band_e: Vec<f32>,
1479    w_x: Vec<f32>,
1480    w_band_log_e: Vec<f32>,
1481    w_band_log_e2: Vec<f32>,
1482    w_error: Vec<f32>,
1483    w_tf_res: Vec<i32>,
1484    w_cap: Vec<i32>,
1485    w_offsets: Vec<i32>,
1486    w_pulses: Vec<i32>,
1487    w_ebits: Vec<i32>,
1488    w_fine_priority: Vec<i32>,
1489    w_collapse_masks: Vec<u32>,
1490    w_band_amp_synth: Vec<f32>,
1491    w_freq_synth: Vec<f32>,
1492    consec_transient: i32,
1493
1494    w_prefilter_pre: Vec<f32>,
1495    w_prefilter_pitch_buf: Vec<f32>,
1496
1497    w_transient_tmp: Vec<f32>,
1498    w_transient_tmp2: Vec<f32>,
1499
1500    pub(crate) analysis: AnalysisInfo,
1501    /// Expected packet loss %, plumbed from `OpusEncoder.packet_loss_perc`
1502    /// each frame (was never assigned — census 2026-08-07). Drives the
1503    /// prefilter loss ladder and coarse-energy intra bias.
1504    pub(crate) loss_rate: i32,
1505    /// Enable libopus's tonality VBR boost in `compute_vbr_target`
1506    /// (`RUSTY_OPUS_TONAL_VBR`). Opt-in until the per-class ladder clears it;
1507    /// off = byte-identical.
1508    pub(crate) tonal_vbr: bool,
1509    /// Emit the CELT per-frame silence flag. **Default ON** since 2026-08-07;
1510    /// `RUSTY_OPUS_SILENCE_FLAG=0` restores the previous byte-identical
1511    /// behaviour. Without it we spend ~69% of the active-frame rate coding
1512    /// digital silence where libopus spends ~3% (docs/great-gate.md §5.5).
1513    ///
1514    /// Gated by: 13-class rate-matched BD (mean +0.198, **worst class exactly
1515    /// +0.000**, silence_dtx +1.647), an independent libopus decode at equal
1516    /// quality for 28% fewer bits, and a CBR run that keeps packet length exact.
1517    pub(crate) silence_flag: bool,
1518    /// Peak |sample| of the previous frame's overlap tail — the `st->overlap_max`
1519    /// of celt_encoder.c, needed so silence is only declared once the region the
1520    /// MDCT folds is silent as well.
1521    overlap_max: f32,
1522}
1523
1524const INTEN_THRESHOLDS: [i32; 21] = [
1525    1, 2, 3, 4, 5, 6, 7, 8, 16, 24, 36, 44, 50, 56, 62, 67, 72, 79, 88, 106, 134,
1526];
1527const INTEN_HYSTERESIS: [i32; 21] = [
1528    1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 5, 6, 8, 8,
1529];
1530
1531fn hysteresis_decision(val: i32, thresholds: &[i32], hysteresis: &[i32], prev: i32) -> i32 {
1532    let mut i = 0;
1533    while i < thresholds.len() {
1534        if val < thresholds[i] {
1535            break;
1536        }
1537        i += 1;
1538    }
1539    let mut res = i as i32;
1540    if res > prev && val < thresholds[prev as usize] + hysteresis[prev as usize] {
1541        res = prev;
1542    }
1543    if res < prev && res > 0 && val > thresholds[prev as usize - 1] - hysteresis[prev as usize - 1]
1544    {
1545        res = prev;
1546    }
1547    res
1548}
1549
1550#[allow(clippy::too_many_arguments)]
1551fn alloc_trim_analysis(
1552    mode: &CeltMode,
1553    x: &[f32],
1554    band_log_e: &[f32],
1555    end: usize,
1556    lm: i32,
1557    channels: usize,
1558    n0: usize,
1559    stereo_saving: &mut f32,
1560    tf_estimate: f32,
1561    intensity: i32,
1562    surround_trim: f32,
1563    equiv_rate: i32,
1564) -> i32 {
1565    let _prof = crate::prof::scope(crate::prof::Stage::CeltAlloc);
1566    let mut trim = 5.0f32;
1567    if equiv_rate < 64000 {
1568        trim = 4.0;
1569    } else if equiv_rate < 80000 {
1570        let frac = (equiv_rate - 64000) as f32 / 1024.0;
1571        trim = 4.0 + (1.0 / 16.0) * frac;
1572    }
1573
1574    if channels == 2 {
1575        let mut sum = 0.0f32;
1576        for i in 0..8 {
1577            let offset = (mode.e_bands[i] as usize) << lm;
1578            let n = ((mode.e_bands[i + 1] - mode.e_bands[i]) as usize) << lm;
1579            let mut partial = 0.0f32;
1580            for j in 0..n {
1581                partial += x[offset + j] * x[n0 + offset + j];
1582            }
1583            sum += partial;
1584        }
1585        sum = (sum / 8.0).abs().min(1.0);
1586        let mut min_xc = sum;
1587        for i in 8..intensity as usize {
1588            let offset = (mode.e_bands[i] as usize) << lm;
1589            let n = ((mode.e_bands[i + 1] - mode.e_bands[i]) as usize) << lm;
1590            let mut partial = 0.0f32;
1591            for j in 0..n {
1592                partial += x[offset + j] * x[n0 + offset + j];
1593            }
1594            min_xc = min_xc.min(partial.abs());
1595        }
1596        min_xc = min_xc.min(1.0);
1597
1598        let log_xc = (1.001 - sum * sum).log2();
1599        let log_xc2 = (log_xc * 0.5).max((1.001 - min_xc * min_xc).log2());
1600
1601        trim += (-4.0f32).max(0.75 * log_xc);
1602        *stereo_saving = (*stereo_saving + 0.25).min(-0.5 * log_xc2);
1603    }
1604
1605    let mut diff = 0.0f32;
1606    for c in 0..channels {
1607        for i in 0..end - 1 {
1608            diff += band_log_e[c * mode.nb_ebands + i] * (2 + 2 * i as i32 - end as i32) as f32;
1609        }
1610    }
1611    diff /= (channels * (end - 1)) as f32;
1612    trim -= (-2.0f32).max(2.0f32.min((diff + 1.0) / 6.0));
1613    trim -= surround_trim;
1614    trim -= 2.0 * tf_estimate;
1615
1616    // Stereo-music LF tilt (PEAQ-tuned, opt-out via env NO_STEREO_TRIM). Our
1617    // per-output analysis lands the trim slightly lower than is perceptually ideal
1618    // for coupled stereo music — tilting a little more toward LF (where our coding
1619    // is strongest) recovers ~0.03–0.10 ODG on stereo music across 64–192 kbps with
1620    // no regressions (a +2 tilt was stronger at mid rates but starved HF at 64k under
1621    // VBR rate overlap; +1 is the safe, monotonic choice). Mono is untouched, and
1622    // trim is transmitted so encoder/decoder stay in sync — fully conformant.
1623    let _ = equiv_rate;
1624    // Env read cached once (was a per-stereo-frame var() inside a profiled
1625    // stage — Great Gate census 2026-08-07 hygiene batch).
1626    static STEREO_TRIM_OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1627    if channels == 2
1628        && !*STEREO_TRIM_OFF.get_or_init(|| std::env::var_os("NO_STEREO_TRIM").is_some())
1629    {
1630        trim += 1.0;
1631    }
1632
1633    let trim_index = (trim + 0.5).floor() as i32;
1634    trim_index.clamp(0, 10)
1635}
1636
1637#[inline(always)]
1638fn median3(a: f32, b: f32, c: f32) -> f32 {
1639    let mut v = [a, b, c];
1640    v.sort_by(|x, y| x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal));
1641    v[1]
1642}
1643
1644#[inline(always)]
1645fn median5(v: &[f32]) -> f32 {
1646    let mut x = [v[0], v[1], v[2], v[3], v[4]];
1647    x.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1648    x[2]
1649}
1650
1651/// Full port of celt_encoder.c dynalloc_analysis: per-band boosts (offsets),
1652/// the tf importance weights, the spreading-decision SMR weights, and maxDepth
1653/// (the signal depth over the noise floor, used as the VBR ceiling). Consumes
1654/// the pre-transient band logs (band_log_e2) and the analysis leak_boost.
1655#[allow(clippy::too_many_arguments)]
1656fn dynalloc_analysis(
1657    mode: &CeltMode,
1658    band_log_e: &[f32],
1659    band_log_e2: &[f32],
1660    start: usize,
1661    end: usize,
1662    channels: usize,
1663    offsets: &mut [i32],
1664    lsb_depth: i32,
1665    is_transient: bool,
1666    vbr: bool,
1667    constrained_vbr: bool,
1668    lm: usize,
1669    effective_bytes: usize,
1670    analysis: &AnalysisInfo,
1671    importance: &mut [f32],
1672    spread_weight: &mut [i32],
1673) -> f32 {
1674    let _prof = crate::prof::scope(crate::prof::Stage::CeltAlloc);
1675    let nb = mode.nb_ebands;
1676    offsets.fill(0);
1677
1678    // Noise floor: eMeans, depth, band width (logN) and the preemphasis tilt
1679    // (~ square of the bark band index).
1680    let mut noise_floor = [0.0f32; MAX_NB_EBANDS];
1681    for i in 0..end {
1682        noise_floor[i] = 0.0625 * mode.log_n[i] as f32 + 0.5 + (9 - lsb_depth) as f32
1683            - mode.e_means[i]
1684            + 0.0062 * ((i + 5) * (i + 5)) as f32;
1685    }
1686    let mut max_depth = -31.9f32;
1687    for c in 0..channels {
1688        for i in 0..end {
1689            max_depth = max_depth.max(band_log_e[c * nb + i] - noise_floor[i]);
1690        }
1691    }
1692
1693    // Simple masking model for the spreading decision: ignore fully masked bands.
1694    {
1695        let mut mask = [0.0f32; MAX_NB_EBANDS];
1696        let mut sig = [0.0f32; MAX_NB_EBANDS];
1697        for i in 0..end {
1698            mask[i] = band_log_e[i] - noise_floor[i];
1699        }
1700        if channels == 2 {
1701            for i in 0..end {
1702                mask[i] = mask[i].max(band_log_e[nb + i] - noise_floor[i]);
1703            }
1704        }
1705        sig[..end].copy_from_slice(&mask[..end]);
1706        for i in 1..end {
1707            mask[i] = mask[i].max(mask[i - 1] - 2.0);
1708        }
1709        for i in (0..end.saturating_sub(1)).rev() {
1710            mask[i] = mask[i].max(mask[i + 1] - 3.0);
1711        }
1712        for i in 0..end {
1713            // SMR: mask never more than 72 dB below the peak, never below floor.
1714            let smr = sig[i] - (0.0f32.max(max_depth - 12.0)).max(mask[i]);
1715            let shift = 5.min(0.max(-((0.5 + smr).floor() as i32)));
1716            spread_weight[i] = 32 >> shift;
1717        }
1718    }
1719
1720    // Make sure dynamic allocation can't bust the budget.
1721    if effective_bytes > 50 && lm >= 1 {
1722        let mut follower = [0.0f32; 2 * MAX_NB_EBANDS];
1723        let mut last = 0usize;
1724        for c in 0..channels {
1725            let base = c * nb;
1726            follower[base] = band_log_e2[base];
1727            for i in 1..end {
1728                // The last band at least .5 dB higher than the previous one is
1729                // the last we'll consider (band-limited signals).
1730                if band_log_e2[base + i] > band_log_e2[base + i - 1] + 0.5 {
1731                    last = i;
1732                }
1733                follower[base + i] =
1734                    (follower[base + i - 1] + 1.5).min(band_log_e2[base + i]);
1735            }
1736            for i in (0..last).rev() {
1737                follower[base + i] = follower[base + i]
1738                    .min((follower[base + i + 1] + 2.0).min(band_log_e2[base + i]));
1739            }
1740
1741            // Median filter so dynalloc doesn't trigger unnecessarily.
1742            let offset = 1.0f32;
1743            if end >= 5 {
1744                for i in 2..end - 2 {
1745                    follower[base + i] = follower[base + i]
1746                        .max(median5(&band_log_e2[base + i - 2..base + i + 3]) - offset);
1747                }
1748            }
1749            if end >= 3 {
1750                let tmp = median3(
1751                    band_log_e2[base],
1752                    band_log_e2[base + 1],
1753                    band_log_e2[base + 2],
1754                ) - offset;
1755                follower[base] = follower[base].max(tmp);
1756                follower[base + 1] = follower[base + 1].max(tmp);
1757                let tmp = median3(
1758                    band_log_e2[base + end - 3],
1759                    band_log_e2[base + end - 2],
1760                    band_log_e2[base + end - 1],
1761                ) - offset;
1762                follower[base + end - 2] = follower[base + end - 2].max(tmp);
1763                follower[base + end - 1] = follower[base + end - 1].max(tmp);
1764            }
1765
1766            for i in 0..end {
1767                follower[base + i] = follower[base + i].max(noise_floor[i]);
1768            }
1769        }
1770        if channels == 2 {
1771            for i in start..end {
1772                // Consider 24 dB "cross-talk".
1773                follower[nb + i] = follower[nb + i].max(follower[i] - 4.0);
1774                follower[i] = follower[i].max(follower[nb + i] - 4.0);
1775                follower[i] = 0.5
1776                    * ((band_log_e[i] - follower[i]).max(0.0)
1777                        + (band_log_e[nb + i] - follower[nb + i]).max(0.0));
1778            }
1779        } else {
1780            for i in start..end {
1781                follower[i] = (band_log_e[i] - follower[i]).max(0.0);
1782            }
1783        }
1784        for i in start..end {
1785            importance[i] = (0.5 + 13.0 * (follower[i].min(4.0)).exp2()).floor();
1786        }
1787        // For non-transient CBR/CVBR frames, halve the dynalloc contribution.
1788        if (!vbr || constrained_vbr) && !is_transient {
1789            for f in follower.iter_mut().take(end).skip(start) {
1790                *f *= 0.5;
1791            }
1792        }
1793        for i in start..end {
1794            if i < 8 {
1795                follower[i] *= 2.0;
1796            }
1797            if i >= 12 {
1798                follower[i] *= 0.5;
1799            }
1800        }
1801        if analysis.valid {
1802            for i in start..end.min(19) {
1803                follower[i] += analysis.leak_boost[i] as f32 * (1.0 / 64.0);
1804            }
1805        }
1806        let mut tot_boost = 0i32;
1807        for i in start..end {
1808            follower[i] = follower[i].min(4.0);
1809
1810            let width =
1811                channels as i32 * (mode.e_bands[i + 1] - mode.e_bands[i]) as i32 * (1 << lm);
1812            let (boost, boost_bits) = if width < 6 {
1813                let b = follower[i] as i32;
1814                (b, (b * width) << BITRES)
1815            } else if width > 48 {
1816                let b = (follower[i] * 8.0) as i32;
1817                (b, ((b * width) << BITRES) / 8)
1818            } else {
1819                let b = (follower[i] * width as f32 / 6.0) as i32;
1820                (b, (b * 6) << BITRES)
1821            };
1822            // For CBR and non-transient CVBR frames, limit dynalloc to 2/3 of
1823            // the bits.
1824            if (!vbr || (constrained_vbr && !is_transient))
1825                && ((tot_boost + boost_bits) >> BITRES >> 3) > 2 * effective_bytes as i32 / 3
1826            {
1827                let cap = (2 * effective_bytes as i32 / 3) << BITRES << 3;
1828                offsets[i] = cap - tot_boost;
1829                break;
1830            } else {
1831                offsets[i] = boost;
1832                tot_boost += boost_bits;
1833            }
1834        }
1835    } else {
1836        for i in start..end {
1837            importance[i] = 13.0;
1838        }
1839    }
1840    max_depth
1841}
1842
1843impl CeltEncoder {
1844    pub fn new(mode: &'static CeltMode, channels: usize) -> Self {
1845        let overlap = mode.overlap;
1846        let channel_mem_size = 2048 + overlap;
1847        let syn_mem_size = channels * channel_mem_size;
1848        let nb_ebands = mode.nb_ebands;
1849        let nb_x_ch = nb_ebands * channels;
1850        let frame_x_ch = MAX_FRAME_SIZE * channels;
1851        let bufstride_x_ch = (MAX_FRAME_SIZE + overlap) * channels;
1852        Self {
1853            mode,
1854            channels,
1855            complexity: 9,
1856            syn_mem: vec![0.0; syn_mem_size],
1857            enc_decode_mem: vec![0.0; syn_mem_size],
1858            old_band_e: vec![0.0; nb_x_ch],
1859            preemph_mem: vec![0.0; channels],
1860            tonal_average: 256,
1861            hf_average: 0,
1862            tapset_decision: 0,
1863            spread_decision: SPREAD_NORMAL,
1864            intensity: 0,
1865            last_coded_bands: 0,
1866            lsb_depth: 24,
1867            vbr_rate: 0,
1868            constrained_vbr: true,
1869            vbr_reservoir: 0,
1870            vbr_drift: 0,
1871            vbr_offset: 0,
1872            vbr_count: 0,
1873            prefilter_mem: vec![0.0; channels * COMBFILTER_MAXPERIOD],
1874            prefilter_period: COMBFILTER_MINPERIOD,
1875            prefilter_gain: 0.0,
1876            prefilter_tapset: 0,
1877            old_band_e2: vec![0.0; nb_x_ch],
1878            old_band_e3: vec![0.0; nb_x_ch],
1879            last_band_log_e: vec![0.0; nb_x_ch],
1880            delayed_intra: 0.0,
1881
1882            w_in_buf: vec![0.0; bufstride_x_ch],
1883            w_freq: vec![0.0; frame_x_ch + 4],
1884            w_band_e: vec![0.0; nb_x_ch],
1885
1886            w_x: vec![0.0; frame_x_ch + STRIDE_ACCESS_PAD],
1887            w_band_log_e: vec![0.0; nb_x_ch],
1888            w_band_log_e2: vec![0.0; nb_x_ch],
1889            w_error: vec![0.0; nb_x_ch],
1890            w_tf_res: vec![0; nb_ebands],
1891            w_cap: vec![0; nb_ebands],
1892            w_offsets: vec![0; nb_ebands],
1893            w_pulses: vec![0; nb_ebands],
1894            w_ebits: vec![0; nb_x_ch],
1895            w_fine_priority: vec![0; nb_x_ch],
1896            w_collapse_masks: vec![0; nb_x_ch],
1897            w_band_amp_synth: vec![0.0; nb_x_ch],
1898            w_freq_synth: vec![0.0; frame_x_ch + 4],
1899
1900            w_prefilter_pre: vec![0.0; channels * (COMBFILTER_MAXPERIOD + MAX_FRAME_SIZE)],
1901            w_prefilter_pitch_buf: vec![0.0; (COMBFILTER_MAXPERIOD + MAX_FRAME_SIZE) >> 1],
1902            w_transient_tmp: vec![0.0; MAX_TRANSIENT_LEN],
1903            w_transient_tmp2: vec![0.0; MAX_TRANSIENT_LEN / 2],
1904            consec_transient: 0,
1905
1906            analysis: AnalysisInfo::default(),
1907            loss_rate: 0,
1908            tonal_vbr: std::env::var_os("RUSTY_OPUS_TONAL_VBR").is_some(),
1909            silence_flag: std::env::var("RUSTY_OPUS_SILENCE_FLAG")
1910                .map(|v| v != "0")
1911                .unwrap_or(true),
1912            overlap_max: 0.0,
1913        }
1914    }
1915
1916    pub fn encode(&mut self, pcm: &[f32], frame_size: usize, rc: &mut RangeCoder) {
1917        self.encode_impl(pcm, frame_size, rc, 0, self.mode.nb_ebands, None)
1918    }
1919
1920    pub fn encode_with_start_band(
1921        &mut self,
1922        pcm: &[f32],
1923        frame_size: usize,
1924        rc: &mut RangeCoder,
1925        start_band: usize,
1926    ) {
1927        self.encode_impl(pcm, frame_size, rc, start_band, self.mode.nb_ebands, None)
1928    }
1929
1930    pub fn encode_with_budget(
1931        &mut self,
1932        pcm: &[f32],
1933        frame_size: usize,
1934        rc: &mut RangeCoder,
1935        start_band: usize,
1936        end_band: usize,
1937        total_bits: i32,
1938    ) {
1939        self.encode_impl(pcm, frame_size, rc, start_band, end_band, Some(total_bits))
1940    }
1941
1942    fn encode_impl(
1943        &mut self,
1944        pcm: &[f32],
1945        frame_size: usize,
1946        rc: &mut RangeCoder,
1947        start_band: usize,
1948        end_band: usize,
1949        explicit_total_bits: Option<i32>,
1950    ) {
1951        debug_assert!(end_band > start_band && end_band <= self.mode.nb_ebands);
1952        let mode = self.mode;
1953        let channels = self.channels;
1954        let nb_ebands = mode.nb_ebands;
1955
1956        // ---- Digital-silence detection (celt_encoder.c) ----
1957        // sample_max spans this frame's non-overlap part PLUS the previous
1958        // frame's overlap tail, so a frame is only "silent" once the region the
1959        // MDCT will actually fold is silent too. Gated by `silence_flag` until
1960        // the per-class ladder clears it; off = byte-identical.
1961        let silence = if self.silence_flag {
1962            let ovl = mode.overlap.min(frame_size);
1963            let head = (frame_size - ovl) * channels;
1964            let maxabs = |s: &[f32]| s.iter().fold(0.0f32, |m, &v| m.max(v.abs()));
1965            let n = (frame_size * channels).min(pcm.len());
1966            let head_max = maxabs(&pcm[..head.min(n)]);
1967            let tail_max = maxabs(&pcm[head.min(n)..n]);
1968            let sample_max = self.overlap_max.max(head_max).max(tail_max);
1969            self.overlap_max = tail_max;
1970            sample_max <= 1.0 / (1i64 << self.lsb_depth) as f32
1971        } else {
1972            false
1973        };
1974        let overlap = mode.overlap;
1975        // Bits already in the coder at entry (the SILK part in hybrid mode) — used
1976        // by the VBR min-size guard so shrinking never truncates them.
1977        let tell0_frac = rc.tell_frac();
1978
1979        let mut lm = 0;
1980        while (mode.short_mdct_size << lm) != frame_size {
1981            lm += 1;
1982            if lm > mode.max_lm {
1983                break;
1984            }
1985        }
1986        if (mode.short_mdct_size << lm) != frame_size {
1987            lm = 0;
1988        }
1989
1990        let _prof_pre = crate::prof::scope(crate::prof::Stage::CeltPreemph);
1991        let syn_mem_size = 2048 + overlap;
1992        for c in 0..channels {
1993            let channel_offset = c * syn_mem_size;
1994
1995            self.syn_mem.copy_within(
1996                channel_offset + frame_size..channel_offset + syn_mem_size,
1997                channel_offset,
1998            );
1999
2000            let mut m = self.preemph_mem[c];
2001            let coef = mode.preemph[0];
2002            for i in 0..frame_size {
2003                let x = pcm[c * frame_size + i] * 32768.0;
2004                let val = x - m;
2005                self.syn_mem[channel_offset + syn_mem_size - frame_size + i] = val;
2006                m = x * coef;
2007            }
2008            self.preemph_mem[c] = m;
2009        }
2010
2011        let buf_stride = frame_size + overlap;
2012        let in_buf = &mut self.w_in_buf[..buf_stride * channels];
2013        for c in 0..channels {
2014            let channel_offset = c * syn_mem_size;
2015            let in_buf_offset = c * buf_stride;
2016
2017            let src_start = syn_mem_size - frame_size - overlap;
2018            in_buf[in_buf_offset..in_buf_offset + buf_stride].copy_from_slice(
2019                &self.syn_mem[channel_offset + src_start..channel_offset + syn_mem_size],
2020            );
2021        }
2022
2023        drop(_prof_pre);
2024
2025        // Encoder pitch prefilter (the inverse of the decoder postfilter).
2026        // Enable gate matches celt_encoder.c: enough bytes to be worth the ~7
2027        // bits, CELT-only (start_band == 0; the hybrid high band has no pf),
2028        // complexity >= 5. `CELT_PF_OFF` disables for A/B debugging.
2029        // (History: default-off until 2026-07-09 — the octave signalling was one
2030        // low for every pitch_index >= 31, so decoders reconstructed a garbage
2031        // period; fixed, sine round-trip 7.6 -> 45.7 dB.)
2032        let nb_available_bytes = (explicit_total_bits.unwrap_or((rc.buf.len() * 8) as i32) >> 3)
2033            - ((rc.tell() + 4) >> 3);
2034        // Env read cached once (was per-frame — census 2026-08-07 hygiene batch).
2035        static PF_OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2036        let pf_enabled = start_band == 0
2037            && self.complexity >= 5
2038            && nb_available_bytes > 12 * channels as i32
2039            && !*PF_OFF.get_or_init(|| std::env::var_os("CELT_PF_OFF").is_some());
2040        // Capture the tapset used for THIS frame's comb (C's `prefilter_tapset`
2041        // local): spreading_decision mutates self.tapset_decision later in the
2042        // frame, and the value applied+signalled here — not the mutated one —
2043        // must become next frame's "old" tapset.
2044        let prefilter_tapset = self.tapset_decision;
2045        let (pf_on, gain1, pitch_index) = if pf_enabled {
2046            run_prefilter(
2047                in_buf,
2048                &mut self.prefilter_mem,
2049                self.prefilter_period,
2050                self.prefilter_gain,
2051                self.prefilter_tapset,
2052                prefilter_tapset,
2053                mode.window,
2054                channels,
2055                frame_size,
2056                overlap,
2057                &mut self.w_prefilter_pre,
2058                &mut self.w_prefilter_pitch_buf,
2059                &self.analysis,
2060                self.loss_rate,
2061                nb_available_bytes,
2062            )
2063        } else {
2064            (false, 0.0f32, COMBFILTER_MINPERIOD)
2065        };
2066
2067        // Save the prefiltered overlap for the next frame.
2068        // In libopus, st->in_mem stores the overlap separately and run_prefilter
2069        // copies it to/from in[]. Here we emulate that by updating syn_mem with
2070        // the last overlap samples of in_buf (which were prefiltered in place).
2071        let syn_mem_size = 2048 + overlap;
2072        for c in 0..channels {
2073            let channel_offset = c * syn_mem_size;
2074            let in_buf_offset = c * buf_stride;
2075            self.syn_mem[channel_offset + syn_mem_size - overlap..channel_offset + syn_mem_size]
2076                .copy_from_slice(&in_buf[in_buf_offset + frame_size..in_buf_offset + buf_stride]);
2077        }
2078
2079        // Transient analysis runs on the PREFILTERED signal (celt_encoder.c
2080        // order) — the comb removes periodic energy so pitch pulses don't read
2081        // as transients.
2082        let mut tf_estimate = 0.0f32;
2083        let mut tf_chan = 0;
2084        let mut weak_transient = false;
2085        let is_transient = if self.complexity >= 1 {
2086            transient_analysis(
2087                in_buf,
2088                buf_stride,
2089                channels,
2090                &mut tf_estimate,
2091                &mut tf_chan,
2092                false,
2093                &mut weak_transient,
2094                0.0,
2095                0.0,
2096                &mut self.w_transient_tmp,
2097                &mut self.w_transient_tmp2,
2098            )
2099        } else {
2100            false
2101        };
2102
2103        let freq = &mut self.w_freq[..frame_size * channels];
2104        // The first MDCT pass is always LONG blocks: for non-transients it is
2105        // the coding transform; for transients it feeds bandLogE2 (the
2106        // pre-transient spectrum dynalloc smooths against, celt_encoder.c
2107        // secondMdct) and the short re-MDCT below produces the coding one.
2108        let (shift, b) = (mode.max_lm - lm, 1);
2109        let n = frame_size / b;
2110
2111        for c in 0..channels {
2112            let c_buf_offset = c * buf_stride;
2113
2114            if c == 0 && b == 1 && channels == 1 {
2115                let mut max_val = 0.0f32;
2116                let check_len = (frame_size + overlap).min(buf_stride);
2117                for j in 0..check_len {
2118                    max_val = max_val.max(in_buf[c_buf_offset + j].abs());
2119                }
2120            }
2121
2122            for i in 0..b {
2123                mode.mdct.forward(
2124                    &in_buf[c_buf_offset + i * n..],
2125                    &mut freq[c * frame_size + i..],
2126                    mode.window,
2127                    overlap,
2128                    shift,
2129                    b,
2130                );
2131            }
2132        }
2133
2134        let band_e = &mut self.w_band_e[..nb_ebands * channels];
2135        band_e.fill(0.0);
2136        compute_band_energies(mode, freq, band_e, end_band, channels, lm);
2137
2138        let x_pad_end = (frame_size * channels + STRIDE_ACCESS_PAD).min(self.w_x.len());
2139        let x = &mut self.w_x[..x_pad_end];
2140        normalise_bands(
2141            mode,
2142            freq,
2143            x,
2144            band_e,
2145            end_band,
2146            channels,
2147            (1 << lm) as usize,
2148        );
2149
2150        if channels == 1 {
2151            let _ = freq[0];
2152        }
2153
2154        let mut total_bits = explicit_total_bits.unwrap_or_else(|| (rc.buf.len() * 8) as i32);
2155        self.w_error[..nb_ebands * channels].fill(0.0);
2156        let error = &mut self.w_error[..nb_ebands * channels];
2157
2158        let tell = rc.tell();
2159        if tell == 1 {
2160            rc.encode_bit_logp(silence, 15);
2161        }
2162        if silence {
2163            // celt_encoder.c: on a silent frame send only the minimum. Clamp the
2164            // coder to the bytes already filled + 2, then tell the range coder
2165            // the rest is spoken for. Every downstream budget check (allocation,
2166            // prefilter, bands) then has nothing to spend and codes nothing,
2167            // while the whole pipeline still runs — which is what keeps the
2168            // encoder in lockstep with the decoder's mirror of this at
2169            // `rc.nbits_total += total_bits - rc.tell()`.
2170            //
2171            // CBR frames keep their full size (the packet length is fixed), so
2172            // the shrink is VBR-only, exactly as in the C.
2173            if self.vbr_rate > 0 {
2174                let filled = (rc.tell() + 7) >> 3;
2175                let nb_compressed = (total_bits >> 3).min(filled + 2).max(2);
2176                rc.shrink(nb_compressed as u32);
2177                total_bits = nb_compressed * 8;
2178            }
2179            rc.nbits_total += total_bits - rc.tell();
2180        }
2181
2182        if start_band == 0 && !silence && rc.tell() + 16 <= total_bits {
2183            rc.encode_bit_logp(pf_on, 1);
2184            if pf_on {
2185                let qg = (gain1 / 0.09375 - 1.0 + 0.5).floor() as i32;
2186                let qg = qg.clamp(0, 7);
2187                let pi = (pitch_index + 1) as u32;
2188                // octave = EC_ILOG(pi) - 5 (EC_ILOG = 32 - clz, the BIT COUNT of
2189                // pi, not floor(log2)). The old `31 - clz` was one octave low for
2190                // every pi >= 32, overflowing the 4+octave residual field -> the
2191                // decoder reconstructed a garbage period (the prefilter's AM/PM
2192                // sideband bug). pi >= MINPERIOD+1 = 16 keeps this >= 0.
2193                let octave = 32 - pi.leading_zeros() - 5;
2194                rc.enc_uint(octave, 6);
2195                rc.enc_bits(pi - (16 << octave), 4 + octave);
2196                rc.enc_bits(qg as u32, 3);
2197                rc.encode_icdf(prefilter_tapset, &TAPSET_ICDF, 2);
2198            }
2199        }
2200
2201        let mut short_blocks = false;
2202        if lm > 0 && rc.tell() + 3 <= total_bits {
2203            rc.encode_bit_logp(is_transient, 3);
2204            if is_transient {
2205                short_blocks = true;
2206            }
2207        }
2208
2209        // bandLogE2: the long-MDCT logs + 0.5*LM when we re-MDCT short
2210        // (celt_encoder.c secondMdct); else a copy of the final logs (set after
2211        // the final amp2log2 below).
2212        let mut second_mdct_logs = false;
2213        if short_blocks && self.complexity >= 8 {
2214            let band_log_e2 = &mut self.w_band_log_e2[..nb_ebands * channels];
2215            band_log_e2.fill(-14.0);
2216            crate::bands::amp2log2(mode, 0, end_band, band_e, band_log_e2, channels);
2217            for v in band_log_e2.iter_mut() {
2218                *v += 0.5 * lm as f32;
2219            }
2220            second_mdct_logs = true;
2221        }
2222        if short_blocks {
2223            let b = 1 << lm;
2224            let n = frame_size / b;
2225            for c in 0..channels {
2226                let c_offset = c * buf_stride;
2227                for i in 0..b {
2228                    mode.mdct.forward(
2229                        &in_buf[c_offset + i * n..c_offset + buf_stride],
2230                        &mut freq[c * frame_size + i..],
2231                        mode.window,
2232                        overlap,
2233                        mode.max_lm,
2234                        b,
2235                    );
2236                }
2237            }
2238
2239            compute_band_energies(mode, freq, band_e, end_band, channels, lm);
2240            normalise_bands(
2241                mode,
2242                freq,
2243                x,
2244                band_e,
2245                end_band,
2246                channels,
2247                (1 << lm) as usize,
2248            );
2249        }
2250
2251        // Final band logs come AFTER the (possibly short) coding MDCT — C order
2252        // (celt_encoder.c:1742). C computes real logs for ALL bands below end
2253        // (amp2Log2 effEnd==end), incl. below start in hybrid: dynalloc's noise
2254        // floor and the spreading mask read them.
2255        let band_log_e = &mut self.w_band_log_e[..nb_ebands * channels];
2256        band_log_e.fill(-14.0);
2257        crate::bands::amp2log2(mode, 0, end_band, band_e, band_log_e, channels);
2258        if !second_mdct_logs {
2259            self.w_band_log_e2[..nb_ebands * channels].copy_from_slice(band_log_e);
2260        }
2261
2262        let intra_ener = if self.complexity >= 4 {
2263            false
2264        } else {
2265            self.old_band_e[..nb_ebands * channels]
2266                .iter()
2267                .all(|&e| e <= -27.0)
2268        };
2269        quant_coarse_energy_advanced(
2270            mode,
2271            start_band,
2272            end_band,
2273            end_band,
2274            band_log_e,
2275            &mut self.old_band_e,
2276            total_bits as u32,
2277            error,
2278            rc,
2279            channels,
2280            lm,
2281            (total_bits / 8) as usize,
2282            is_transient || intra_ener,
2283            &mut self.delayed_intra,
2284            self.complexity >= 4,
2285            0,
2286            false,
2287        );
2288        // Dynalloc analysis runs BEFORE tf (celt_encoder.c order): its
2289        // importance[] weights the tf Viterbi costs and spread_weight[] feeds
2290        // the spreading decision. The boost FLAGS are still written later, in
2291        // bitstream order.
2292        let effective_bytes = ((total_bits / 8) as usize).max(1);
2293        let mut importance = [13.0f32; MAX_NB_EBANDS];
2294        let mut spread_weight = [32i32; MAX_NB_EBANDS];
2295        self.w_offsets[..nb_ebands].fill(0);
2296        let max_depth = {
2297            let band_log_e2 = &self.w_band_log_e2[..nb_ebands * channels];
2298            dynalloc_analysis(
2299                mode,
2300                band_log_e,
2301                band_log_e2,
2302                start_band,
2303                end_band,
2304                channels,
2305                &mut self.w_offsets[..nb_ebands],
2306                self.lsb_depth,
2307                is_transient,
2308                self.vbr_rate > 0,
2309                self.constrained_vbr,
2310                lm,
2311                effective_bytes,
2312                &self.analysis,
2313                &mut importance,
2314                &mut spread_weight,
2315            )
2316        };
2317
2318        self.w_tf_res[..nb_ebands].fill(0);
2319        let tf_res = &mut self.w_tf_res[..nb_ebands];
2320        let lambda = 80.max(20480 / effective_bytes + 2) as i32;
2321
2322        let tf_select = if self.complexity >= 2 && effective_bytes >= 15 * channels {
2323            tf_analysis(
2324                mode,
2325                end_band,
2326                is_transient,
2327                tf_res,
2328                lambda,
2329                x,
2330                frame_size,
2331                lm as i32,
2332                tf_estimate,
2333                tf_chan,
2334                &importance,
2335            )
2336        } else {
2337            0
2338        };
2339        tf_encode(
2340            start_band,
2341            end_band,
2342            is_transient,
2343            tf_res,
2344            lm as i32,
2345            tf_select,
2346            rc,
2347        );
2348
2349        let mut dual_stereo_val = if channels == 2 {
2350            stereo_analysis(mode, x, lm as i32, frame_size) as i32
2351        } else {
2352            0
2353        };
2354
2355        let mut stereo_saving = 0.0f32;
2356        let equiv_rate = (total_bits * 48000) / frame_size as i32;
2357        if channels == 2 {
2358            self.intensity = hysteresis_decision(
2359                equiv_rate / 1000,
2360                &INTEN_THRESHOLDS,
2361                &INTEN_HYSTERESIS,
2362                self.intensity,
2363            );
2364            // Clamp to [start, end], NOT [0, nb_ebands] (celt_encoder.c:2034).
2365            // clt_compute_allocation codes `intensity - start` in a field of
2366            // width `end + 1 - start`; a value below start (which happens in
2367            // stereo HYBRID, start_band = 17) underflowed that field and
2368            // desynced the range coder on the first stereo-hybrid frame.
2369            self.intensity = self.intensity.clamp(start_band as i32, end_band as i32);
2370        }
2371
2372        if self.complexity == 0 {
2373            self.spread_decision = SPREAD_NONE;
2374            if rc.tell() + 4 <= total_bits {
2375                rc.encode_icdf(self.spread_decision, &SPREAD_ICDF, 5);
2376            }
2377        } else if rc.tell() + 4 <= total_bits {
2378            if is_transient || self.complexity < 3 || effective_bytes < 10 * channels {
2379                self.spread_decision = SPREAD_NORMAL;
2380            } else {
2381                let update_hf = lm == mode.max_lm;
2382                self.spread_decision = spreading_decision(
2383                    mode,
2384                    x,
2385                    &mut self.tonal_average,
2386                    self.spread_decision,
2387                    &mut self.hf_average,
2388                    &mut self.tapset_decision,
2389                    update_hf,
2390                    end_band,
2391                    channels,
2392                    (1 << lm) as usize,
2393                    &spread_weight,
2394                );
2395            }
2396            rc.encode_icdf(self.spread_decision, &SPREAD_ICDF, 5);
2397        } else {
2398            self.spread_decision = SPREAD_NORMAL;
2399        }
2400
2401        self.w_cap[..nb_ebands].fill(0);
2402        let cap = &mut self.w_cap[..nb_ebands];
2403        for (i, cap_i) in cap.iter_mut().enumerate() {
2404            let n = (mode.e_bands[i + 1] - mode.e_bands[i]) << lm;
2405            *cap_i = ((mode.cache.caps[nb_ebands * (2 * lm + channels - 1) + i] as i32 + 64)
2406                * channels as i32
2407                * n as i32)
2408                >> 2;
2409        }
2410
2411        let offsets = &mut self.w_offsets[..nb_ebands];
2412
2413        let mut dynalloc_logp = 6i32;
2414        let total_bits_bitres = total_bits << BITRES;
2415        let mut total_boost = 0i32;
2416        let mut tell_frac = rc.tell_frac();
2417
2418        for i in start_band..end_band {
2419            let width =
2420                channels as i32 * (mode.e_bands[i + 1] - mode.e_bands[i]) as i32 * (1 << lm);
2421            let quanta = (width << BITRES).min((6 << BITRES).max(width));
2422            let mut dynalloc_loop_logp = dynalloc_logp;
2423            let mut boost = 0i32;
2424            let mut j = 0i32;
2425
2426            while tell_frac + (dynalloc_loop_logp << BITRES) < total_bits_bitres - total_boost
2427                && boost < cap[i]
2428            {
2429                let flag = j < offsets[i];
2430                rc.encode_bit_logp(flag, dynalloc_loop_logp as u32);
2431                tell_frac = rc.tell_frac();
2432                if !flag {
2433                    break;
2434                }
2435                boost += quanta;
2436                total_boost += quanta;
2437                dynalloc_loop_logp = 1;
2438                j += 1;
2439            }
2440
2441            if j > 0 {
2442                dynalloc_logp = 2.max(dynalloc_logp - 1);
2443            }
2444            offsets[i] = boost;
2445        }
2446
2447        let alloc_trim = alloc_trim_analysis(
2448            mode,
2449            x,
2450            band_log_e,
2451            end_band,
2452            lm as i32,
2453            channels,
2454            frame_size,
2455            &mut stereo_saving,
2456            tf_estimate,
2457            self.intensity,
2458            0.0,
2459            equiv_rate,
2460        );
2461        // libopus celt_encoder.c: alloc_trim is 5 UNLESS there is room to code the
2462        // analysis value — the decoder falls back to 5 when the trim isn't coded,
2463        // so the encoder MUST use 5 in the allocation math too. Keeping the
2464        // analysis trim here made trim_offset (hence the allocation) differ from
2465        // every conformant decoder on tight-budget frames (e.g. 24 kbps hybrid),
2466        // desyncing the range coder on ~1% of packets.
2467        let alloc_trim = if rc.tell_frac() + (6 << BITRES) <= total_bits_bitres - total_boost {
2468            rc.encode_icdf(alloc_trim, &TRIM_ICDF, 7);
2469            alloc_trim
2470        } else {
2471            5
2472        };
2473
2474        // ---- VBR: pick this frame's size and shrink the coder to it ----
2475        // (libopus celt_encoder.c `if (vbr_rate>0)`; runs between the trim and the
2476        // allocation so the allocator sees the final budget.)
2477        let total_bits = if self.vbr_rate > 0 {
2478            let hybrid = start_band != 0;
2479            let lm_diff = mode.max_lm as i32 - lm as i32;
2480            let vbr_rate = self.vbr_rate;
2481            let mut base_target = if hybrid {
2482                0.max(vbr_rate - ((9 * channels as i32 + 4) << BITRES))
2483            } else {
2484                vbr_rate - ((40 * channels as i32 + 20) << BITRES)
2485            };
2486            if self.constrained_vbr {
2487                base_target += self.vbr_offset >> lm_diff;
2488            }
2489            let mut target = if hybrid {
2490                // (libopus also nudges by the SILK quantization offset; we don't
2491                // track silk_info yet — quality refinement, not conformance.)
2492                let mut t = base_target;
2493                t += ((tf_estimate - 0.25) * (50 << BITRES) as f32) as i32;
2494                if tf_estimate > 0.7 {
2495                    t = t.max(50 << BITRES);
2496                }
2497                t
2498            } else {
2499                compute_vbr_target(
2500                    mode,
2501                    base_target,
2502                    lm as i32,
2503                    self.last_coded_bands,
2504                    channels as i32,
2505                    self.intensity,
2506                    self.constrained_vbr,
2507                    stereo_saving,
2508                    total_boost,
2509                    tf_estimate,
2510                    max_depth,
2511                    &self.analysis,
2512                    self.tonal_vbr,
2513                )
2514            };
2515            let tell = rc.tell_frac();
2516            target += tell;
2517            // Never shrink below what's already coded (+2 bytes of margin); in
2518            // hybrid, keep >=37 bits after the SILK part so the redundancy
2519            // signalling space assumed by every decoder still exists.
2520            let mut min_allowed =
2521                ((tell + total_boost + (1 << (BITRES + 3)) - 1) >> (BITRES + 3)) + 2;
2522            if hybrid {
2523                min_allowed = min_allowed.max(
2524                    (tell0_frac + (37 << BITRES) + total_boost + (1 << (BITRES + 3)) - 1)
2525                        >> (BITRES + 3),
2526                );
2527            }
2528            let cap_bytes = (total_bits / 8).min(1275 >> (3 - lm as i32));
2529            let mut nb_available = (target + (1 << (BITRES + 2))) >> (BITRES + 3);
2530            nb_available = nb_available.max(min_allowed).min(cap_bytes);
2531
2532            // Reservoir/drift tracking (constrained VBR).
2533            let delta = target - vbr_rate;
2534            let target_q = nb_available << (BITRES + 3);
2535            if self.vbr_count < 970 {
2536                self.vbr_count += 1;
2537            }
2538            let alpha = if self.vbr_count < 970 {
2539                1.0f32 / (self.vbr_count as f32 + 20.0)
2540            } else {
2541                0.001f32
2542            };
2543            if self.constrained_vbr {
2544                self.vbr_reservoir += target_q - vbr_rate;
2545                self.vbr_drift += (alpha
2546                    * ((delta * (1 << lm_diff)) - self.vbr_offset - self.vbr_drift) as f32)
2547                    as i32;
2548                self.vbr_offset = -self.vbr_drift;
2549                if self.vbr_reservoir < 0 {
2550                    let adjust = (-self.vbr_reservoir) / (8 << BITRES);
2551                    nb_available += adjust;
2552                    self.vbr_reservoir = 0;
2553                }
2554            }
2555            let nb_compressed = cap_bytes.min(nb_available).max(2);
2556            rc.shrink(nb_compressed as u32);
2557            nb_compressed * 8
2558        } else {
2559            total_bits
2560        };
2561
2562        let mut intensity = self.intensity;
2563        self.w_pulses[..nb_ebands].fill(0);
2564        let pulses = &mut self.w_pulses[..nb_ebands];
2565
2566        let stereo = channels > 1;
2567        let ebands_stereo = if stereo {
2568            nb_ebands * channels
2569        } else {
2570            nb_ebands
2571        };
2572        self.w_fine_priority[..ebands_stereo].fill(0);
2573        let fine_priority = &mut self.w_fine_priority[..ebands_stereo];
2574        self.w_ebits[..ebands_stereo].fill(0);
2575        let ebits = &mut self.w_ebits[..ebands_stereo];
2576        let mut balance = 0;
2577
2578        // The anti-collapse bit reservation must be subtracted from the allocation
2579        // budget BEFORE compute_allocation (libopus celt_encoder.c: `total_bits -=
2580        // anti_collapse_rsv` precedes it) — the decoder reserves it there too.
2581        // Computing it only afterwards (as this code used to) let the encoder
2582        // allocate 1<<BITRES more than the decoder assumes on transient LM>=2
2583        // frames -> band budgets differ from band `start` -> range desync on
2584        // exactly those frames (caught by opus_demo -d's per-packet range check).
2585        // Same formula as the decoder for exact symmetry.
2586        let anti_collapse_rsv = if is_transient && lm >= 2 {
2587            let remaining = (total_bits << BITRES) - rc.tell_frac() - 1;
2588            if remaining >= ((lm as i32 + 2) << BITRES) {
2589                1i32 << BITRES
2590            } else {
2591                0
2592            }
2593        } else {
2594            0
2595        };
2596
2597        // signalBandwidth: end-1 by CHOICE (C uses the analysis bandwidth,
2598        // celt_encoder.c:2174, to let the allocator skip top bands — but that
2599        // narrowing loses ~0.7 ODG on music even with leak_boost live, and
2600        // libopus's own narrowed scores lose to our full-band ones). PEAQ-gated
2601        // out twice; do not re-enable without a corpus win.
2602        let signal_bandwidth = end_band as i32 - 1;
2603        let _ = equiv_rate;
2604
2605        self.last_coded_bands = clt_compute_allocation(
2606            mode,
2607            start_band,
2608            end_band,
2609            offsets,
2610            cap,
2611            alloc_trim,
2612            &mut intensity,
2613            &mut dual_stereo_val,
2614            (total_bits << BITRES) - rc.tell_frac() - 1 - anti_collapse_rsv,
2615            &mut balance,
2616            pulses,
2617            ebits,
2618            fine_priority,
2619            channels as i32,
2620            lm as i32,
2621            rc,
2622            true,
2623            0,
2624            signal_bandwidth,
2625        );
2626
2627        quant_fine_energy(
2628            mode,
2629            start_band,
2630            end_band,
2631            &mut self.old_band_e,
2632            error,
2633            ebits,
2634            rc,
2635            channels,
2636        );
2637
2638        self.w_collapse_masks[..nb_ebands * channels].fill(0);
2639        let collapse_masks = &mut self.w_collapse_masks[..nb_ebands * channels];
2640        let (x_split, y_split) = x.split_at_mut(frame_size);
2641        let y_opt = if channels == 2 { Some(y_split) } else { None };
2642
2643        let mut dual_stereo = dual_stereo_val != 0;
2644
2645        let theta_rdo = channels == 2 && !dual_stereo && self.complexity >= 8;
2646        let resynth = theta_rdo;
2647
2648        quant_all_bands(
2649            true,
2650            mode,
2651            start_band,
2652            end_band,
2653            x_split,
2654            y_opt,
2655            collapse_masks,
2656            band_e,
2657            pulses,
2658            short_blocks,
2659            self.spread_decision,
2660            &mut dual_stereo,
2661            intensity as usize,
2662            tf_res,
2663            (total_bits << BITRES) - anti_collapse_rsv,
2664            &mut balance,
2665            rc,
2666            lm as i32,
2667            self.last_coded_bands,
2668            resynth,
2669            false,
2670            &mut 0u32,
2671        );
2672
2673        if anti_collapse_rsv > 0 {
2674            let anti_collapse_on = if self.consec_transient < 2 {
2675                1u32
2676            } else {
2677                0u32
2678            };
2679            rc.enc_bits(anti_collapse_on, 1);
2680        }
2681
2682        quant_energy_finalise(
2683            mode,
2684            start_band,
2685            end_band,
2686            &mut self.old_band_e,
2687            error,
2688            ebits,
2689            fine_priority,
2690            total_bits - rc.tell(),
2691            rc,
2692            channels,
2693        );
2694
2695        if resynth {
2696            let _prof = crate::prof::scope(crate::prof::Stage::CeltSynth);
2697            let band_amp_synth = &mut self.w_band_amp_synth[..nb_ebands * channels];
2698            log2amp(mode, nb_ebands, band_amp_synth, &self.old_band_e, channels);
2699            self.w_freq_synth[..frame_size * channels].fill(0.0);
2700            let freq_synth = &mut self.w_freq_synth[..frame_size * channels];
2701            denormalise_bands(
2702                mode,
2703                x,
2704                freq_synth,
2705                band_amp_synth,
2706                start_band,
2707                end_band,
2708                channels,
2709                (1 << lm) as usize,
2710            );
2711            let (syn_shift, syn_b) = if is_transient {
2712                (mode.max_lm, 1 << lm)
2713            } else {
2714                (mode.max_lm - lm, 1)
2715            };
2716            let syn_n = frame_size / syn_b;
2717            let decode_buf_size = 2048;
2718
2719            for c in 0..channels {
2720                let co = c * syn_mem_size;
2721                self.enc_decode_mem
2722                    .copy_within(co + frame_size..co + decode_buf_size + overlap, co);
2723            }
2724
2725            for c in 0..channels {
2726                let co = c * syn_mem_size;
2727                let out_syn_idx = decode_buf_size - frame_size;
2728                for bi in 0..syn_b {
2729                    let syn_stride = if is_transient {
2730                        mode.short_mdct_size
2731                    } else {
2732                        syn_n
2733                    };
2734                    mode.mdct.backward(
2735                        &freq_synth[c * frame_size + bi..],
2736                        &mut self.enc_decode_mem[co + out_syn_idx + bi * syn_stride..],
2737                        mode.window,
2738                        overlap,
2739                        syn_shift,
2740                        syn_b,
2741                    );
2742                }
2743            }
2744        }
2745
2746        self.last_band_log_e.copy_from_slice(&self.old_band_e);
2747
2748        if !is_transient {
2749            self.old_band_e3.copy_from_slice(&self.old_band_e2);
2750            self.old_band_e2.copy_from_slice(&self.old_band_e);
2751        } else {
2752            for i in 0..channels * nb_ebands {
2753                self.old_band_e2[i] = self.old_band_e2[i].min(self.old_band_e[i]);
2754            }
2755        }
2756
2757        // "In case start or end were to change" (celt_encoder.c:2301): zero the
2758        // coarse-energy state outside [start, end) and floor the log history —
2759        // the decoder does the same every frame, and a later frame with a wider
2760        // end must predict those bands from the SAME (zeroed) base.
2761        for c in 0..channels {
2762            for i in 0..start_band {
2763                self.old_band_e[c * nb_ebands + i] = 0.0;
2764                self.old_band_e2[c * nb_ebands + i] = -28.0;
2765                self.old_band_e3[c * nb_ebands + i] = -28.0;
2766            }
2767            for i in end_band..nb_ebands {
2768                self.old_band_e[c * nb_ebands + i] = 0.0;
2769                self.old_band_e2[c * nb_ebands + i] = -28.0;
2770                self.old_band_e3[c * nb_ebands + i] = -28.0;
2771            }
2772        }
2773
2774        rc.pad_to_bits(total_bits);
2775
2776        if pf_on {
2777            self.prefilter_period = pitch_index;
2778            self.prefilter_gain = gain1;
2779        } else {
2780            self.prefilter_period = COMBFILTER_MINPERIOD;
2781            self.prefilter_gain = 0.0;
2782        }
2783        self.prefilter_tapset = prefilter_tapset;
2784
2785        if is_transient {
2786            self.consec_transient += 1;
2787        } else {
2788            self.consec_transient = 0;
2789        }
2790    }
2791}
2792
2793pub struct CeltDecoder {
2794    mode: &'static CeltMode,
2795    channels: usize,
2796    // Bitstream (coded) channels C; normally == channels (CC). A mono packet in a
2797    // stereo decoder sets this to 1 (C=1, CC=2) so the CELT inter-frame state stays
2798    // one continuous chain across mono<->stereo switches, matching libopus.
2799    stream_channels: usize,
2800    decode_mem: Vec<f32>,
2801    old_band_e: Vec<f32>,
2802    preemph_mem: Vec<f32>,
2803    prefilter_mem: Vec<f32>,
2804    prefilter_period: usize,
2805    prefilter_period_old: usize,
2806    prefilter_gain: f32,
2807    prefilter_gain_old: f32,
2808    prefilter_tapset: i32,
2809    prefilter_tapset_old: i32,
2810    old_band_e2: Vec<f32>,
2811    old_band_e3: Vec<f32>,
2812    rng: u32,
2813    /// Consecutive-loss counter for packet-loss concealment (celt_decode_lost).
2814    loss_count: u32,
2815    /// Pitch lag from the first lost frame, reused across a loss burst.
2816    last_pitch_index: i32,
2817    /// LPC coefficients (per channel, PLC_LPC_ORDER) computed at the first loss
2818    /// and reused for the rest of the burst (pitch-based PLC).
2819    plc_lpc: Vec<f32>,
2820
2821    w_tf_res: Vec<i32>,
2822    w_cap: Vec<i32>,
2823    w_offsets: Vec<i32>,
2824    w_pulses: Vec<i32>,
2825    w_ebits: Vec<i32>,
2826    w_fine_priority: Vec<i32>,
2827    w_x: Vec<f32>,
2828    w_collapse_masks: Vec<u32>,
2829    w_freq: Vec<f32>,
2830    w_band_amp: Vec<f32>,
2831    w_pcm_frame: Vec<f32>,
2832    w_post: Vec<f32>,
2833}
2834
2835impl CeltDecoder {
2836    pub fn new(mode: &'static CeltMode, channels: usize) -> Self {
2837        let overlap = mode.overlap;
2838        let nb_ebands = mode.nb_ebands;
2839        let nb_x_ch = nb_ebands * channels;
2840        let dec_frame_x_ch = DECODE_BUFFER_SIZE * channels;
2841        Self {
2842            mode,
2843            channels,
2844            stream_channels: channels,
2845            decode_mem: vec![0.0; channels * (DECODE_BUFFER_SIZE + overlap)],
2846            // libopus: oldBandE inits to 0 (OPUS_CLEAR); only oldLogE/oldLogE2 get
2847            // the -28 "very quiet" floor. Do NOT init old_band_e to -28 (it is the
2848            // coarse-energy prediction state; -28 makes the first frames too quiet).
2849            old_band_e: vec![0.0; nb_x_ch],
2850            preemph_mem: vec![0.0; channels],
2851            prefilter_mem: vec![0.0; channels * COMBFILTER_MAXPERIOD],
2852            prefilter_period: COMBFILTER_MINPERIOD,
2853            prefilter_period_old: COMBFILTER_MINPERIOD,
2854            prefilter_gain: 0.0,
2855            prefilter_gain_old: 0.0,
2856            prefilter_tapset: 0,
2857            prefilter_tapset_old: 0,
2858            // oldLogE / oldLogE2 in libopus: init -QCONST16(28,DB_SHIFT).
2859            old_band_e2: vec![-28.0; nb_x_ch],
2860            old_band_e3: vec![-28.0; nb_x_ch],
2861            rng: 0,
2862            loss_count: 0,
2863            last_pitch_index: 0,
2864            plc_lpc: vec![0.0; channels * PLC_LPC_ORDER],
2865
2866            w_tf_res: vec![0; nb_ebands],
2867            w_cap: vec![0; nb_ebands],
2868            w_offsets: vec![0; nb_ebands],
2869            w_pulses: vec![0; nb_ebands],
2870            w_ebits: vec![0; nb_x_ch],
2871            w_fine_priority: vec![0; nb_x_ch],
2872
2873            w_x: vec![0.0; dec_frame_x_ch + STRIDE_ACCESS_PAD],
2874            w_collapse_masks: vec![0; nb_x_ch],
2875            w_freq: vec![0.0; dec_frame_x_ch + 4], // +4: NEON backward pre-rotation reads up to 3 elements past n2
2876            w_band_amp: vec![0.0; nb_x_ch],
2877            w_pcm_frame: vec![0.0; DECODE_BUFFER_SIZE],
2878            w_post: vec![0.0; DECODE_BUFFER_SIZE + COMBFILTER_MAXPERIOD],
2879        }
2880    }
2881
2882    /// Seed this decoder's inter-frame state from another decoder (typically the
2883    /// auxiliary mono decoder), replicating its channel 0 into every channel of
2884    /// self. Used at a mono->stereo switch so the primary stereo CeltDecoder's
2885    /// overlap/energy/prefilter state is continuous with the preceding mono
2886    /// packets (which libopus keeps in one continuous decoder) — without this the
2887    /// first stereo frame's MDCT overlap-add starts from silence.
2888    pub fn seed_from(&mut self, src: &CeltDecoder) {
2889        let overlap = self.mode.overlap;
2890        let nb = self.mode.nb_ebands;
2891        let per_dm = DECODE_BUFFER_SIZE + overlap;
2892        let src_ch = src.channels.max(1);
2893        for c in 0..self.channels {
2894            let sc = c.min(src_ch - 1);
2895            self.decode_mem[c * per_dm..(c + 1) * per_dm]
2896                .copy_from_slice(&src.decode_mem[sc * per_dm..(sc + 1) * per_dm]);
2897            self.old_band_e[c * nb..(c + 1) * nb]
2898                .copy_from_slice(&src.old_band_e[sc * nb..(sc + 1) * nb]);
2899            self.old_band_e2[c * nb..(c + 1) * nb]
2900                .copy_from_slice(&src.old_band_e2[sc * nb..(sc + 1) * nb]);
2901            self.old_band_e3[c * nb..(c + 1) * nb]
2902                .copy_from_slice(&src.old_band_e3[sc * nb..(sc + 1) * nb]);
2903            self.preemph_mem[c] = src.preemph_mem[sc];
2904            self.prefilter_mem[c * COMBFILTER_MAXPERIOD..(c + 1) * COMBFILTER_MAXPERIOD]
2905                .copy_from_slice(
2906                    &src.prefilter_mem[sc * COMBFILTER_MAXPERIOD..(sc + 1) * COMBFILTER_MAXPERIOD],
2907                );
2908        }
2909        self.prefilter_period = src.prefilter_period;
2910        self.prefilter_period_old = src.prefilter_period_old;
2911        self.prefilter_gain = src.prefilter_gain;
2912        self.prefilter_gain_old = src.prefilter_gain_old;
2913        self.prefilter_tapset = src.prefilter_tapset;
2914        self.prefilter_tapset_old = src.prefilter_tapset_old;
2915        self.rng = src.rng;
2916    }
2917
2918    /// Channels coded in the next packet's bitstream (1 for a mono packet decoded
2919    /// by a stereo decoder — keeps 2-channel state continuous across switches).
2920    pub fn set_stream_channels(&mut self, sc: usize) {
2921        self.stream_channels = sc.clamp(1, self.channels);
2922    }
2923
2924    /// libopus OPUS_RESET_STATE for the decoder: clear everything from rng onward,
2925    /// then oldLogE/oldLogE2 = -28 (oldBandE stays 0).
2926    pub fn reset(&mut self) {
2927        self.decode_mem.fill(0.0);
2928        self.old_band_e.fill(0.0);
2929        self.old_band_e2.fill(-28.0);
2930        self.old_band_e3.fill(-28.0);
2931        self.preemph_mem.fill(0.0);
2932        self.prefilter_mem.fill(0.0);
2933        self.prefilter_period = COMBFILTER_MINPERIOD;
2934        self.prefilter_period_old = COMBFILTER_MINPERIOD;
2935        self.prefilter_gain = 0.0;
2936        self.prefilter_gain_old = 0.0;
2937        self.prefilter_tapset = 0;
2938        self.prefilter_tapset_old = 0;
2939        self.rng = 0;
2940    }
2941
2942    pub fn decode(&mut self, compressed: &[u8], frame_size: usize, pcm: &mut [f32]) -> usize {
2943        self.decode_impl(compressed, frame_size, pcm, 0, self.mode.nb_ebands)
2944    }
2945
2946    pub fn decode_with_start_band(
2947        &mut self,
2948        compressed: &[u8],
2949        frame_size: usize,
2950        pcm: &mut [f32],
2951        start_band: usize,
2952    ) -> usize {
2953        self.decode_impl(compressed, frame_size, pcm, start_band, self.mode.nb_ebands)
2954    }
2955
2956    pub fn decode_from_range_coder(
2957        &mut self,
2958        rc: &mut RangeCoder,
2959        total_bits: i32,
2960        frame_size: usize,
2961        pcm: &mut [f32],
2962        start_band: usize,
2963    ) -> usize {
2964        self.decode_impl_from_rc(
2965            rc,
2966            total_bits,
2967            frame_size,
2968            pcm,
2969            start_band,
2970            self.mode.nb_ebands,
2971        )
2972    }
2973
2974    pub fn decode_from_range_coder_with_band_range(
2975        &mut self,
2976        rc: &mut RangeCoder,
2977        total_bits: i32,
2978        frame_size: usize,
2979        pcm: &mut [f32],
2980        start_band: usize,
2981        end_band: usize,
2982    ) -> usize {
2983        self.decode_impl_from_rc(rc, total_bits, frame_size, pcm, start_band, end_band)
2984    }
2985
2986    fn decode_impl(
2987        &mut self,
2988        compressed: &[u8],
2989        frame_size: usize,
2990        pcm: &mut [f32],
2991        start_band: usize,
2992        end_band: usize,
2993    ) -> usize {
2994        let total_bits = (compressed.len() * 8) as i32;
2995        let mut rc = RangeCoder::new_decoder(compressed);
2996        self.decode_impl_from_rc(&mut rc, total_bits, frame_size, pcm, start_band, end_band)
2997    }
2998
2999    fn decode_impl_from_rc(
3000        &mut self,
3001        rc: &mut RangeCoder,
3002        total_bits: i32,
3003        frame_size: usize,
3004        pcm: &mut [f32],
3005        start_band: usize,
3006        end_band: usize,
3007    ) -> usize {
3008        let mode = self.mode;
3009        // CC = state/output channels; C (=`channels`) = channels coded in the
3010        // bitstream. Mono packet in a stereo decoder: C=1, CC=2 — energy/allocation/
3011        // bands/denormalise all use C; synthesis writes CC output channels reading
3012        // the single decoded channel.
3013        let cc = self.channels;
3014        let channels = self.stream_channels.clamp(1, cc);
3015        let nb_ebands = mode.nb_ebands;
3016        let end_band = end_band.min(nb_ebands).max(start_band);
3017        let overlap = mode.overlap;
3018
3019        let mut lm = 0;
3020        while (mode.short_mdct_size << lm) != frame_size {
3021            lm += 1;
3022            if lm > mode.max_lm {
3023                break;
3024            }
3025        }
3026        if (mode.short_mdct_size << lm) != frame_size {
3027            lm = 0;
3028        }
3029
3030        // libopus celt_decoder.c:953: `if (C==1) oldBandE[i]=MAX(oldBandE[i],
3031        // oldBandE[nbEBands+i])` before the coarse-energy decode — a mono packet in
3032        // a stereo decoder predicts its single channel from the MAX of both
3033        // channels' previous energy. (Only meaningful on the first mono frame after
3034        // stereo; after every mono frame ch0 is replicated to ch1 at frame end.)
3035        if channels == 1 && cc == 2 {
3036            for i in 0..nb_ebands {
3037                self.old_band_e[i] = self.old_band_e[i].max(self.old_band_e[nb_ebands + i]);
3038            }
3039        }
3040
3041        let tell = rc.tell();
3042        let mut silence = false;
3043        if tell >= total_bits {
3044            silence = true;
3045        } else if tell == 1 {
3046            silence = rc.decode_bit_logp(15);
3047        }
3048        if silence {
3049            // libopus: "Pretend we've read all the remaining bits" — every
3050            // downstream budget check then skips its entropy reads naturally, the
3051            // whole pipeline still runs (decode_mem shift, overlap fade-out via a
3052            // zeroed spectrum, postfilter/deemph, frame-end energy bookkeeping).
3053            // The old early-return left the decoder state one frame stale and the
3054            // energy prediction hot -> the next loud frame decoded ~2^15 too loud
3055            // and railed the output.
3056            rc.nbits_total += total_bits - rc.tell();
3057        }
3058
3059        let mut pf_on = false;
3060        let mut pitch_index = COMBFILTER_MINPERIOD;
3061        let mut gain1 = 0.0f32;
3062        let mut prefilter_tapset = 0;
3063
3064        if start_band == 0 && !silence && rc.tell() + 16 <= total_bits {
3065            pf_on = rc.decode_bit_logp(1);
3066            if pf_on {
3067                let octave = rc.dec_uint(6);
3068                pitch_index = ((16 << octave) + rc.dec_bits(4 + octave)) as usize - 1;
3069                let qg = rc.dec_bits(3);
3070                if rc.tell() + 2 <= total_bits {
3071                    prefilter_tapset = rc.decode_icdf(&TAPSET_ICDF, 2) as usize;
3072                }
3073                gain1 = 0.09375 * (qg as f32 + 1.0);
3074            }
3075        }
3076        if start_band != 0 {
3077            self.prefilter_gain = 0.0;
3078        }
3079
3080        let mut is_transient = false;
3081        if lm > 0 && rc.tell() + 3 <= total_bits {
3082            is_transient = rc.decode_bit_logp(3);
3083        }
3084        let short_blocks = is_transient;
3085
3086        let intra_ener = if rc.tell() + 3 <= total_bits {
3087            rc.decode_bit_logp(3)
3088        } else {
3089            false
3090        };
3091
3092        unquant_coarse_energy(
3093            mode,
3094            start_band,
3095            end_band,
3096            &mut self.old_band_e,
3097            intra_ener,
3098            rc,
3099            channels,
3100            lm,
3101        );
3102        self.w_tf_res[..nb_ebands].fill(0);
3103        let tf_res = &mut self.w_tf_res[..nb_ebands];
3104        tf_decode(start_band, end_band, is_transient, tf_res, lm as i32, rc);
3105
3106        let spread_decision = if rc.tell() + 4 <= total_bits {
3107            rc.decode_icdf(&SPREAD_ICDF, 5)
3108        } else {
3109            SPREAD_NORMAL
3110        };
3111
3112        self.w_cap[..nb_ebands].fill(0);
3113        let cap = &mut self.w_cap[..nb_ebands];
3114        for (i, cap_i) in cap.iter_mut().enumerate() {
3115            let n = (mode.e_bands[i + 1] - mode.e_bands[i]) << lm;
3116            *cap_i = ((mode.cache.caps[nb_ebands * (2 * lm + channels - 1) + i] as i32 + 64)
3117                * channels as i32
3118                * n as i32)
3119                >> 2;
3120        }
3121
3122        self.w_offsets[..nb_ebands].fill(0);
3123        let offsets = &mut self.w_offsets[..nb_ebands];
3124        let mut dynalloc_logp = 6i32;
3125        let mut total_bits_bitres = total_bits << BITRES;
3126        let mut tell_frac = rc.tell_frac();
3127        for i in start_band..end_band {
3128            let width =
3129                channels as i32 * (mode.e_bands[i + 1] - mode.e_bands[i]) as i32 * (1 << lm);
3130            let quanta = (width << BITRES).min((6i32 << BITRES).max(width));
3131            let mut dynalloc_loop_logp = dynalloc_logp;
3132            let mut boost = 0i32;
3133            while tell_frac + (dynalloc_loop_logp << BITRES) < total_bits_bitres && boost < cap[i] {
3134                let flag = rc.decode_bit_logp(dynalloc_loop_logp as u32);
3135                tell_frac = rc.tell_frac();
3136                if !flag {
3137                    break;
3138                }
3139                boost += quanta;
3140                total_bits_bitres -= quanta;
3141                dynalloc_loop_logp = 1;
3142            }
3143            offsets[i] = boost;
3144            if boost > 0 {
3145                dynalloc_logp = dynalloc_logp.max(2) - 1;
3146                dynalloc_logp = dynalloc_logp.max(2);
3147            }
3148        }
3149
3150        let alloc_trim = if rc.tell_frac() + (6 << BITRES) <= total_bits_bitres {
3151            rc.decode_icdf(&TRIM_ICDF, 7)
3152        } else {
3153            5
3154        };
3155        let anti_collapse_rsv = if is_transient && lm >= 2 {
3156            let remaining = (total_bits << BITRES) - rc.tell_frac() - 1;
3157            if remaining >= ((lm as i32 + 2) << BITRES) {
3158                1i32 << BITRES
3159            } else {
3160                0
3161            }
3162        } else {
3163            0
3164        };
3165
3166        let mut intensity = 0;
3167        let mut dual_stereo_val = if channels == 2 { 1 } else { 0 };
3168        let mut balance = 0;
3169        self.w_pulses[..nb_ebands].fill(0);
3170        let pulses = &mut self.w_pulses[..nb_ebands];
3171
3172        let ebands_stereo = if channels > 1 {
3173            nb_ebands * channels
3174        } else {
3175            nb_ebands
3176        };
3177        self.w_fine_priority[..ebands_stereo].fill(0);
3178        let fine_priority = &mut self.w_fine_priority[..ebands_stereo];
3179        self.w_ebits[..ebands_stereo].fill(0);
3180        let ebits = &mut self.w_ebits[..ebands_stereo];
3181
3182        let alloc_bits = (total_bits << BITRES) - rc.tell_frac() - 1 - anti_collapse_rsv;
3183        let coded_bands = clt_compute_allocation(
3184            mode,
3185            start_band,
3186            end_band,
3187            offsets,
3188            cap,
3189            alloc_trim,
3190            &mut intensity,
3191            &mut dual_stereo_val,
3192            alloc_bits,
3193            &mut balance,
3194            pulses,
3195            ebits,
3196            fine_priority,
3197            channels as i32,
3198            lm as i32,
3199            rc,
3200            false,
3201            0,
3202            end_band as i32 - 1,
3203        );
3204
3205        unquant_fine_energy(
3206            mode,
3207            start_band,
3208            end_band,
3209            &mut self.old_band_e,
3210            ebits,
3211            rc,
3212            channels,
3213        );
3214
3215        if frame_size > DECODE_BUFFER_SIZE + overlap {
3216            return 0;
3217        }
3218
3219        self.w_x[..frame_size * channels].fill(0.0);
3220
3221        let x_pad_end = (frame_size * channels + STRIDE_ACCESS_PAD).min(self.w_x.len());
3222        let x = &mut self.w_x[..x_pad_end];
3223        self.w_collapse_masks[..nb_ebands * channels].fill(0);
3224        let collapse_masks = &mut self.w_collapse_masks[..nb_ebands * channels];
3225
3226        let (x_split, y_split) = x.split_at_mut(frame_size);
3227        let y_opt = if channels == 2 { Some(y_split) } else { None };
3228
3229        let mut dual_stereo = dual_stereo_val != 0;
3230        self.w_band_amp[..nb_ebands * channels].fill(0.0);
3231        let band_amp = &mut self.w_band_amp[..nb_ebands * channels];
3232        log2amp(mode, nb_ebands, band_amp, &self.old_band_e, channels);
3233        quant_all_bands(
3234            false,
3235            mode,
3236            start_band,
3237            end_band,
3238            x_split,
3239            y_opt,
3240            collapse_masks,
3241            band_amp,
3242            pulses,
3243            short_blocks,
3244            spread_decision,
3245            &mut dual_stereo,
3246            intensity as usize,
3247            tf_res,
3248            (total_bits << BITRES) - anti_collapse_rsv,
3249            &mut balance,
3250            rc,
3251            lm as i32,
3252            coded_bands,
3253            true,
3254            false,
3255            &mut self.rng,
3256        );
3257        // Trace X values for comparison with C decoder
3258        let mut anti_collapse_on = false;
3259        if anti_collapse_rsv > 0 {
3260            anti_collapse_on = rc.dec_bits(1) != 0;
3261        }
3262
3263        unquant_energy_finalise(
3264            mode,
3265            start_band,
3266            end_band,
3267            &mut self.old_band_e,
3268            ebits,
3269            fine_priority,
3270            total_bits - rc.tell(),
3271            rc,
3272            channels,
3273        );
3274        if anti_collapse_on {
3275            // libopus passes `end`, not nbEBands: for narrower bandwidths (e.g.
3276            // SWB end=19) anti-collapsing the uncoded bands would burn PRNG draws
3277            // and desync the noise-fill seed for every subsequent frame.
3278            self.rng = crate::bands::anti_collapse(
3279                mode,
3280                x,
3281                collapse_masks,
3282                lm as i32,
3283                channels,
3284                frame_size,
3285                start_band,
3286                end_band,
3287                &self.old_band_e,
3288                &self.old_band_e2,
3289                &self.old_band_e3,
3290                pulses,
3291                self.rng,
3292            );
3293        }
3294
3295        // libopus celt_decoder.c:1107: silence floors the coded channels' energy to
3296        // -28 (so the next frame's inter prediction starts from "very quiet") and
3297        // renders a zero spectrum — the frame's output is just the MDCT overlap
3298        // fade-out of the previous frame.
3299        if silence {
3300            for i in 0..channels * nb_ebands {
3301                self.old_band_e[i] = -28.0;
3302            }
3303        }
3304
3305        // Recompute band_amp after unquant_energy_finalise, which adjusts old_band_e.
3306        // (Mirrors the encoder's resynth path: log2amp is called after quant_energy_finalise.)
3307        log2amp(mode, nb_ebands, band_amp, &self.old_band_e, channels);
3308        self.w_freq[..frame_size * channels].fill(0.0);
3309        let freq = &mut self.w_freq[..frame_size * channels];
3310        if !silence {
3311            denormalise_bands(
3312                mode,
3313                x,
3314                freq,
3315                band_amp,
3316                start_band,
3317                end_band,
3318                channels,
3319                (1 << lm) as usize,
3320            );
3321        }
3322        // Always trace freq and band_amp for comparison
3323
3324        let (shift, b) = if short_blocks {
3325            (mode.max_lm, 1 << lm)
3326        } else {
3327            (mode.max_lm - lm, 1)
3328        };
3329        let n = frame_size / b;
3330
3331        for c in 0..cc {
3332            // A mono packet (C=1) in a stereo decoder (CC=2) renders its single
3333            // decoded channel into both outputs: re-run the iMDCT reading channel
3334            // 0's freq (fc clamps to C-1). Re-synthesis (not a decode_mem copy) is
3335            // required so the per-channel postfilter/deemph below run exactly once
3336            // each; denormalise_bands leaves freq unmodified so this is exact.
3337            let fc = c.min(channels - 1);
3338            let channel_mem_offset = c * (DECODE_BUFFER_SIZE + overlap);
3339
3340            let mem_size = DECODE_BUFFER_SIZE + overlap;
3341            self.decode_mem.copy_within(
3342                channel_mem_offset + frame_size..channel_mem_offset + mem_size,
3343                channel_mem_offset,
3344            );
3345
3346            let out_syn_idx = DECODE_BUFFER_SIZE - frame_size;
3347
3348            for i in 0..b {
3349                let block_freq_idx = fc * frame_size + i;
3350                // Stride between short-block MDCT outputs is short_mdct_size (not n).
3351                // In libopus: out_syn[c] + NB*b, where NB = mode->shortMdctSize.
3352                // For non-transient b=1, i*n == 0 either way.
3353                let block_stride = if short_blocks {
3354                    mode.short_mdct_size
3355                } else {
3356                    n
3357                };
3358                let block_out_idx = channel_mem_offset + out_syn_idx + i * block_stride;
3359                let available_len = self.decode_mem.len() - block_out_idx;
3360                if available_len < n + overlap {
3361                    panic!(
3362                        "MDCT backward buffer too small: need {}, have {} (out_syn_idx={}, n={}, overlap={})",
3363                        n + overlap,
3364                        available_len,
3365                        out_syn_idx,
3366                        n,
3367                        overlap
3368                    );
3369                }
3370                self.mode.mdct.backward(
3371                    &freq[block_freq_idx..],
3372                    &mut self.decode_mem[block_out_idx..],
3373                    mode.window,
3374                    overlap,
3375                    shift,
3376                    b,
3377                );
3378            }
3379
3380            const SIG_SAT: f32 = 536870911.0;
3381            for i in 0..frame_size {
3382                let v = &mut self.decode_mem[channel_mem_offset + out_syn_idx + i];
3383                *v = v.clamp(-SIG_SAT, SIG_SAT);
3384            }
3385
3386            self.w_pcm_frame[..frame_size].fill(0.0);
3387            let pcm_frame = &mut self.w_pcm_frame[..frame_size];
3388
3389            pcm_frame.copy_from_slice(
3390                &self.decode_mem[channel_mem_offset + out_syn_idx
3391                    ..channel_mem_offset + out_syn_idx + frame_size],
3392            );
3393            if pf_on || self.prefilter_gain > 0.0 || self.prefilter_gain_old > 0.0 {
3394                // Set up w_post = [prefilter_mem | pcm_frame] for history access.
3395                // We apply combfilter in-place on w_post[COMBFILTER_MAXPERIOD..] so that
3396                // later samples can reference already-filtered earlier samples, matching C's
3397                // in-place comb_filter behavior.
3398                self.w_post[..COMBFILTER_MAXPERIOD].copy_from_slice(
3399                    &self.prefilter_mem[c * COMBFILTER_MAXPERIOD..(c + 1) * COMBFILTER_MAXPERIOD],
3400                );
3401                self.w_post[COMBFILTER_MAXPERIOD..COMBFILTER_MAXPERIOD + frame_size]
3402                    .copy_from_slice(pcm_frame);
3403
3404                let short_n = mode.short_mdct_size;
3405                // Call 1: first short_n samples, transition old→current params
3406                // Apply in-place on w_post[COMBFILTER_MAXPERIOD..], output overwrites input
3407                comb_filter_inplace(
3408                    &mut self.w_post,
3409                    COMBFILTER_MAXPERIOD,
3410                    self.prefilter_period_old,
3411                    self.prefilter_period,
3412                    short_n,
3413                    self.prefilter_gain_old,
3414                    self.prefilter_gain,
3415                    self.prefilter_tapset_old,
3416                    self.prefilter_tapset,
3417                    mode.window,
3418                    overlap,
3419                );
3420                if lm != 0 {
3421                    // Call 2: remaining N-short_n samples, transition current→new params
3422                    comb_filter_inplace(
3423                        &mut self.w_post,
3424                        COMBFILTER_MAXPERIOD + short_n,
3425                        self.prefilter_period,
3426                        pitch_index,
3427                        frame_size - short_n,
3428                        self.prefilter_gain,
3429                        gain1,
3430                        self.prefilter_tapset,
3431                        prefilter_tapset as i32,
3432                        mode.window,
3433                        overlap,
3434                    );
3435                }
3436
3437                pcm_frame.copy_from_slice(
3438                    &self.w_post[COMBFILTER_MAXPERIOD..COMBFILTER_MAXPERIOD + frame_size],
3439                );
3440
3441                self.decode_mem[channel_mem_offset + out_syn_idx
3442                    ..channel_mem_offset + out_syn_idx + frame_size]
3443                    .copy_from_slice(pcm_frame);
3444            }
3445            let mut new_mem = [0.0f32; COMBFILTER_MAXPERIOD];
3446            if frame_size >= COMBFILTER_MAXPERIOD {
3447                new_mem.copy_from_slice(&pcm_frame[frame_size - COMBFILTER_MAXPERIOD..frame_size]);
3448            } else {
3449                new_mem[..COMBFILTER_MAXPERIOD - frame_size].copy_from_slice(
3450                    &self.prefilter_mem
3451                        [c * COMBFILTER_MAXPERIOD + frame_size..(c + 1) * COMBFILTER_MAXPERIOD],
3452                );
3453                new_mem[COMBFILTER_MAXPERIOD - frame_size..].copy_from_slice(pcm_frame);
3454            }
3455            self.prefilter_mem[c * COMBFILTER_MAXPERIOD..(c + 1) * COMBFILTER_MAXPERIOD]
3456                .copy_from_slice(&new_mem);
3457
3458            let coef = mode.preemph[0];
3459            let mut m = self.preemph_mem[c];
3460            const VERY_SMALL: f32 = 1e-30f32;
3461            for i in 0..frame_size {
3462                let x = pcm_frame[i];
3463                let val = (x + VERY_SMALL + m).clamp(-SIG_SAT, SIG_SAT);
3464                pcm[c * frame_size + i] = val * (1.0 / 32768.0);
3465                m = val * coef;
3466            }
3467            self.preemph_mem[c] = m;
3468        }
3469
3470        self.prefilter_period_old = self.prefilter_period;
3471        self.prefilter_gain_old = self.prefilter_gain;
3472        self.prefilter_tapset_old = self.prefilter_tapset;
3473
3474        if pf_on {
3475            self.prefilter_period = pitch_index;
3476            self.prefilter_gain = gain1;
3477            self.prefilter_tapset = prefilter_tapset as i32;
3478        } else {
3479            self.prefilter_period = COMBFILTER_MINPERIOD;
3480            self.prefilter_gain = 0.0;
3481            self.prefilter_tapset = 0;
3482        }
3483
3484        if lm > 0 {
3485            self.prefilter_period_old = self.prefilter_period;
3486            self.prefilter_gain_old = self.prefilter_gain;
3487            self.prefilter_tapset_old = self.prefilter_tapset;
3488        }
3489
3490        // libopus celt_decoder.c:1140: after a mono frame in a stereo decoder,
3491        // replicate channel 0's coarse energy to channel 1 — this keeps ch1's
3492        // prediction state current through mono runs (and is what makes the
3493        // pre-decode MAX-merge a first-frame-only event).
3494        if channels == 1 && cc == 2 {
3495            let (ch0, ch1) = self.old_band_e.split_at_mut(nb_ebands);
3496            ch1[..nb_ebands].copy_from_slice(&ch0[..nb_ebands]);
3497        }
3498
3499        // oldLogE/oldLogE2 updates run over ALL state channels (2*nbEBands in
3500        // libopus), not just the coded ones.
3501        if !is_transient {
3502            self.old_band_e3.copy_from_slice(&self.old_band_e2);
3503            self.old_band_e2.copy_from_slice(&self.old_band_e);
3504        } else {
3505            for i in 0..cc * nb_ebands {
3506                self.old_band_e2[i] = self.old_band_e2[i].min(self.old_band_e[i]);
3507            }
3508        }
3509
3510        // "In case start or end were to change" (celt_decoder.c:1162-1174): zero
3511        // the coarse energy outside [start, end) and floor the log history, for
3512        // BOTH state channels. Matters for hybrid (start=17) and narrower
3513        // bandwidths (end<21) mixing with full-band frames in one stream.
3514        for c in 0..cc {
3515            for i in 0..start_band {
3516                self.old_band_e[c * nb_ebands + i] = 0.0;
3517                self.old_band_e2[c * nb_ebands + i] = -28.0;
3518                self.old_band_e3[c * nb_ebands + i] = -28.0;
3519            }
3520            for i in end_band..nb_ebands {
3521                self.old_band_e[c * nb_ebands + i] = 0.0;
3522                self.old_band_e2[c * nb_ebands + i] = -28.0;
3523                self.old_band_e3[c * nb_ebands + i] = -28.0;
3524            }
3525        }
3526
3527        self.rng = rc.rng;
3528        self.loss_count = 0;
3529
3530        frame_size
3531    }
3532
3533    /// Packet-loss concealment for a lost CELT frame — a port of libopus
3534    /// `celt_decode_lost` (celt_decoder.c). For the first few losses of a burst
3535    /// it uses the pitch-based branch (LPC-whitened excitation extrapolated at
3536    /// the last pitch period, resynthesized through the LPC filter — good for
3537    /// tonal/music content); once the burst runs long (`loss_count >= 5`) it
3538    /// falls back to the noise-based branch (spectrally-shaped, energy-decayed
3539    /// random excitation). Both fill the decode buffer, then this deemphasises
3540    /// to `pcm` (interleaved, /32768). Real attenuating audio instead of silence.
3541    pub fn conceal_lost(&mut self, frame_size: usize, pcm: &mut [f32]) {
3542        let n = frame_size;
3543        // start==0 for CELT-only; noise-based only once the burst is long.
3544        if self.loss_count >= 5 {
3545            self.conceal_fill_noise(n);
3546        } else {
3547            self.conceal_fill_pitch(n);
3548        }
3549
3550        // Deemphasise the concealed frame (decode_mem out_syn) to interleaved pcm.
3551        let mode = self.mode;
3552        let c = self.channels;
3553        let overlap = mode.overlap;
3554        let mem_size = DECODE_BUFFER_SIZE + overlap;
3555        let out_syn_idx = DECODE_BUFFER_SIZE - n;
3556        const SIG_SAT: f32 = 536870911.0;
3557        const VERY_SMALL: f32 = 1e-30f32;
3558        let coef = mode.preemph[0];
3559        for ch in 0..c {
3560            let out = ch * mem_size + out_syn_idx;
3561            let mut m = self.preemph_mem[ch];
3562            for i in 0..n {
3563                let x = self.decode_mem[out + i];
3564                let val = (x + VERY_SMALL + m).clamp(-SIG_SAT, SIG_SAT);
3565                pcm[i * c + ch] = val * (1.0 / 32768.0);
3566                m = val * coef;
3567            }
3568            self.preemph_mem[ch] = m;
3569        }
3570
3571        self.prefilter_period_old = self.prefilter_period;
3572        self.prefilter_gain_old = self.prefilter_gain;
3573        self.prefilter_period = COMBFILTER_MINPERIOD;
3574        self.prefilter_gain = 0.0;
3575        self.loss_count += 1;
3576    }
3577
3578    /// Noise-based concealment branch (celt_decode_lost, `noise_based`): fill the
3579    /// decode buffer's out_syn region with an energy-decayed random spectrum.
3580    fn conceal_fill_noise(&mut self, n: usize) {
3581        let mode = self.mode;
3582        let nb_ebands = mode.nb_ebands;
3583        let overlap = mode.overlap;
3584        let c = self.channels;
3585        let start = 0usize;
3586        let end = nb_ebands;
3587        let eff_end = end.min(mode.eff_ebands);
3588        let mem_size = DECODE_BUFFER_SIZE + overlap;
3589
3590        let mut lm = 0usize;
3591        while (mode.short_mdct_size << lm) != n && lm < mode.max_lm {
3592            lm += 1;
3593        }
3594
3595        let decay = if self.loss_count == 0 { 1.5f32 } else { 0.5f32 };
3596        for ch in 0..c {
3597            for i in start..end {
3598                let e = &mut self.old_band_e[ch * nb_ebands + i];
3599                *e = (*e - decay).max(-28.0);
3600            }
3601        }
3602
3603        let mut seed = self.rng;
3604        self.w_x[..n * c].fill(0.0);
3605        for ch in 0..c {
3606            for i in start..eff_end {
3607                let boffs = n * ch + ((mode.e_bands[i] as usize) << lm);
3608                let blen = ((mode.e_bands[i + 1] - mode.e_bands[i]) as usize) << lm;
3609                for j in 0..blen {
3610                    seed = crate::bands::celt_lcg_rand(seed);
3611                    self.w_x[boffs + j] = ((seed as i32) >> 20) as f32;
3612                }
3613                crate::bands::renormalise_vector(&mut self.w_x[boffs..boffs + blen], blen, 1.0);
3614            }
3615        }
3616        self.rng = seed;
3617
3618        for ch in 0..c {
3619            let base = ch * mem_size;
3620            self.decode_mem
3621                .copy_within(base + n..base + DECODE_BUFFER_SIZE + overlap / 2, base);
3622        }
3623
3624        self.w_band_amp[..nb_ebands * c].fill(0.0);
3625        let band_amp = &mut self.w_band_amp[..nb_ebands * c];
3626        log2amp(mode, nb_ebands, band_amp, &self.old_band_e, c);
3627        self.w_freq[..n * c].fill(0.0);
3628        let freq = &mut self.w_freq[..n * c];
3629        denormalise_bands(mode, &self.w_x, freq, band_amp, start, end, c, 1usize << lm);
3630
3631        let shift = mode.max_lm - lm;
3632        let out_syn_idx = DECODE_BUFFER_SIZE - n;
3633        const SIG_SAT: f32 = 536870911.0;
3634        for ch in 0..c {
3635            let out = ch * mem_size + out_syn_idx;
3636            self.mode.mdct.backward(
3637                &freq[ch * n..],
3638                &mut self.decode_mem[out..],
3639                mode.window,
3640                overlap,
3641                shift,
3642                1,
3643            );
3644            for i in 0..n {
3645                let v = &mut self.decode_mem[out + i];
3646                *v = v.clamp(-SIG_SAT, SIG_SAT);
3647            }
3648        }
3649    }
3650
3651    /// Pitch-based concealment branch (celt_decode_lost, pitch-based): extrapolate
3652    /// the LPC-whitened excitation at the last pitch period with per-period decay,
3653    /// resynthesize through the LPC filter, then TDAC-fold the overlap.
3654    fn conceal_fill_pitch(&mut self, n: usize) {
3655        let mode = self.mode;
3656        let overlap = mode.overlap;
3657        let c = self.channels;
3658        let mem_size = DECODE_BUFFER_SIZE + overlap;
3659        const MAX_PERIOD: usize = COMBFILTER_MAXPERIOD;
3660        let ord = PLC_LPC_ORDER;
3661        let out_syn_idx = DECODE_BUFFER_SIZE - n;
3662        const SIG_SAT: f32 = 536870911.0;
3663        let window = mode.window;
3664
3665        // Pitch lag: search on the first loss, reuse across the burst.
3666        let mut fade = 1.0f32;
3667        if self.loss_count == 0 {
3668            let mut lp = vec![0.0f32; DECODE_BUFFER_SIZE >> 1];
3669            let slices: Vec<&[f32]> = (0..c)
3670                .map(|ch| &self.decode_mem[ch * mem_size..ch * mem_size + DECODE_BUFFER_SIZE])
3671                .collect();
3672            crate::pitch::pitch_downsample(&slices, &mut lp, DECODE_BUFFER_SIZE >> 1, c, 2);
3673            let pr = crate::pitch::pitch_search(
3674                &lp[PLC_PITCH_LAG_MAX >> 1..],
3675                &lp,
3676                DECODE_BUFFER_SIZE - PLC_PITCH_LAG_MAX,
3677                PLC_PITCH_LAG_MAX - PLC_PITCH_LAG_MIN,
3678            );
3679            self.last_pitch_index = (PLC_PITCH_LAG_MAX - pr) as i32;
3680        } else {
3681            fade = 0.8;
3682        }
3683        let pitch_index = (self.last_pitch_index.max(1) as usize).min(MAX_PERIOD - 1);
3684        let exc_length = (2 * pitch_index).min(MAX_PERIOD);
3685
3686        let mut etmp = vec![0.0f32; overlap];
3687        for ch in 0..c {
3688            let base = ch * mem_size;
3689            // exc[k] = exc_buf[ord + k] for k in -ord..MAX_PERIOD.
3690            let mut exc_buf = vec![0.0f32; MAX_PERIOD + ord];
3691            for (i, v) in exc_buf.iter_mut().enumerate() {
3692                *v = self.decode_mem[base + DECODE_BUFFER_SIZE - MAX_PERIOD - ord + i];
3693            }
3694            if self.loss_count == 0 {
3695                let mut ac = vec![0.0f32; ord + 1];
3696                crate::celt_lpc::autocorr(
3697                    &exc_buf[ord..ord + MAX_PERIOD],
3698                    &mut ac,
3699                    Some(window),
3700                    overlap,
3701                    ord,
3702                    MAX_PERIOD,
3703                );
3704                ac[0] *= 1.0001; // -40 dB noise floor
3705                for i in 1..=ord {
3706                    ac[i] -= ac[i] * (0.008 * 0.008) * (i * i) as f32; // lag windowing
3707                }
3708                let mut lc = vec![0.0f32; ord];
3709                crate::celt_lpc::lpc(&mut lc, &ac, ord);
3710                self.plc_lpc[ch * ord..ch * ord + ord].copy_from_slice(&lc);
3711            }
3712            let lc: Vec<f32> = self.plc_lpc[ch * ord..ch * ord + ord].to_vec();
3713
3714            // Whiten the last exc_length excitation samples (celt_fir with history
3715            // — pass the ord preceding samples and read outputs at [ord..]).
3716            {
3717                let x = &exc_buf[MAX_PERIOD - exc_length..];
3718                let mut y = vec![0.0f32; ord + exc_length];
3719                crate::celt_lpc::celt_fir(x, &lc, &mut y, ord + exc_length, ord);
3720                for i in 0..exc_length {
3721                    exc_buf[ord + MAX_PERIOD - exc_length + i] = y[ord + i];
3722                }
3723            }
3724
3725            // Decay factor from the excitation energy ratio (avoid adding energy).
3726            let decay_length = exc_length >> 1;
3727            let mut e1 = 1.0f32;
3728            let mut e2 = 1.0f32;
3729            for i in 0..decay_length {
3730                let a = exc_buf[ord + MAX_PERIOD - decay_length + i];
3731                e1 += a * a;
3732                let b = exc_buf[ord + MAX_PERIOD - 2 * decay_length + i];
3733                e2 += b * b;
3734            }
3735            e1 = e1.min(e2);
3736            let decay = (e1 / e2).sqrt();
3737
3738            // Shift decode buffer one frame left.
3739            self.decode_mem
3740                .copy_within(base + n..base + DECODE_BUFFER_SIZE, base);
3741
3742            // Extrapolate at period `pitch_index`, attenuating each period.
3743            let extrapolation_offset = MAX_PERIOD - pitch_index;
3744            let extrapolation_len = n + overlap;
3745            let mut atten = fade * decay;
3746            let mut j = 0usize;
3747            let mut s1 = 0.0f32;
3748            for i in 0..extrapolation_len {
3749                if j >= pitch_index {
3750                    j -= pitch_index;
3751                    atten *= decay;
3752                }
3753                self.decode_mem[base + out_syn_idx + i] =
3754                    atten * exc_buf[ord + extrapolation_offset + j];
3755                let tmp = self.decode_mem
3756                    [base + (DECODE_BUFFER_SIZE - MAX_PERIOD - n) + extrapolation_offset + j];
3757                s1 += tmp * tmp;
3758                j += 1;
3759            }
3760
3761            // Resynthesize: excitation -> signal through the LPC synthesis filter.
3762            let mut lpc_mem = [0.0f32; PLC_LPC_ORDER];
3763            for (i, v) in lpc_mem.iter_mut().enumerate().take(ord) {
3764                *v = self.decode_mem[base + DECODE_BUFFER_SIZE - n - 1 - i];
3765            }
3766            let extrap: Vec<f32> = self.decode_mem
3767                [base + out_syn_idx..base + out_syn_idx + extrapolation_len]
3768                .to_vec();
3769            crate::celt_lpc::celt_iir(
3770                &extrap,
3771                &lc,
3772                &mut self.decode_mem[base + out_syn_idx..base + out_syn_idx + extrapolation_len],
3773                extrapolation_len,
3774                ord,
3775                &mut lpc_mem[..ord],
3776            );
3777            for i in 0..extrapolation_len {
3778                let v = &mut self.decode_mem[base + out_syn_idx + i];
3779                *v = v.clamp(-SIG_SAT, SIG_SAT);
3780            }
3781
3782            // Explosion / NaN guard (the !(S1 > .2*S2) test also catches IIR NaNs).
3783            let mut s2 = 0.0f32;
3784            for i in 0..extrapolation_len {
3785                let t = self.decode_mem[base + out_syn_idx + i];
3786                s2 += t * t;
3787            }
3788            if !(s1 > 0.2 * s2) {
3789                for i in 0..extrapolation_len {
3790                    self.decode_mem[base + out_syn_idx + i] = 0.0;
3791                }
3792            } else if s1 < s2 {
3793                let ratio = ((s1 + 1.0) / (s2 + 1.0)).sqrt();
3794                for i in 0..overlap {
3795                    let g = 1.0 - window[i] * (1.0 - ratio);
3796                    self.decode_mem[base + out_syn_idx + i] *= g;
3797                }
3798                for i in overlap..extrapolation_len {
3799                    self.decode_mem[base + out_syn_idx + i] *= ratio;
3800                }
3801            }
3802
3803            // Re-apply the postfilter to the overlap, then TDAC-fold so the
3804            // concealed audio blends with the next frame's MDCT.
3805            comb_filter(
3806                &mut etmp,
3807                &self.decode_mem,
3808                0,
3809                base + DECODE_BUFFER_SIZE,
3810                self.prefilter_period,
3811                self.prefilter_period,
3812                overlap,
3813                -self.prefilter_gain,
3814                -self.prefilter_gain,
3815                self.prefilter_tapset,
3816                self.prefilter_tapset,
3817                window,
3818                0,
3819            );
3820            for i in 0..overlap / 2 {
3821                self.decode_mem[base + DECODE_BUFFER_SIZE + i] =
3822                    window[i] * etmp[overlap - 1 - i] + window[overlap - 1 - i] * etmp[i];
3823            }
3824        }
3825    }
3826}
3827
3828#[cfg(test)]
3829mod tests {
3830    use super::*;
3831    use crate::{modes, range_coder::RangeCoder};
3832
3833    // Regression test: directly drive CeltEncoder with an invalid frame_size=48,
3834    // bypassing the OpusEncoder::encode() validation layer.
3835    //
3836    // This reproduces the crash that was reported against opus-rs 0.1.19 when
3837    // G.729-decoded PCM (8 kHz) reached the 48 kHz Opus encoder without correct
3838    // resampling, producing a 48-sample frame instead of 480.
3839    //
3840    // Root cause: the lm-search in encode_impl finds no valid match for frame_size=48
3841    // (valid sizes are 120, 240, 480, 960) and silently falls back to lm=0.
3842    // With lm=0 and shift=max_lm=3: n=1920>>3=240, n2=120, overlap2=60.
3843    // The in_buf slice has only frame_size+overlap=168 elements, but forward()
3844    // requires input.len() >= n2+overlap2 = 180, so it panics immediately.
3845    // In opus-rs 0.1.19 this assertion was absent and the crash reached the MDCT
3846    // output write: "index out of bounds: the len is 48 but the index is 119".
3847    //
3848    // Either way: the call panics, confirming the crash path is real.
3849    // The fix in OpusEncoder::encode() returns Err before reaching CeltEncoder.
3850    #[test]
3851    #[should_panic]
3852    fn test_celt_frame_size_48_panics_confirms_crash_path() {
3853        let mode = modes::default_mode();
3854        let mut enc = CeltEncoder::new(mode, 1);
3855        // frame_size=48: lm-search fails, falls back to lm=0.
3856        // forward() will panic — either on the input-size assertion (0.1.21+) or
3857        // on the output write (0.1.19): "len is 48 but the index is 119".
3858        let pcm = vec![0.0f32; 48 + mode.overlap]; // supply ≥ frame_size samples
3859        let mut rc = RangeCoder::new_encoder(100);
3860        enc.encode_with_budget(&pcm, 48, &mut rc, 0, 21, 800);
3861    }
3862
3863    // Prefilter/postfilter inversion, MDCT bypassed: run the real run_prefilter
3864    // per frame (with the real signalling quantization of gain/period), feed the
3865    // FILTERED stream straight into the decoder's postfilter sequence (call 1
3866    // old->current over shortMdctSize, call 2 current->new with the crossfade),
3867    // honoring the 120-sample MDCT delay. If the encoder applies exactly what it
3868    // signals with the timing the decoder inverts, the round trip is ~identity.
3869    #[test]
3870    fn prefilter_postfilter_inversion() {
3871        let mode = modes::default_mode();
3872        let n = 960usize;
3873        let overlap = mode.overlap; // 120
3874        let short_n = mode.short_mdct_size; // 120
3875        let frames = 100usize;
3876        let max_period = COMBFILTER_MAXPERIOD;
3877
3878        // Signal designed to TOGGLE the prefilter: alternating strongly periodic
3879        // stretches (varying pitch) and noise bursts.
3880        let total = frames * n;
3881        let mut x = vec![0.0f32; total];
3882        let mut rng = 0x12345678u32;
3883        let mut next = || {
3884            rng = rng.wrapping_mul(1664525).wrapping_add(1013904223);
3885            (rng >> 8) as f32 / (1 << 24) as f32 - 0.5
3886        };
3887        for (t, v) in x.iter_mut().enumerate() {
3888            let seg = t / (n * 10);
3889            let phase = t as f32;
3890            *v = match seg % 4 {
3891                0 => (phase * std::f32::consts::TAU / 147.0).sin() * 8000.0, // ~326 Hz
3892                1 => next() * 6000.0,
3893                2 => {
3894                    ((phase * std::f32::consts::TAU / 89.0).sin()
3895                        + 0.5 * (phase * std::f32::consts::TAU / 44.5).sin())
3896                        * 7000.0
3897                }
3898                _ => (phase * std::f32::consts::TAU / 480.0).sin() * 5000.0, // 100 Hz
3899            };
3900        }
3901
3902        // ---- encoder side ----
3903        let mut pre = vec![0.0f32; max_period + n];
3904        let mut pitch_buf = vec![0.0f32; (max_period + n) >> 1];
3905        let mut prefilter_mem = vec![0.0f32; max_period];
3906        let mut in_mem = vec![0.0f32; overlap];
3907        let (mut prev_t, mut prev_g) = (COMBFILTER_MINPERIOD, 0.0f32);
3908        let analysis = AnalysisInfo::default();
3909        let mut filtered = vec![0.0f32; total];
3910        let mut params = Vec::new(); // (pf_on, T, g) per frame
3911        let mut in_buf = vec![0.0f32; n + overlap];
3912        for k in 0..frames {
3913            in_buf[..overlap].copy_from_slice(&in_mem);
3914            in_buf[overlap..].copy_from_slice(&x[k * n..(k + 1) * n]);
3915            let (pf_on, g1, t1) = run_prefilter(
3916                &mut in_buf,
3917                &mut prefilter_mem,
3918                prev_t,
3919                prev_g,
3920                0, // prefilter_tapset (old)
3921                0, // tapset_decision (new)
3922                mode.window,
3923                1,
3924                n,
3925                overlap,
3926                &mut pre,
3927                &mut pitch_buf,
3928                &analysis,
3929                0,
3930                159,
3931            );
3932            filtered[k * n..(k + 1) * n].copy_from_slice(&in_buf[overlap..]);
3933            in_mem.copy_from_slice(&in_buf[n..]);
3934            params.push((pf_on, t1, g1));
3935            // encoder end-of-frame state update
3936            prev_t = if pf_on { t1 } else { COMBFILTER_MINPERIOD };
3937            prev_g = if pf_on { g1 } else { 0.0 };
3938        }
3939
3940        // ---- decoder side (postfilter only), 120-sample MDCT delay ----
3941        let mut delayed = vec![0.0f32; total];
3942        delayed[short_n..].copy_from_slice(&filtered[..total - short_n]);
3943        let mut w = vec![0.0f32; max_period + n];
3944        let mut post_mem = vec![0.0f32; max_period];
3945        let (mut d_t_old, mut d_g_old) = (COMBFILTER_MINPERIOD, 0.0f32);
3946        let (mut d_t, mut d_g) = (COMBFILTER_MINPERIOD, 0.0f32);
3947        let mut out = vec![0.0f32; total];
3948        for k in 0..frames {
3949            let (pf_on, sig_t, sig_g) = params[k];
3950            let (gain1, pitch_index) = if pf_on {
3951                (sig_g, sig_t)
3952            } else {
3953                (0.0, COMBFILTER_MINPERIOD)
3954            };
3955            w[..max_period].copy_from_slice(&post_mem);
3956            w[max_period..].copy_from_slice(&delayed[k * n..(k + 1) * n]);
3957            if pf_on || d_g > 0.0 || d_g_old > 0.0 {
3958                comb_filter_inplace(
3959                    &mut w, max_period, d_t_old, d_t, short_n, d_g_old, d_g, 0, 0, mode.window,
3960                    overlap,
3961                );
3962                comb_filter_inplace(
3963                    &mut w,
3964                    max_period + short_n,
3965                    d_t,
3966                    pitch_index,
3967                    n - short_n,
3968                    d_g,
3969                    gain1,
3970                    0,
3971                    0,
3972                    mode.window,
3973                    overlap,
3974                );
3975            }
3976            out[k * n..(k + 1) * n].copy_from_slice(&w[max_period..]);
3977            post_mem.copy_from_slice(&w[n..]);
3978            // decoder end-of-frame chain, then the lm > 0 override
3979            if pf_on {
3980                d_t = pitch_index;
3981                d_g = gain1;
3982            } else {
3983                d_t = COMBFILTER_MINPERIOD;
3984                d_g = 0.0;
3985            }
3986            d_t_old = d_t;
3987            d_g_old = d_g;
3988        }
3989
3990        // ---- compare out (delayed by short_n) against x ----
3991        let m = total - 2 * n;
3992        let mut se = 0.0f64;
3993        let mut sx = 0.0f64;
3994        for t in n..m {
3995            let e = (out[t + short_n] - x[t]) as f64;
3996            se += e * e;
3997            sx += (x[t] as f64) * (x[t] as f64);
3998        }
3999        let snr = 10.0 * (sx / se.max(1e-30)).log10();
4000        let engaged = params.iter().filter(|p| p.0).count();
4001        assert!(
4002            engaged > frames / 4,
4003            "prefilter never engaged ({engaged}/{frames}) — test signal too weak"
4004        );
4005        assert!(
4006            snr > 90.0,
4007            "prefilter/postfilter round trip not transparent: SNR={snr:.1} dB (engaged {engaged}/{frames})"
4008        );
4009    }
4010}