Skip to main content

libflac_rs/
encoder.rs

1//! Block/frame orchestration, ported from `process_frame_` /
2//! `process_subframes_` / `process_subframe_` (`stream_encoder.c`).
3//! CONSTANT/VERBATIM/FIXED/LPC subframes and, for stereo, the per-frame mid-side
4//! channel decision (L/R vs L/S vs R/S vs M/S by estimated bits, including the
5//! `loose_mid_side` periodic-redecision mode). All compression levels 0–8 are
6//! supported via [`Config`]/[`preset`] (the `tukey(0.5)` / `subdivide_tukey(2|3)`
7//! apodizations and the per-level LPC-order / partition-order caps). All bit depths
8//! 8/12/16/20/24/32 are supported — RICE2 entropy coding above 16 bps, and for
9//! 32-bit input the 33-bit `i64` side channel plus the wide / overflow-limited
10//! residual paths.
11
12use crate::bitmath;
13use crate::bitwriter::BitWriter;
14use crate::format::{
15    ENTROPY_CODING_METHOD_PARTITIONED_RICE_ESCAPE_PARAMETER,
16    ENTROPY_CODING_METHOD_PARTITIONED_RICE2_ESCAPE_PARAMETER, MAX_FIXED_ORDER, MAX_LPC_ORDER,
17    MAX_QLP_COEFF_PRECISION, MIN_QLP_COEFF_PRECISION,
18};
19use crate::frame::{ChannelAssignment, FrameHeader, write_frame_footer, write_frame_header};
20use crate::{fixed, lpc, metadata, ogg, rice, subframe, window};
21
22/// Minimum residual partition order (0 for every compression level).
23const MIN_RESIDUAL_PARTITION_ORDER: u32 = 0;
24
25/// The Rice parameter limit (escape value): RICE2 (31) above 16 bps, else RICE
26/// (15) (`process_subframe_`, `stream_encoder.c:3471`). Based on the *stream* bps.
27fn rice_parameter_limit(bits_per_sample: u32) -> u32 {
28    if bits_per_sample > 16 {
29        ENTROPY_CODING_METHOD_PARTITIONED_RICE2_ESCAPE_PARAMETER
30    } else {
31        ENTROPY_CODING_METHOD_PARTITIONED_RICE_ESCAPE_PARAMETER
32    }
33}
34
35/// The apodization a compression level uses. Both variants ultimately window with
36/// a Tukey window; `SubdivideTukey` additionally evaluates the per-subdivision
37/// partial/punchout sub-windows via the a/b/c state machine.
38#[derive(Clone, Copy)]
39pub enum Apodization {
40    /// A single Tukey window of the given `p` (e.g. `tukey(0.5)`).
41    Tukey(f32),
42    /// `subdivide_tukey(parts)`: a Tukey window of `p = 0.5/parts` plus its
43    /// `parts`-deep partial/punchout subdivisions.
44    SubdivideTukey(i32),
45}
46
47impl Apodization {
48    /// The `p` of the underlying Tukey window.
49    fn window_p(self) -> f32 {
50        match self {
51            Apodization::Tukey(p) => p,
52            Apodization::SubdivideTukey(parts) => 0.5 / parts as f32,
53        }
54    }
55    fn subdivide_parts(self) -> i32 {
56        match self {
57            Apodization::Tukey(_) => 1,
58            Apodization::SubdivideTukey(parts) => parts,
59        }
60    }
61    fn is_subdivide(self) -> bool {
62        matches!(self, Apodization::SubdivideTukey(_))
63    }
64}
65
66/// The compression settings of one libFLAC compression level (the fields of
67/// `compression_levels_[]`, `stream_encoder.c:123`, that actually vary). The
68/// constant fields — `min_residual_partition_order = 0`, `qlp_coeff_precision = 0`
69/// (auto), and the escape/exhaustive/precision-search flags all `false` — are
70/// implied.
71#[derive(Clone, Copy)]
72pub struct Config {
73    pub do_mid_side: bool,
74    pub loose_mid_side: bool,
75    pub max_lpc_order: u32,
76    pub max_residual_partition_order: u32,
77    pub apodization: Apodization,
78}
79
80/// The preset for compression level `0..=8` (`compression_levels_[]`); levels
81/// above 8 clamp to 8.
82pub fn preset(level: u32) -> Config {
83    use Apodization::{SubdivideTukey, Tukey};
84    let (do_mid_side, loose_mid_side, max_lpc_order, max_residual_partition_order, apodization) =
85        match level {
86            0 => (false, false, 0, 3, Tukey(0.5)),
87            1 => (true, true, 0, 3, Tukey(0.5)),
88            2 => (true, false, 0, 3, Tukey(0.5)),
89            3 => (false, false, 6, 4, Tukey(0.5)),
90            4 => (true, true, 8, 4, Tukey(0.5)),
91            5 => (true, false, 8, 5, Tukey(0.5)),
92            6 => (true, false, 8, 6, SubdivideTukey(2)),
93            7 => (true, false, 12, 6, SubdivideTukey(2)),
94            _ => (true, false, 12, 6, SubdivideTukey(3)),
95        };
96    Config {
97        do_mid_side,
98        loose_mid_side,
99        max_lpc_order,
100        max_residual_partition_order,
101        apodization,
102    }
103}
104
105/// Detect and strip wasted (common trailing-zero) bits, mutating `signal` in
106/// place and returning the shift (`get_wasted_bits_` / `get_wasted_bits_wide_`,
107/// `stream_encoder.c:4469`/`4493`). `wide` selects the 33-bit-side variant, used
108/// only for the side channel of 32-bit stereo: it differs solely in the all-zero
109/// case, returning shift **1** (so a 33-bit side always fits `i32` after the
110/// shift) where the narrow version returns 0.
111pub(crate) fn get_wasted_bits(signal: &mut [i64], wide: bool) -> u32 {
112    let mut x = 0i64;
113    let mut i = 0;
114    while i < signal.len() && x & 1 == 0 {
115        x |= signal[i];
116        i += 1;
117    }
118    let shift = if x == 0 {
119        u32::from(wide)
120    } else {
121        x.trailing_zeros()
122    };
123    if shift > 0 {
124        for s in signal.iter_mut() {
125            *s >>= shift;
126        }
127    }
128    shift
129}
130
131/// Auto QLP-coefficient precision (`stream_encoder.c:704`), derived once at init
132/// from the *stream* bits-per-sample and the (full) block size; the short final
133/// frame reuses the same value.
134fn auto_qlp_coeff_precision(bits_per_sample: u32, blocksize: u32) -> u32 {
135    if bits_per_sample < 16 {
136        (2 + bits_per_sample / 2).max(MIN_QLP_COEFF_PRECISION)
137    } else if bits_per_sample == 16 {
138        match blocksize {
139            0..=192 => 7,
140            193..=384 => 8,
141            385..=576 => 9,
142            577..=1152 => 10,
143            1153..=2304 => 11,
144            2305..=4608 => 12,
145            _ => 13,
146        }
147    } else {
148        match blocksize {
149            0..=384 => MAX_QLP_COEFF_PRECISION - 2,
150            385..=1152 => MAX_QLP_COEFF_PRECISION - 1,
151            _ => MAX_QLP_COEFF_PRECISION,
152        }
153    }
154}
155
156/// Settings the LPC path needs, plus the precomputed window for the current
157/// frame's block size.
158struct LpcCtx<'a> {
159    window: &'a [f32],
160    max_lpc_order: u32,
161    qlp_coeff_precision: u32,
162    apodization: Apodization,
163}
164
165/// One fully-evaluated LPC subframe candidate.
166struct LpcChoice {
167    order: u32,
168    qlp_coeff: Vec<i32>,
169    precision: u32,
170    shift: i32,
171    residual: Vec<i32>,
172    rice: rice::RicePartition,
173}
174
175enum Choice {
176    Constant,
177    Verbatim,
178    Fixed {
179        order: u32,
180        residual: Vec<i32>,
181        rice: rice::RicePartition,
182    },
183    Lpc(LpcChoice),
184}
185
186/// A channel's chosen subframe plus everything needed to write it once the
187/// channel assignment is decided.
188struct ChosenChannel {
189    signal: Vec<i64>,
190    subframe_bps: u32,
191    wasted_bits: u32,
192    choice: Choice,
193    bits: u32,
194}
195
196/// Advance the `subdivide_tukey` a/b/c state to the next sub-window
197/// (`set_next_subdivide_tukey`, `stream_encoder.c:3686`). `a` = apodization index,
198/// `depth` = subdivision denominator (b), `part` = interleaved partial/punchout
199/// counter (c).
200fn set_next_subdivide_tukey(parts: i32, a: &mut u32, depth: &mut u32, part: &mut u32) {
201    if *depth == 2 {
202        // Depth 2 does only partial, no (near-redundant) punchout.
203        if *part == 0 {
204            *part = 2;
205        } else {
206            *part = 0;
207            *depth += 1;
208        }
209    } else if *part < 2 * *depth - 1 {
210        *part += 1;
211    } else {
212        *part = 0;
213        *depth += 1;
214    }
215    if *depth > parts as u32 {
216        *a += 1;
217        *depth = 1;
218        *part = 0;
219    }
220}
221
222/// One step of the apodization state machine (`apply_apodization_`,
223/// `stream_encoder.c:3711`) for a `subdivide_tukey` apodization. Produces the LP
224/// coefficients and a guess order for the current sub-window, advancing `a/b/c`,
225/// or returns `None` to skip this sub-window (tiny block, or a constant signal).
226#[allow(clippy::too_many_arguments)]
227fn apply_apodization(
228    signal: &[i64],
229    window: &[f32],
230    windowed: &mut [f32],
231    autoc: &mut [f64],
232    autoc_root: &mut [f64],
233    blocksize: usize,
234    max_order: usize,
235    subframe_bps: u32,
236    ctx: &LpcCtx,
237    a: &mut u32,
238    b: &mut u32,
239    c: &mut u32,
240) -> Option<(lpc::LpCoefficients, usize)> {
241    let parts = ctx.apodization.subdivide_parts();
242    let lag = max_order + 1;
243    if *b == 1 {
244        // Window the full block (the "root" autocorrelation).
245        lpc::window_data(signal, window, windowed, blocksize);
246        lpc::compute_autocorrelation(&windowed[..blocksize], lag, autoc);
247        if ctx.apodization.is_subdivide() {
248            autoc_root[..max_order].copy_from_slice(&autoc[..max_order]);
249            *b += 1;
250        } else {
251            // A plain Tukey apodization is a single full-block window.
252            *a += 1;
253        }
254    } else {
255        let bb = *b as usize;
256        if blocksize / bb <= MAX_LPC_ORDER as usize {
257            // Windowing parts <= 32 samples is unsupported; skip and advance.
258            set_next_subdivide_tukey(parts, a, b, c);
259            return None;
260        }
261        if *c % 2 == 0 {
262            // Even c: the (c/2)th partial window of size blocksize/b.
263            let part_size = blocksize / bb / 2;
264            let data_shift = (*c as usize / 2 * blocksize) / bb;
265            lpc::window_data_partial(signal, window, windowed, blocksize, part_size, data_shift);
266            lpc::compute_autocorrelation(&windowed[..blocksize / bb], lag, autoc);
267        } else {
268            // Odd c: the root window minus the previous partial (a punchout). Only
269            // the first `max_order` lags are subtracted; autoc[max_order] is left
270            // as the previous partial's value, exactly as libFLAC does.
271            for i in 0..max_order {
272                autoc[i] = autoc_root[i] - autoc[i];
273            }
274        }
275        set_next_subdivide_tukey(parts, a, b, c);
276    }
277
278    if autoc[0] == 0.0 {
279        // Signal is constant; can't do LP.
280        return None;
281    }
282    let lp = lpc::compute_lp_coefficients(autoc, max_order);
283    // do_qlp_coeff_prec_search is false at level 8, so the order-selection overhead
284    // uses the actual qlp precision.
285    let overhead = subframe_bps + ctx.qlp_coeff_precision;
286    let guess_order = lpc::compute_best_order(&lp.error, lp.max_order, blocksize as u32, overhead);
287    Some((lp, guess_order))
288}
289
290/// Quantize + residual + rice + estimate for one (order, coefficient row)
291/// (`evaluate_lpc_subframe_`, `stream_encoder.c:3954`). Returns `None` when the
292/// coefficients can't be quantized or the residual overflows i32 (the C returns 0,
293/// meaning "can't LPC at this order").
294#[allow(clippy::too_many_arguments)]
295fn evaluate_lpc_subframe(
296    signal: &[i64],
297    subframe_bps: u32,
298    wasted_bits: u32,
299    lp_coeff_row: &[f32],
300    order: u32,
301    qlp_coeff_precision: u32,
302    rice_parameter_limit: u32,
303    min_partition_order: u32,
304    max_partition_order: u32,
305) -> Option<(LpcChoice, u32)> {
306    let order_us = order as usize;
307
308    // Keep qlp precision low enough that decode of <=16bps(+1 for side) needs only
309    // 32-bit math.
310    let mut precision = qlp_coeff_precision;
311    if subframe_bps <= 17 {
312        precision = precision.min(32 - subframe_bps - bitmath::ilog2(order));
313    }
314
315    let q = lpc::quantize_coefficients(lp_coeff_row, order_us, precision).ok()?;
316
317    let residual = if lpc::max_residual_bps(subframe_bps, &q.qlp_coeff, order_us, q.shift) > 32 {
318        lpc::compute_residual_limit(signal, order_us, &q.qlp_coeff, q.shift)?
319    } else {
320        lpc::compute_residual(signal, order_us, &q.qlp_coeff, q.shift)
321    };
322
323    let (rice, residual_bits) = rice::find_best_partition_order(
324        &residual,
325        order,
326        rice_parameter_limit,
327        min_partition_order,
328        max_partition_order,
329    );
330    let bits = subframe::lpc_bits(order, precision, subframe_bps, wasted_bits, residual_bits);
331
332    Some((
333        LpcChoice {
334            order,
335            qlp_coeff: q.qlp_coeff,
336            precision,
337            shift: q.shift,
338            residual,
339            rice,
340        },
341        bits,
342    ))
343}
344
345/// Best LPC subframe over all `subdivide_tukey` sub-windows (the LPC arm of
346/// `process_subframe_`, `stream_encoder.c:3594`). Each sub-window is evaluated at
347/// its single guess order (no exhaustive/precision search at level 8). Returns the
348/// lowest-bit candidate, or `None` if none could be produced.
349#[allow(clippy::too_many_arguments)]
350fn best_lpc_subframe(
351    signal: &[i64],
352    subframe_bps: u32,
353    wasted_bits: u32,
354    blocksize: u32,
355    ctx: &LpcCtx,
356    rice_parameter_limit: u32,
357    min_partition_order: u32,
358    max_partition_order: u32,
359) -> Option<(LpcChoice, u32)> {
360    let max_lpc_order = if ctx.max_lpc_order >= blocksize {
361        blocksize - 1
362    } else {
363        ctx.max_lpc_order
364    };
365    if max_lpc_order == 0 {
366        return None;
367    }
368
369    let bs = blocksize as usize;
370    let mut windowed = vec![0f32; bs];
371    let mut autoc = [0f64; MAX_LPC_ORDER as usize + 1];
372    let mut autoc_root = [0f64; MAX_LPC_ORDER as usize + 1];
373
374    let mut best: Option<(LpcChoice, u32)> = None;
375    // num_apodizations == 1 (a single subdivide_tukey); the state machine drives
376    // `a` to 1 once the apodization is exhausted.
377    let (mut a, mut b, mut c) = (0u32, 1u32, 0u32);
378    while a < 1 {
379        let max_order_this = max_lpc_order as usize;
380        if let Some((lp, guess_order)) = apply_apodization(
381            signal,
382            ctx.window,
383            &mut windowed,
384            &mut autoc,
385            &mut autoc_root,
386            bs,
387            max_order_this,
388            subframe_bps,
389            ctx,
390            &mut a,
391            &mut b,
392            &mut c,
393        ) {
394            // Non-exhaustive: only the guess order is tried.
395            let lpc_residual_bps =
396                lpc::expected_bits(lp.error[guess_order - 1], blocksize - guess_order as u32);
397            if lpc_residual_bps < subframe_bps as f64 {
398                if let Some((choice, bits)) = evaluate_lpc_subframe(
399                    signal,
400                    subframe_bps,
401                    wasted_bits,
402                    lp.row(guess_order),
403                    guess_order as u32,
404                    ctx.qlp_coeff_precision,
405                    rice_parameter_limit,
406                    min_partition_order,
407                    max_partition_order,
408                ) {
409                    if best.as_ref().is_none_or(|(_, bb)| bits < *bb) {
410                        best = Some((choice, bits));
411                    }
412                }
413            }
414        }
415    }
416    best
417}
418
419/// Choose the smallest-estimate subframe for one (already wasted-bits-shifted)
420/// channel block, returning the choice and its estimated bit cost
421/// (`process_subframe_`, `stream_encoder.c:3441`). VERBATIM is the baseline;
422/// CONSTANT wins for a single repeated value; otherwise FIXED and LPC compete. On
423/// ties the earlier candidate is kept (strict `<`). Writing is deferred so the
424/// mid-side channel decision can pick among already-evaluated subframes.
425#[allow(clippy::too_many_arguments)]
426fn choose_subframe(
427    signal: &[i64],
428    subframe_bps: u32,
429    wasted_bits: u32,
430    blocksize: u32,
431    rice_parameter_limit: u32,
432    min_partition_order: u32,
433    max_partition_order: u32,
434    lpc_ctx: Option<&LpcCtx>,
435) -> (Choice, u32) {
436    let bs = signal.len() as u32;
437    let mut best_bits = subframe::verbatim_bits(bs, subframe_bps, wasted_bits);
438    let mut best = Choice::Verbatim;
439
440    if bs > MAX_FIXED_ORDER {
441        // libFLAC's constant detection keys off `fixed_residual_bits_per_sample[1]
442        // == 0.0`, which the guess predictor only produces below 28 bps; at
443        // `subframe_bps >= 28` the `_limit_residual` predictor reports 34.0 even for
444        // a constant signal, so CONSTANT is never selected there (it becomes FIXED).
445        if subframe_bps < 28 && signal.iter().all(|&s| s == signal[0]) {
446            let cb = subframe::constant_bits(subframe_bps, wasted_bits);
447            if cb < best_bits {
448                best_bits = cb;
449                best = Choice::Constant;
450            }
451        } else {
452            let (order, fixed_rbps) = fixed::compute_best_predictor_order(signal, subframe_bps);
453            // libFLAC skips a fixed order whose estimated bits/sample already meets
454            // or exceeds the subframe bps (`process_subframe_`, stream_encoder.c:3561)
455            // — e.g. an incompressible/overflowing wide signal — leaving VERBATIM.
456            if (fixed_rbps) < subframe_bps as f32 {
457                let residual = fixed::compute_residual(signal, order);
458                let (rice_part, residual_bits) = rice::find_best_partition_order(
459                    &residual,
460                    order,
461                    rice_parameter_limit,
462                    min_partition_order,
463                    max_partition_order,
464                );
465                let fb = subframe::fixed_bits(order, subframe_bps, wasted_bits, residual_bits);
466                if fb < best_bits {
467                    best_bits = fb;
468                    best = Choice::Fixed {
469                        order,
470                        residual,
471                        rice: rice_part,
472                    };
473                }
474            }
475
476            if let Some(ctx) = lpc_ctx {
477                if let Some((lpc_choice, lpc_bits)) = best_lpc_subframe(
478                    signal,
479                    subframe_bps,
480                    wasted_bits,
481                    blocksize,
482                    ctx,
483                    rice_parameter_limit,
484                    min_partition_order,
485                    max_partition_order,
486                ) {
487                    if lpc_bits < best_bits {
488                        best_bits = lpc_bits;
489                        best = Choice::Lpc(lpc_choice);
490                    }
491                }
492            }
493        }
494    }
495
496    (best, best_bits)
497}
498
499/// Write a previously-chosen subframe (`add_subframe_`, `stream_encoder.c:3787`).
500/// `signal` is the channel block the choice was made on (for warmup/raw samples).
501fn write_choice(
502    bw: &mut BitWriter,
503    choice: &Choice,
504    signal: &[i64],
505    subframe_bps: u32,
506    wasted_bits: u32,
507) {
508    match choice {
509        Choice::Constant => subframe::write_constant(bw, signal[0], subframe_bps, wasted_bits),
510        Choice::Verbatim => subframe::write_verbatim(bw, signal, subframe_bps, wasted_bits),
511        Choice::Fixed {
512            order,
513            residual,
514            rice,
515        } => subframe::write_fixed(
516            bw,
517            *order,
518            &signal[..*order as usize],
519            subframe_bps,
520            wasted_bits,
521            residual,
522            rice,
523        ),
524        Choice::Lpc(c) => subframe::write_lpc(
525            bw,
526            c.order,
527            &signal[..c.order as usize],
528            &c.qlp_coeff,
529            c.precision,
530            c.shift,
531            subframe_bps,
532            wasted_bits,
533            &c.residual,
534            &c.rice,
535        ),
536    }
537}
538
539/// Mark every seek point whose target sample falls in the frame starting at
540/// `frame_first_sample` (`write_frame_`, `stream_encoder.c:2741`). The match range
541/// and the recorded `frame_samples` use this frame's *actual* sample count
542/// (`blocksize`): libFLAC reads `get_blocksize()`, which equals the configured block
543/// size for every frame except the short final one, where `finish` lowers it to the
544/// remaining sample count (`stream_encoder.c:1493`). `stream_offset` is the frame's
545/// byte offset from the first audio frame. `first` is the persistent
546/// `first_seekpoint_to_check` cursor; points are visited in (sorted) target order,
547/// and a claimed point's `sample_number` is rewritten to the frame's first sample,
548/// so several targets landing in one frame become duplicates (deduped at finish by
549/// [`metadata::seektable_sort`]).
550fn fill_seekpoints(
551    points: &mut [metadata::SeekPoint],
552    first: &mut usize,
553    frame_first_sample: u64,
554    blocksize: u64,
555    stream_offset: u64,
556) {
557    let frame_last_sample = frame_first_sample + blocksize - 1;
558    while *first < points.len() {
559        let test = points[*first].sample_number;
560        if test > frame_last_sample {
561            break; // belongs to a later frame; resume here next time
562        }
563        if test >= frame_first_sample {
564            points[*first] = metadata::SeekPoint {
565                sample_number: frame_first_sample,
566                stream_offset,
567                frame_samples: blocksize as u32,
568            };
569        }
570        // Either claimed (set above) or already passed (test < frame_first_sample);
571        // advance the cursor in both cases, as the C does.
572        *first += 1;
573    }
574}
575
576/// Encode interleaved integer PCM into FLAC audio frames (no metadata) with the
577/// given compression `config`. For stereo with `do_mid_side`, each frame picks the
578/// channel assignment with the fewest bits (or, with `loose_mid_side`, re-decides
579/// only every ~0.4 s and reuses it between). The block size is fixed except for a
580/// possibly shorter final frame.
581pub fn encode_frames(
582    interleaved: &[i32],
583    channels: u32,
584    bits_per_sample: u32,
585    sample_rate: u32,
586    blocksize: u32,
587    config: &Config,
588) -> Vec<u8> {
589    encode_frames_inner(
590        interleaved,
591        channels,
592        bits_per_sample,
593        sample_rate,
594        blocksize,
595        config,
596        None,
597        None,
598    )
599    .0
600}
601
602/// As [`encode_frames`], also returning the min/max frame size in bytes (for
603/// STREAMINFO). `min_framesize` is 0 if no frames were produced. When `seektable`
604/// is `Some`, it is a SEEKTABLE *template* (sorted target sample numbers) filled in
605/// place as frames go by, then sorted/uniquified at finish — exactly as libFLAC
606/// generates a seektable during encoding. When `frame_lengths` is `Some`, each
607/// frame's byte length is appended (so the Ogg path can split the frame stream into
608/// per-frame packets).
609#[allow(clippy::too_many_arguments)]
610fn encode_frames_inner(
611    interleaved: &[i32],
612    channels: u32,
613    bits_per_sample: u32,
614    sample_rate: u32,
615    blocksize: u32,
616    config: &Config,
617    mut seektable: Option<&mut [metadata::SeekPoint]>,
618    mut frame_lengths: Option<&mut Vec<usize>>,
619) -> (Vec<u8>, u32, u32) {
620    let ch = channels as usize;
621    assert!(ch > 0 && interleaved.len() % ch == 0, "ragged interleave");
622    let total = interleaved.len() / ch;
623    let stereo_ms = config.do_mid_side && ch == 2;
624
625    // Auto qlp precision is fixed once from the stream bps and the full block size.
626    let qlp_coeff_precision = auto_qlp_coeff_precision(bits_per_sample, blocksize);
627    // The apodization window, recomputed only when the frame block size changes
628    // (i.e. once for the full frames, again for a short final frame).
629    let window_p = config.apodization.window_p();
630    let mut window_bs = 0usize;
631    let mut window_buf: Vec<f32> = Vec::new();
632
633    // Loose mid-side: re-decide the assignment only every `loose_frames` frames
634    // (`stream_encoder.c:894`), reusing it in between.
635    let loose_frames = if config.loose_mid_side {
636        ((sample_rate as f64 * 0.4 / blocksize as f64 + 0.5) as u32).max(1)
637    } else {
638        0
639    };
640    let mut loose_count = 0u32;
641    let mut last_assignment = ChannelAssignment::Independent;
642
643    let mut out = Vec::new();
644    let mut min_framesize = u32::MAX;
645    let mut max_framesize = 0u32;
646    let mut frame_number = 0u32;
647    // Persistent SEEKTABLE cursor (`first_seekpoint_to_check`): the index of the
648    // first not-yet-resolved seek point, only ever advanced.
649    let mut first_seekpoint_to_check = 0usize;
650    let mut start = 0usize;
651    while start < total {
652        let bs = (total - start).min(blocksize as usize);
653        // Byte offset of this frame from the first audio frame (= bytes already
654        // emitted, since `out` holds only frames here) — the seek-point offset.
655        let stream_offset = out.len() as u64;
656        if let Some(points) = seektable.as_deref_mut() {
657            fill_seekpoints(
658                points,
659                &mut first_seekpoint_to_check,
660                start as u64,
661                bs as u64,
662                stream_offset,
663            );
664        }
665
666        // Per-frame Rice partition-order bounds (`process_subframes_:3163`). The
667        // C clamps min to max (`flac_min`); min is 0 so that is a no-op, and
668        // `find_best_partition_order` re-clamps min against the limited max.
669        let max_partition_order = rice::max_partition_order_from_blocksize(bs as u32)
670            .min(config.max_residual_partition_order);
671        let min_partition_order = MIN_RESIDUAL_PARTITION_ORDER;
672
673        // (Re)compute the window for this block size if LPC is enabled.
674        if config.max_lpc_order > 0 && bs > 1 && bs != window_bs {
675            window_buf = vec![0f32; bs];
676            window::tukey(&mut window_buf, window_p);
677            window_bs = bs;
678        }
679        let lpc_ctx = (config.max_lpc_order > 0 && bs > 1).then(|| LpcCtx {
680            window: &window_buf,
681            max_lpc_order: config.max_lpc_order,
682            qlp_coeff_precision,
683            apodization: config.apodization,
684        });
685
686        // Wasted-bits-shift a channel signal, then choose its best subframe.
687        // `extra_bps` is 1 for the side channel (its values span one extra bit).
688        let choose = |signal: Vec<i64>, extra_bps: u32| -> ChosenChannel {
689            let mut sig = signal;
690            // The 33-bit-side wasted-bits variant is used only for the side channel
691            // (`extra_bps == 1`) of a 32-bit stream (`get_wasted_bits_wide_`).
692            let wide = extra_bps == 1 && bits_per_sample == 32;
693            let mut wasted = get_wasted_bits(&mut sig, wide);
694            if wasted > bits_per_sample {
695                wasted = bits_per_sample;
696            }
697            let subframe_bps = bits_per_sample - wasted + extra_bps;
698            let (choice, bits) = choose_subframe(
699                &sig,
700                subframe_bps,
701                wasted,
702                bs as u32,
703                rice_parameter_limit(bits_per_sample),
704                min_partition_order,
705                max_partition_order,
706                lpc_ctx.as_ref(),
707            );
708            ChosenChannel {
709                signal: sig,
710                subframe_bps,
711                wasted_bits: wasted,
712                choice,
713                bits,
714            }
715        };
716
717        let mut frame = BitWriter::new();
718
719        if stereo_ms {
720            let left: Vec<i64> = (0..bs)
721                .map(|i| interleaved[(start + i) * ch] as i64)
722                .collect();
723            let right: Vec<i64> = (0..bs)
724                .map(|i| interleaved[(start + i) * ch + 1] as i64)
725                .collect();
726            // Mid/side from the *original* (pre-wasted-shift) L/R
727            // (`process_subframes_:3210`): side = L - R, mid = (L + R) >> 1. The i64
728            // arithmetic is exact for the 33-bit side / 32-bit mid of 32-bit input
729            // and matches the i32 path for ≤24-bit.
730            let mid_side = || {
731                let mid: Vec<i64> = (0..bs).map(|i| (left[i] + right[i]) >> 1).collect();
732                let side: Vec<i64> = (0..bs).map(|i| left[i] - right[i]).collect();
733                (mid, side)
734            };
735
736            // Between loose decision points, reuse the last assignment and encode
737            // only the channels it needs.
738            let (assignment, pair): (ChannelAssignment, [ChosenChannel; 2]) =
739                if config.loose_mid_side && loose_count != 0 {
740                    if last_assignment == ChannelAssignment::Independent {
741                        (
742                            ChannelAssignment::Independent,
743                            [choose(left, 0), choose(right, 0)],
744                        )
745                    } else {
746                        let (mid, side) = mid_side();
747                        (
748                            ChannelAssignment::MidSide,
749                            [choose(mid, 0), choose(side, 1)],
750                        )
751                    }
752                } else {
753                    let (mid, side) = mid_side();
754                    let cl = choose(left, 0);
755                    let cr = choose(right, 0);
756                    let cm = choose(mid, 0);
757                    let cs = choose(side, 1);
758                    // Smallest total wins, independent preferred on ties. Loose
759                    // decision frames consider only independent vs mid-side.
760                    let mut assignment = ChannelAssignment::Independent;
761                    let mut min_bits = cl.bits + cr.bits;
762                    let all = !config.loose_mid_side;
763                    for (enabled, bits, ca) in [
764                        (all, cl.bits + cs.bits, ChannelAssignment::LeftSide),
765                        (all, cr.bits + cs.bits, ChannelAssignment::RightSide),
766                        (true, cm.bits + cs.bits, ChannelAssignment::MidSide),
767                    ] {
768                        if enabled && bits < min_bits {
769                            min_bits = bits;
770                            assignment = ca;
771                        }
772                    }
773                    let pair = match assignment {
774                        ChannelAssignment::Independent => [cl, cr],
775                        ChannelAssignment::LeftSide => [cl, cs],
776                        ChannelAssignment::RightSide => [cs, cr],
777                        ChannelAssignment::MidSide => [cm, cs],
778                    };
779                    (assignment, pair)
780                };
781
782            write_frame_header(
783                &mut frame,
784                &FrameHeader {
785                    blocksize: bs as u32,
786                    sample_rate,
787                    channels,
788                    channel_assignment: assignment,
789                    bits_per_sample,
790                    frame_number,
791                },
792            );
793            for cc in &pair {
794                write_choice(
795                    &mut frame,
796                    &cc.choice,
797                    &cc.signal,
798                    cc.subframe_bps,
799                    cc.wasted_bits,
800                );
801            }
802
803            last_assignment = assignment;
804            if config.loose_mid_side {
805                loose_count += 1;
806                if loose_count >= loose_frames {
807                    loose_count = 0;
808                }
809            }
810        } else {
811            write_frame_header(
812                &mut frame,
813                &FrameHeader {
814                    blocksize: bs as u32,
815                    sample_rate,
816                    channels,
817                    channel_assignment: ChannelAssignment::Independent,
818                    bits_per_sample,
819                    frame_number,
820                },
821            );
822            for c in 0..ch {
823                let signal: Vec<i64> = (0..bs)
824                    .map(|i| interleaved[(start + i) * ch + c] as i64)
825                    .collect();
826                let cc = choose(signal, 0);
827                write_choice(
828                    &mut frame,
829                    &cc.choice,
830                    &cc.signal,
831                    cc.subframe_bps,
832                    cc.wasted_bits,
833                );
834            }
835        }
836
837        write_frame_footer(&mut frame);
838        let fsize = frame.as_bytes().len() as u32;
839        min_framesize = min_framesize.min(fsize);
840        max_framesize = max_framesize.max(fsize);
841        if let Some(fl) = frame_lengths.as_deref_mut() {
842            fl.push(fsize as usize);
843        }
844        out.extend_from_slice(frame.as_bytes());
845
846        start += bs;
847        frame_number += 1;
848    }
849    if min_framesize == u32::MAX {
850        min_framesize = 0;
851    }
852    // Finish: sort + uniquify the filled seektable, padding the tail with
853    // placeholders (`stream_encoder.c:2918`).
854    if let Some(points) = seektable {
855        metadata::seektable_sort(points);
856    }
857    (out, min_framesize, max_framesize)
858}
859
860/// Encode interleaved integer PCM into a complete FLAC stream: the `fLaC` marker,
861/// STREAMINFO, the given metadata `blocks` (in order), then the audio frames.
862/// `do_md5` controls whether STREAMINFO carries the audio MD5 (libFLAC's
863/// `--no-md5-sum` writes zeros). `blocks` are written after STREAMINFO exactly as
864/// supplied — pass a single [`metadata::MetadataBlock::VorbisComment`] with
865/// [`metadata::LIBFLAC_VENDOR_STRING`] to match libFLAC's default output. The last
866/// block written (STREAMINFO if `blocks` is empty) carries the `is_last` flag.
867#[allow(clippy::too_many_arguments)]
868pub fn encode(
869    interleaved: &[i32],
870    channels: u32,
871    bits_per_sample: u32,
872    sample_rate: u32,
873    blocksize: u32,
874    config: &Config,
875    do_md5: bool,
876    blocks: &[metadata::MetadataBlock],
877) -> Vec<u8> {
878    let ch = channels as usize;
879    assert!(ch > 0 && interleaved.len() % ch == 0, "ragged interleave");
880    let total_samples = (interleaved.len() / ch) as u64;
881
882    // libFLAC fills the (first) SEEKTABLE during encoding, then rewrites it; we
883    // clone the template, fill it as frames are produced, and serialize the filled
884    // copy below in place of the original block.
885    let mut filled_seektable: Option<Vec<metadata::SeekPoint>> = blocks.iter().find_map(|b| {
886        if let metadata::MetadataBlock::Seektable(pts) = b {
887            Some(pts.to_vec())
888        } else {
889            None
890        }
891    });
892
893    let (frames, min_framesize, max_framesize) = encode_frames_inner(
894        interleaved,
895        channels,
896        bits_per_sample,
897        sample_rate,
898        blocksize,
899        config,
900        filled_seektable.as_deref_mut(),
901        None,
902    );
903
904    let md5 = if do_md5 {
905        crate::md5::audio_md5(interleaved, bits_per_sample.div_ceil(8) as usize)
906    } else {
907        [0u8; 16]
908    };
909
910    let si = metadata::StreamInfo {
911        min_blocksize: blocksize,
912        max_blocksize: blocksize,
913        min_framesize,
914        max_framesize,
915        sample_rate,
916        channels,
917        bits_per_sample,
918        total_samples,
919        md5,
920    };
921
922    let mut bw = BitWriter::new();
923    bw.write_byte_block(b"fLaC");
924    metadata::write_streaminfo(&mut bw, &si, blocks.is_empty());
925    for (i, block) in blocks.iter().enumerate() {
926        let is_last = i + 1 == blocks.len();
927        match block {
928            // Substitute the filled+sorted seek points for the supplied template.
929            metadata::MetadataBlock::Seektable(_) => metadata::write_seektable(
930                &mut bw,
931                filled_seektable.as_deref().unwrap_or(&[]),
932                is_last,
933            ),
934            _ => metadata::write_block(&mut bw, block, is_last),
935        }
936    }
937    let mut out = bw.as_bytes().to_vec();
938    out.extend_from_slice(&frames);
939    out
940}
941
942/// Encode interleaved integer PCM into a complete **Ogg FLAC** stream, byte-identical
943/// to libFLAC+libogg. The native FLAC stream (STREAMINFO + `blocks` + frames) is
944/// mapped into Ogg packets and paged exactly as libFLAC's `ogg_encoder_aspect` drives
945/// libogg:
946/// - The first packet (BOS page) is `0x7F` + `"FLAC"` + mapping version `1.0` +
947///   a 2-byte header count (always 0 = "unknown") + `"fLaC"` + STREAMINFO, flushed.
948/// - Each remaining metadata block is its own flushed packet/page. **A SEEKTABLE is
949///   dropped** (libFLAC removes it for Ogg).
950/// - Each audio frame is its own packet, paged out (accumulated) with the last frame
951///   carrying EOS. Granule positions are cumulative sample counts.
952///
953/// `serial` is the Ogg logical-bitstream serial number. As with [`encode`], pass a
954/// [`metadata::MetadataBlock::VorbisComment`] with [`metadata::LIBFLAC_VENDOR_STRING`]
955/// first to match libFLAC's default output.
956#[allow(clippy::too_many_arguments)]
957pub fn encode_ogg(
958    interleaved: &[i32],
959    channels: u32,
960    bits_per_sample: u32,
961    sample_rate: u32,
962    blocksize: u32,
963    config: &Config,
964    do_md5: bool,
965    blocks: &[metadata::MetadataBlock],
966    serial: i32,
967) -> Vec<u8> {
968    let ch = channels as usize;
969    assert!(ch > 0 && interleaved.len() % ch == 0, "ragged interleave");
970    let total_samples = (interleaved.len() / ch) as u64;
971
972    // Encode the audio frames, capturing each frame's byte length so the stream can
973    // be split into per-frame Ogg packets. (Ogg drops the seektable, so none here.)
974    let mut frame_lengths: Vec<usize> = Vec::new();
975    let (frames, min_framesize, max_framesize) = encode_frames_inner(
976        interleaved,
977        channels,
978        bits_per_sample,
979        sample_rate,
980        blocksize,
981        config,
982        None,
983        Some(&mut frame_lengths),
984    );
985
986    let md5 = if do_md5 {
987        crate::md5::audio_md5(interleaved, bits_per_sample.div_ceil(8) as usize)
988    } else {
989        [0u8; 16]
990    };
991    let si = metadata::StreamInfo {
992        min_blocksize: blocksize,
993        max_blocksize: blocksize,
994        min_framesize,
995        max_framesize,
996        sample_rate,
997        channels,
998        bits_per_sample,
999        total_samples,
1000        md5,
1001    };
1002
1003    // Metadata blocks to write after STREAMINFO, with the seektable removed.
1004    let meta: Vec<&metadata::MetadataBlock> = blocks
1005        .iter()
1006        .filter(|b| !matches!(b, metadata::MetadataBlock::Seektable(_)))
1007        .collect();
1008
1009    // The BOS packet: mapping header + native "fLaC" + STREAMINFO (is_last is false
1010    // when metadata blocks follow, matching libFLAC's native ordering).
1011    let mut si_bw = BitWriter::new();
1012    metadata::write_streaminfo(&mut si_bw, &si, meta.is_empty());
1013    let mut bos = Vec::with_capacity(13 + 38);
1014    bos.push(0x7F);
1015    bos.extend_from_slice(b"FLAC");
1016    bos.push(1); // mapping version major
1017    bos.push(0); // mapping version minor
1018    bos.extend_from_slice(&[0, 0]); // header packet count: 0 = unknown
1019    bos.extend_from_slice(b"fLaC");
1020    bos.extend_from_slice(si_bw.as_bytes());
1021
1022    let mut og = ogg::OggStream::new(serial);
1023    og.packetin(&bos, false, 0);
1024    og.flush();
1025
1026    for (i, block) in meta.iter().enumerate() {
1027        let is_last = i + 1 == meta.len();
1028        let mut bw = BitWriter::new();
1029        metadata::write_block(&mut bw, block, is_last);
1030        og.packetin(bw.as_bytes(), false, 0);
1031        og.flush();
1032    }
1033
1034    let nframes = frame_lengths.len();
1035    let mut byte_off = 0usize;
1036    let mut sample_off = 0u64;
1037    for (i, &flen) in frame_lengths.iter().enumerate() {
1038        let fsamples = (blocksize as u64).min(total_samples - sample_off);
1039        sample_off += fsamples;
1040        let is_last = i + 1 == nframes;
1041        og.packetin(
1042            &frames[byte_off..byte_off + flen],
1043            is_last,
1044            sample_off as i64,
1045        );
1046        byte_off += flen;
1047        og.pageout();
1048    }
1049
1050    og.into_bytes()
1051}
1052
1053/// Audio format and compression settings for an [`Encoder`].
1054///
1055/// Build one with [`EncoderConfig::new`] (libFLAC's defaults: compression level 8,
1056/// 4096-sample blocks, MD5 on) or [`EncoderConfig::chd`] (MAME/CHD's exact
1057/// configuration), then adjust with the `with_*` methods if needed.
1058#[derive(Clone, Copy, Debug)]
1059pub struct EncoderConfig {
1060    /// Number of channels, 1–8. Stereo (2) enables mid/side decorrelation.
1061    pub channels: u32,
1062    /// Bits per sample: 8, 12, 16, 20, 24, or 32.
1063    pub bits_per_sample: u32,
1064    /// Sample rate in Hz.
1065    pub sample_rate: u32,
1066    /// Fixed block size in samples (the final frame may be shorter). libFLAC's
1067    /// default is 4096; CHD uses 2048.
1068    pub block_size: u32,
1069    /// Compression preset, 0 (fastest) … 8 (smallest); values above 8 clamp to 8.
1070    pub compression_level: u32,
1071    /// Whether STREAMINFO carries the audio MD5 signature (libFLAC's `--no-md5-sum`,
1072    /// and CHD, set this `false`).
1073    pub md5: bool,
1074}
1075
1076impl EncoderConfig {
1077    /// A config for the given format at libFLAC's defaults: compression level 8,
1078    /// 4096-sample blocks, MD5 enabled.
1079    pub fn new(channels: u32, bits_per_sample: u32, sample_rate: u32) -> Self {
1080        Self {
1081            channels,
1082            bits_per_sample,
1083            sample_rate,
1084            block_size: 4096,
1085            compression_level: 8,
1086            md5: true,
1087        }
1088    }
1089
1090    /// The exact configuration MAME/CHD uses: 2-channel / 16-bit / 44.1 kHz,
1091    /// compression level 8, MD5 **off**, and the given fixed `block_size` (CHD
1092    /// typically uses 2048). Pair with [`Encoder::encode_frames`] for the raw frame
1093    /// bytes CHD embeds.
1094    pub fn chd(block_size: u32) -> Self {
1095        Self {
1096            channels: 2,
1097            bits_per_sample: 16,
1098            sample_rate: 44_100,
1099            block_size,
1100            compression_level: 8,
1101            md5: false,
1102        }
1103    }
1104
1105    /// Set the compression level (0–8).
1106    pub fn with_compression_level(mut self, level: u32) -> Self {
1107        self.compression_level = level;
1108        self
1109    }
1110    /// Set the fixed block size in samples.
1111    pub fn with_block_size(mut self, block_size: u32) -> Self {
1112        self.block_size = block_size;
1113        self
1114    }
1115    /// Set whether STREAMINFO carries the audio MD5.
1116    pub fn with_md5(mut self, md5: bool) -> Self {
1117        self.md5 = md5;
1118        self
1119    }
1120}
1121
1122/// A one-shot FLAC encoder, byte-identical to libFLAC 1.4.3 at the same settings.
1123///
1124/// Each `encode*` method takes the **entire** interleaved signal (channel-major
1125/// within a sample — `L R L R …` for stereo, each value a sign-extended `i32`) and
1126/// returns the encoded bytes.
1127///
1128/// ```
1129/// use libflac_rs::{Encoder, EncoderConfig};
1130///
1131/// let enc = Encoder::new(EncoderConfig::new(2, 16, 44_100));
1132/// let pcm = vec![0i32; 4096 * 2]; // one block of stereo silence, interleaved
1133/// let flac = enc.encode(&pcm);    // a complete .flac file
1134/// assert_eq!(&flac[..4], b"fLaC");
1135/// ```
1136pub struct Encoder {
1137    config: EncoderConfig,
1138    inner: Config,
1139}
1140
1141impl Encoder {
1142    /// Create an encoder from `config`.
1143    pub fn new(config: EncoderConfig) -> Self {
1144        let inner = preset(config.compression_level);
1145        Self { config, inner }
1146    }
1147
1148    /// The configuration this encoder was built with.
1149    pub fn config(&self) -> &EncoderConfig {
1150        &self.config
1151    }
1152
1153    /// Encode to **raw FLAC frames** — no `fLaC` marker or metadata — the byte stream
1154    /// MAME/CHD embeds. (The `md5` setting has no effect here: MD5 lives in
1155    /// STREAMINFO, which raw frames omit.)
1156    pub fn encode_frames(&self, interleaved: &[i32]) -> Vec<u8> {
1157        encode_frames(
1158            interleaved,
1159            self.config.channels,
1160            self.config.bits_per_sample,
1161            self.config.sample_rate,
1162            self.config.block_size,
1163            &self.inner,
1164        )
1165    }
1166
1167    /// Encode to a complete `.flac` file (the `fLaC` marker, STREAMINFO, libFLAC's
1168    /// default VORBIS_COMMENT, then the frames) — byte-identical to libFLAC's default
1169    /// output. Use [`Encoder::encode_with_metadata`] to control the metadata.
1170    pub fn encode(&self, interleaved: &[i32]) -> Vec<u8> {
1171        self.encode_with_metadata(
1172            interleaved,
1173            &[metadata::MetadataBlock::VorbisComment(
1174                metadata::LIBFLAC_VENDOR_STRING,
1175            )],
1176        )
1177    }
1178
1179    /// Encode to a complete `.flac` file with the given metadata `blocks`, written
1180    /// after STREAMINFO in order (the last automatically carries the
1181    /// end-of-metadata flag). To match libFLAC's output, put a
1182    /// [`MetadataBlock::VorbisComment`](crate::MetadataBlock::VorbisComment) with
1183    /// [`LIBFLAC_VENDOR_STRING`](crate::LIBFLAC_VENDOR_STRING) first. A
1184    /// [`MetadataBlock::Seektable`](crate::MetadataBlock::Seektable) is filled in
1185    /// during encoding.
1186    pub fn encode_with_metadata(
1187        &self,
1188        interleaved: &[i32],
1189        blocks: &[metadata::MetadataBlock],
1190    ) -> Vec<u8> {
1191        encode(
1192            interleaved,
1193            self.config.channels,
1194            self.config.bits_per_sample,
1195            self.config.sample_rate,
1196            self.config.block_size,
1197            &self.inner,
1198            self.config.md5,
1199            blocks,
1200        )
1201    }
1202
1203    /// Encode to a complete **Ogg FLAC** stream with the given Ogg logical-bitstream
1204    /// `serial` number. libFLAC's default VORBIS_COMMENT is included; any SEEKTABLE is
1205    /// dropped (as libFLAC does for Ogg).
1206    pub fn encode_ogg(&self, interleaved: &[i32], serial: i32) -> Vec<u8> {
1207        encode_ogg(
1208            interleaved,
1209            self.config.channels,
1210            self.config.bits_per_sample,
1211            self.config.sample_rate,
1212            self.config.block_size,
1213            &self.inner,
1214            self.config.md5,
1215            &[metadata::MetadataBlock::VorbisComment(
1216                metadata::LIBFLAC_VENDOR_STRING,
1217            )],
1218            serial,
1219        )
1220    }
1221}