Skip to content

v0.0.7

Latest

Choose a tag to compare

@MagicalTux MagicalTux released this 15 Jun 05:14
· 14 commits to master since this release
b8fcdb0

Other

  • ac4 round 306 — encoder-side aspx_hfgen_iwc_1ch/_2ch writers
  • ac4 r299: multi-envelope ASPX body writers (num_env > 1) consuming the r292 packer
  • ac4 round 292 — encoder-side TIME-direction ASPX envelope DPCM packing
  • ac4 round 285 — real per-band β₃ for the 5_X ASPX_ACPL_3 encoder
  • ac4 round 279 — decision-driven SAP-coded ASPX_ACPL_1 residual layer
  • ac4 round 271 — SAP-coded alpha_q decision driver (select_alpha_q_for_pair)
  • ac4 r263: build_chparam_info_none + select_ms_used_for_pair
  • ac4 r260: encoder-side ChparamInfo builders — duals of extract_sap_abcd
  • ac4 r257: SAP-aware ASPX_ACPL_1 residual-layer writer
  • drop release-plz.toml — use release-plz defaults across the workspace
  • ac4 r246: encoder-side Table-181 SAP residual extractor
  • ac4 r243: encoder-side chparam_info() / sap_data() builders
  • ac4 r240: encoder-side HF QMF energy aggregator (dual of Pseudocodes 90 + 91)
  • ac4 r234: encoder-side ASPX envelope extractor (inverse of P82/83 + P80/81 DPCM)
  • ac4 r226: write_aspx_data_{1,2}ch_real_envelope() builders
  • ac4 r219: ASPX envelope value-emitting helpers (sig/noise F0/DF/DT)
  • ac4 r215: real per-band γ₁ / γ₂ / γ₃ / γ₄ extraction in 5_X ASPX_ACPL_3 encoder
  • ac4 r208: real per-band γ5 / γ6 extraction in 5_X ASPX_ACPL_3 encoder
  • ac4 r202: real per-band α + β extraction in 7.0/7.1 ASPX_ACPL_2 encoder
  • ac4 r196: real per-band α1/α2 extraction in 5_X ASPX_ACPL_3 encoder

Added

  • Round 306 — encoder-side aspx_hfgen_iwc_1ch() /
    aspx_hfgen_iwc_2ch() writers (crate::encoder_acpl3).
    The exact
    duals of the decoder's aspx::parse_aspx_hfgen_iwc_1ch /
    parse_aspx_hfgen_iwc_2ch (ETSI TS 103 190-1 §4.2.12.6 / §4.2.12.7,
    Tables 55 / 56). Until now every encoder body writer emitted this
    HF-generation / interleaved-waveform-coding element as the all-zero
    compact form (aspx_tna_mode[*] = 0, all three presence bits 0),
    even though the decoder fully parses real inverse-filtering modes,
    additive harmonics (add_harmonic), frequency-interleaved coding
    (fic_used_in_sfb) and time-interleaved coding (tic_used_in_slot).
    New encoder_acpl3::write_aspx_hfgen_iwc_1ch /
    write_aspx_hfgen_iwc_2ch take real per-SBG tna_mode (2 b, masked
    to 0..=3) plus per-SBG / per-timeslot flag vectors via the public
    encoder_acpl3::AspxHfgenIwc1ChPayload /
    encoder_acpl3::AspxHfgenIwc2ChPayload payloads, and auto-derive
    every gate from the payload (*_present / *_left / *_right set
    iff the slice has an active flag in range; the 2ch TIC path uses the
    compact aspx_tic_copy = 1 form when both channels carry the same
    active pattern). Under aspx_balance = 1 only channel-0 tna_mode
    is written (decoder mirrors it); short caller slices zero-pad. The
    existing write_aspx_data_1ch_minimal HFGEN block is refactored to
    route through the new 1ch writer with a default payload — output
    stays byte-identical. Eight integration tests in
    tests/round306_aspx_hfgen_iwc_writers.rs pin the bit-exact
    round-trip through the decoder parsers (all-zero compact form, real
    flags, padding + masking, balance-mirror, distinct-tna, TIC-copy,
    TIC-right-only, full multi-field stress).

  • Round 292 — encoder-side TIME-direction ASPX envelope DPCM packing
    (crate::encoder_acpl3).
    The dual of the direction_time == true
    branch of the decoder's aspx::delta_decode_sig /
    aspx::delta_decode_noise (ETSI TS 103 190-1 §5.7.6.3.4 Pseudocode
    80 / 81). The prior round-219/226/234/240 envelope-coding chain only
    emitted the FREQ direction (freq_dpcm_encode_qscf); the decoder also
    accepts a per-envelope direction flag and walks a TIME branch
    reconstructing qscf[sbg][atsg] = prev[sbg] + delta·values[sbg]
    (with prev the previous envelope's row, or qscf_prev_last for the
    first envelope). New encoder_acpl3::time_dpcm_encode_qscf inverts it
    exactly (values[sbg] = (qscf[sbg] − prev[sbg]) / delta), with
    zero-extend-short-prev and ±1-step semantics matching the decoder
    (delta = 0 treated as 1 for totality). New
    encoder_acpl3::dpcm_encode_qscf_envelopes packs a full
    qscf[sbg][atsg] matrix into per-envelope
    encoder_acpl3::AspxEncodedEnvelope { values, direction_time } rows,
    selecting the cheaper direction per envelope by minimising
    Σ|values[sbg]| (FREQ wins ties; force_freq reproduces the legacy
    single-direction scaffold). Twelve integration tests
    (tests/round292_aspx_time_direction_dpcm.rs) pin the bit-exact
    round-trip through both delta_decode_sig and delta_decode_noise,
    step/totality edges, short-prev zero-extension, the min-L1 policy,
    force_freq parity with freq_dpcm_encode_qscf, and empty inputs.
    Total tests 941 (was 929).

  • Round 285 — real per-parameter-band β₃ extraction for the 5_X
    SIMPLE/ASPX_ACPL_3 encoder (crate::encoder_acpl3 +
    crate::encoder_ims).
    Closes the round-215 "β₃ stays at the
    round-95 zero-delta scaffold" deferral. Per ETSI TS 103 190-1
    §5.7.7.6.2 Pseudocode 118 steps 8-10, β₃ is the gain on the third
    decorrelator output y₂; step 10 + step 11 give the centre channel
    a wet contribution C_wet = −√2 · 0.5 · β₃ · y₂ carrying energy
    0.5 · β₃² · E[y₂²]. y₂ is decoder-side decorrelator state and
    unobservable at encode time, but its energy is not: the
    decorrelator + ducker chain is energy-preserving in steady state,
    so E[y₂²] ≈ E[v₃²] with the third-Transform drive
    v₃ = (γ₁+γ₃+γ₅)·x0in + (γ₂+γ₄+γ₆)·x1in (Pseudocode 118 step 2)
    fully determined by the carrier spectra and the quantised γ matrix
    the encoder is already emitting. New
    encoder_acpl3::extract_beta3_q_per_band_centre_residual energy-
    matches that wet contribution against the per-band least-squares
    remainder of the round-208 centre dry fit
    E_res = Σ (C − K·(γ₅·L + γ₆·R))² (K = 1 + √(1/2), using the
    quantised γ₅ / γ₆ the decoder will apply), giving the encoder
    decision β₃ = √(2 · E_res / E[v₃²]) — a non-negative magnitude,
    quantised per §5.7.7.7 Table 207 (beta3_q = round(β₃ / beta3_delta)
    with beta3_delta = 0.125 Fine / 0.25 Coarse and the symmetric
    ±cb_off clamp at ±8 / ±4 — half the BETA3 F0 codebook length
    per the staged ETSI table file §A.3 Tables A.46 / A.47). New BETA3
    value writers write_acpl_beta3_f0_value / write_acpl_beta3_df_value
    mirror the round-208 γ writers (symbol_index = q + cb_off
    addressing); a new full acpl_data_2ch() emitter
    write_acpl_data_2ch_real_alpha_beta_full_gamma_beta3 lifts the β₃
    entropy layer from zero-delta scaffold to real FREQ-direction DPCM
    codewords. New public builder
    encoder_acpl3::build_5_x_acpl3_body_from_pcm_spectra_real_alpha_beta_full_gamma_beta3
    is a drop-in over the round-215 full-γ builder with an extra
    beta3_scale decision knob, and new caller-facing entry points
    encoder_ims::Ac4ImsEncoder::encode_frame_pcm_5_0_acpl3_real_alpha_beta_full_gamma_beta3
    / _5_1_ accept [L, R, C, Ls, Rs] / [L, R, C, Ls, Rs, LFE] PCM.
    beta3_scale = 0.0 reproduces the round-215 byte stream exactly
    (the all-zero β₃ row emits exactly the zero-delta scaffold
    codewords). Four new unit tests pin the Table-207 quant grid +
    clamp, the BETA3 F0/DF writer round-trip through
    parse_acpl_huff_data + Pseudocode-121 accumulation, the
    zero-residual ⇒ β₃ = 0 / uncaptured-centre ⇒ β₃ > 0 decision
    split, and builder byte-equality at beta3_scale = 0. Six
    integration tests (tests/round285_5_x_acpl3_real_beta3.rs) pin
    5.0 → 5-channel and 5.1 → 6-channel decoder round-trips, the
    decode-side recovery of the exact per-band beta3_q row through
    parse_5x_audio_data_outer + differential_decode, IMS
    byte-equality with the round-215 entry at beta3_scale = 0,
    wire-liveness of the β₃ layer for an uncaptured centre, and
    bit-determinism. Total tests 919 → 929.

  • Round 279 — decision-driven SAP-coded ASPX_ACPL_1 residual layer
    (crate::encoder_acpl3 + crate::encoder_ims).
    Wires the
    round-271 select_alpha_q_for_pair decision driver into the encoder
    proper, per ETSI TS 103 190-1 §5.3.4.3.2 / Table 181 + §5.3.2
    Pseudocode 59. New
    encoder_acpl3::select_acpl1_residual_chparam_pair runs the
    least-squares alpha_q decision per target (L, Ls) / (R, Rs)
    pair over the residual layer's single-window-group
    [max_sfb_master] layout — the residual layer's two
    chparam_info() payloads drive two independent 2x2 SAP systems
    mapping the transmitted (sSMP_A, sSMP_3) / (sSMP_B, sSMP_4)
    tracks to the preliminary front/surround pairs — and materialises
    the rows via the round-260
    build_chparam_info_sap_data_from_alpha_q builder, falling back to
    the header-only SapMode::None row when no band raises
    sap_coeff_used. The picked alpha_q is clamped to [-30, +30] so
    the pair-major DPCM deltas Pseudocode 59 accumulates stay within the
    HCB_SCALEFAC-codable [-60, +60] range on a worst-case sign flip.
    New encoder_acpl3::build_5_x_acpl1_body_from_pcm_spectra_sap_auto
    (+ caller-facing
    encoder_ims::Ac4ImsEncoder::encode_frame_pcm_5_0_acpl1_sap /
    _with_max_sfb) additionally closes the round-257 deferred carrier
    side: the two_channel_data() payload now carries the Table-181
    matrix-input carriers (sSMP_A, sSMP_B) recovered through
    invert_sap_table_181 — on a SAP-coded band the transmitted pair is
    (M, S − g·M) (mid + side prediction residual) rather than the raw
    L/R preliminaries the round-257 builder still emitted, so the
    decoder's apply_sap_table_181 forward mix reproduces the requested
    (L, R, Ls, Rs) preliminaries exactly (up to sf_data quantisation).
    Measured: for Ls = κ·L correlated surround the optimal projection
    g* = (1 − κ) / (1 + κ) collapses the transmitted residual to
    near-silence — SAP residual energy < 5 % (unit, synthetic spectra) /
    < 10 % (full PCM → MDCT → encode → decode integration) of the
    identity path's raw-Ls residual — while a no-benefit input
    (Ls = L ⇒ zero side energy ⇒ g* = 0) encodes bit-for-bit
    identical
    to the round-103 identity path (strict-superset
    invariant). Five new unit tests in src/encoder_acpl3.rs pin the
    selector's per-band (1.7, 1, 0.3, −1) extraction for κ = 0.2,
    the SapMode::None fallback on equal pairs, the ±30 clamp on a
    near-anti-correlated pair, the identity byte-equality, and the
    bit-stream round trip (decoder walker recovers sap_mode = 3 rows;
    forward Table-181 mix matches all four preliminaries within 20 %
    relative L2; residual energy < 5 % of raw surround energy). Four new
    integration tests in tests/round279_5_x_acpl1_sap_auto.rs cover
    the 5-channel AudioFrame shape, the recovered SAP rows + residual
    collapse vs the identity encoder on the same tone fixture, the
    no-benefit byte-equality through the full encoder entry point, and
    sequence-counter advancement. Total lib tests 689 (was 684);
    integration suites +4.

  • Round 271 — alpha_q decision driver select_alpha_q_for_pair
    (crate::asf).
    The SAP-coded (SapMode::SapData) analogue of the
    round-263 select_ms_used_for_pair — completes the encoder decision
    surface for the third non-reserved chparam_info() arm. Given the
    target stereo MDCT spectra (L, R) it picks the per-(group, sfb)
    alpha_q[g][sfb] index and the matching sap_coeff_used[g][sfb]
    flag per ETSI TS 103 190-1 §5.3.2 Pseudocode 59 + §5.3.3.2. The
    decoder reconstructs the output pair from the transmitted tracks via
    the SAP matrix (a, b, c, d) = (1 + g, 1, 1 - g, -1), g = alpha_q · 0.1; inverting (det = -2) gives the tracks the encoder must
    transmit: I_0 = M = (L + R) / 2 and I_1 = S − g·M with S = (L − R) / 2. SAP coding is therefore a one-tap prediction of the side
    track from the mid; the g that minimises the transmitted residual
    energy Σ (S[k] − g·M[k])² per parameter band is the least-squares
    projection g* = ⟨S, M⟩ / ⟨M, M⟩, quantised by alpha_q = round(10 · g*) and clamped to the HCB_SCALEFAC-codable range [-60, +60]
    (the offset of 60 is applied by write_sap_data, not the driver).
    sap_coeff_used is raised only when the quantised index is non-zero
    (a pure-mid band, ⟨S, M⟩ == 0, and a zero-mid-energy band both
    clear the flag so no SAP bit is spent where prediction offers no
    benefit). The decision is taken on the even (pair-leading) sfb of
    each (sfb, sfb+1) pair and copied to the odd partner, matching the
    pair-major flag-copy semantics of Pseudocode 59 and
    build_chparam_info_sap_data_from_alpha_q. New public type alias
    crate::asf::SapAlphaDecision for the (alpha_q, sap_coeff_used)
    matrix pair. Five new unit tests in src/asf.rs pin: the
    least-squares projection (S = M → alpha_q = +10, S = -M → alpha_q = -10, with odd-partner inheritance); pure-mid and
    zero-energy bands clear the flag; round-trip through
    build_chparam_info_sap_data_from_alpha_q + extract_sap_abcd
    reproduces the (2, 1, 0, -1) matrix on picked bands and identity
    on cleared bands; saturation to alpha_q = 60 for g* ≫ 6;
    multi-group independence. The returned matrices plug directly into
    the round-260 build_chparam_info_sap_data_from_alpha_q builder,
    closing the encoder path from per-group L/R MDCT spectra to a
    fully-populated SapMode::SapData ChparamInfo. Total lib tests
    684 (was 679); integration suites unchanged.

  • Round 263 — build_chparam_info_none + select_ms_used_for_pair
    encoder helpers (crate::asf).
    Completes the
    build_chparam_info_* family with the trivial third arm
    (SapMode::None, header-only emission whose extract_sap_abcd
    reproduces identity per-sfb across any per-group bound), plus a
    per-(group, sfb) M/S-vs-L/R decision driver
    (select_ms_used_for_pair) that picks ms_used[g][sfb] per band
    using the standard joint-stereo concentration criterion:
    min(E_M', E_S') < min(E_L, E_R) over the per-band MDCT bins
    [sfb_offset[sfb], sfb_offset[sfb+1]). For a correlated pair, M'
    carries the signal and S' vanishes (min_ms = 0); for an
    uncorrelated or anti-correlated pair, M' and S' both sit near
    (E_L + E_R) / 4. Ties (zero-energy bands, no concentration
    benefit) resolve to false so the encoder doesn't spend a
    ms_used bit when joint coding offers no concentration. The
    returned Vec<Vec<bool>> plugs directly into
    build_chparam_info_ms_used and the result round-trips through
    extract_sap_abcd to the per-sfb (1, 1, 1, -1) matrix on picked
    bands and identity on the rest. Five new unit tests in
    src/asf.rs cover: SapMode::None builder extract + bit-stream
    round-trip; per-band correlated / anti-correlated / one-sided /
    zero-energy decision discrimination; round-trip through
    build_chparam_info_ms_used + extract_sap_abcd; respect of the
    per-group max_sfb bound; multi-group independence. Total lib
    tests 679 (was 674); integration suites unchanged.

  • Round 260 — encoder-side ChparamInfo builders
    (crate::asf::build_chparam_info_ms_used +
    crate::asf::build_chparam_info_sap_data_from_alpha_q).
    Encoder-
    side duals of [crate::asf::extract_sap_abcd] (§5.3.4.3.2 /
    Pseudocode 59) for the two non-trivial SapMode arms.

    • build_chparam_info_ms_used wraps a per-(group, sfb) ms_used
      flag matrix into a ChparamInfo with sap_mode = 1; feeding
      the result into extract_sap_abcd reproduces the per-sfb
      (1, 1, 1, -1) vs identity (1, 0, 0, 1) mix the input
      describes, and a write_chparam_infoparse_chparam_info
      round-trip recovers the same row.
    • build_chparam_info_sap_data_from_alpha_q is the real
      workhorse: starting from per-(group, sfb) alpha_q indices
      (range [-60, +60] — the HCB_SCALEFAC raw-symbol offset of 60
      is applied by the writer, not the builder) plus per-pair
      sap_coeff_used flags, it computes the pair-major DPCM
      dpcm_alpha_q[g][sfb] deltas Pseudocode 59 accumulates back
      into alpha_q[g][sfb]. Odd sfbs leave the dpcm slot at zero
      (decoder inherits from the pair-mate); even sfbs compute
      cur - prev with the code_delta policy mirrored exactly
      from extract_sap_abcdcode_delta == 1 when g > 0,
      max_sfb_per_group[g] == max_sfb_per_group[g-1], and the
      caller-supplied delta_code_time is set, with the reference
      being alpha_q[g-1][sfb]; otherwise the reference is
      alpha_q[g][sfb-2] for sfb > 0 and zero for sfb == 0. The
      fully-uniform "all set" matrix is detected and sap_coeff_all
      is raised so the bitstream elides the per-pair flag array.
      delta_code_time is normalised to false on single-group
      payloads (Table 48 doesn't transmit the bit there).
    • Round-trip guarantees pinned by five new unit tests in
      src/asf.rs: extract_sap_abcd reproduces the original
      alpha_q row on set bands and identity on cleared bands
      (build_chparam_info_sap_data_pair_major_round_trip +
      ..._unused_bands_pass_through); the cross-group
      delta_code_time path delivers the expected dpcm_alpha_q
      deltas (..._delta_code_time_cross_group); single-group
      delta_code_time = true input is normalised to false on
      emit (..._single_group_drops_delta_code_time); and
      write_chparam_infoparse_chparam_info recovers the same
      SAP body which extracts to the original alpha_q
      (..._round_trips_through_bitstream).
    • Slots into the round-257 SAP-aware residual-layer writer: an
      IMS encoder that runs a psychoacoustic decision per
      parameter-band (M/S vs. alpha-driven SAP joint stereo) can now
      materialise the ChparamInfo pair from its decision matrix
      instead of hand-crafting the inner SapData body — the same
      bytes the decoder's parse_chparam_info walks back into the
      apply_sap_table_181 pipeline. Total lib tests 674 (was 667);
      integration suites unchanged.
  • Round 257 — SAP-aware ASPX_ACPL_1 residual-layer writer
    (write_acpl_1_residual_layer_sap + body-builder wrapper
    build_5_x_acpl1_body_from_pcm_spectra_sap).
    Pairs the round-246
    Table-181 inverse with the existing round-243
    [crate::encoder_asf::write_chparam_info] emitter so the IMS
    encoder's §4.2.6.6 Table-25 case ASPX_ACPL_1: residual layer can
    now express any of the three SAP coefficient families produced by
    [crate::asf::extract_sap_abcd] — identity (sap_mode = 0), M/S
    (sap_mode = 1) and SAP-coded alpha_q (sap_mode = 3) — rather
    than being hard-pinned to the identity row by the round-103
    [write_acpl_1_residual_layer].

    • The new private helper write_acpl_1_residual_layer_sap takes
      (coeffs_l, coeffs_r, coeffs_ls, coeffs_rs) preliminary
      spectra plus an Option<&[ChparamInfo; 2]> and (1) emits the
      chparam_info() pair via write_chparam_info with
      max_sfb_per_group = [max_sfb_master], (2) recovers the
      joint-MDCT residual (sSMP,3, sSMP,4) via
      [crate::asf::invert_sap_table_181] driven by the same chparam
      pair, and (3) writes the two sf_data(ASF) bodies for the
      recovered residual spectra bounded by max_sfb_master. When
      chparam_pair = None (or both rows carry sap_mode = 0) the
      body is bit-for-bit equivalent to
      write_acpl_1_residual_layer(... coeffs_ls, coeffs_rs) — the
      identity-row inverse reduces to s3 = ls, s4 = rs. The inverse's
      surround-silent convention past max_sfb_master is preserved.
    • The new public body builder
      build_5_x_acpl1_body_from_pcm_spectra_sap mirrors the round-103
      build_5_x_acpl1_body_from_pcm_spectra API with the extra
      chparam_pair: Option<&[ChparamInfo; 2]> slot wedged in between
      the surround spectra and the ASPX config. The legacy
      identity-only builder is unchanged.
    • Five new tests in encoder_acpl3::tests:
      write_acpl_1_residual_layer_sap_none_matches_legacy pins the
      bit-equivalence of the SAP-aware path with chparam_pair = None
      against the legacy emitter on identical Ls/Rs preliminaries;
      write_acpl_1_residual_layer_sap_identity_explicit_matches_default
      pins explicit identity rows == default None;
      write_acpl_1_residual_layer_sap_ms_row_roundtrips_through_decoder
      feeds the body through parse_chparam_info and asserts the
      decoder recovers sap_mode = 1 with the right ms_used rows
      on both chparam slots; build_5_x_acpl1_body_sap_none_matches_legacy
      is the body-builder analogue of the bit-equivalence test;
      build_5_x_acpl1_body_sap_ms_decoder_recovers_chparam feeds the
      full body through parse_5x_audio_data_outer and asserts
      tools.acpl_1_residual_chparam[0..1] recover the original
      chparam pair with all max_sfb_master ms_used bands present.
      Total lib tests 667 (was 662); existing integration suites
      remain green.
    • The downstream decoder pipeline that consumes this is already
      wired up: round-30 decoder.rs (lines 2661-2705) reads the
      persisted tools.acpl_1_residual_chparam and feeds it through
      apply_sap_table_181 to re-mix the L/R/Ls/Rs preliminary
      spectra before IMDCT, so an encoder building a body via the
      new SAP-aware path produces a stream that round-trips through
      the existing decoder without further changes.
  • Round 246 — encoder-side Table-181 SAP residual extractor
    (invert_sap_table_181, dual of apply_sap_table_181).
    An IMS
    encoder that wants to populate the §4.2.6.6 ASPX_ACPL_1 residual
    layer (Table 25 row case ASPX_ACPL_1:, two trailing
    sf_data(ASF) bodies carrying sSMP,3 / sSMP,4) now has a
    closed-form 2x2-per-sfb inverse of the §5.3.4.3.2 / Table 181
    first-stage SAP matrix that recovers the joint-MDCT preliminary
    spectra (sSMP_A, sSMP_B, sSMP_3, sSMP_4) from a target
    (L, R, Ls, Rs) preliminary set and a chparam_info() pair.

    • [crate::asf::invert_sap_table_181] / new public type alias
      [crate::asf::SapTable181EncodeOutput]. Inversion splits the
      Table-181 5x5 matrix into the two independent 2x2 sub-systems
      (L, Ls)(A, s3) driven by chparam_pair[0] and
      (R, Rs)(B, s4) driven by chparam_pair[1]. Per sfb the
      inverse uses det = a*d - b*c and the closed-form
      [[d, -b], [-c, a]] / det.
    • For the three SAP coefficient families produced by
      [crate::asf::extract_sap_abcd] the determinant is always
      non-singular: identity row gives det = 1, M/S row
      (1, 1, 1, -1) gives det = -2, and the SAP-coded row
      (1 + g, 1, 1 - g, -1) with g = alpha_q * 0.1 also gives
      det = -2. The implementation tolerates a hypothetical
      det == 0 band (e.g. a future spec extension) by emitting
      silence for that band instead of panicking, mirroring the
      forward path's graceful-degradation convention.
    • Outside the SAP-coded extent (bins past
      sfb_offset[max_sfb_master]) the forward pass leaves the front
      pair at (L, R) = (A, B) and zeros the surround pair; the
      inverse mirrors this — A = L, B = R, s3 = s4 = 0 — so
      the round-trip is symmetric at the band boundary. Returns
      None when the transform_length has no entry in
      sfb_offset_48, matching the forward path's failure mode.
    • Five new unit tests in src/asf.rs cover identity-row
      inverse, M/S-row inverse, forward-then-inverse round-trip on
      both the identity and M/S rows, and the unsupported-tl None
      return. All 5 pass; the existing crate test suite remains
      green (662 lib tests pass at this commit).
  • Round 243 — encoder-side chparam_info() / sap_data() builders
    (dual of parse_chparam_info / parse_sap_data, Table 47 / 48).

    Adds a reusable encoder helper covering all four sap_mode codes —
    the parser's complement for §4.2.10.1 Table 47 (chparam_info())
    and §4.2.10.2 Table 48 (sap_data()). Until this round the
    encoder's six chparam-emission sites in encoder_asf.rs open-coded
    bw.write_u32(0, 2) for sap_mode = 0 (identity SAP); now there
    is a single builder that handles sap_mode = 0 (header-only),
    sap_mode = 1 (header + per-(group, sfb) ms_used[g][sfb] bit
    array), sap_mode = 2 (reserved; header-only, mirroring the
    parser's accept-and-skip behaviour) and sap_mode = 3 (full
    sap_data() body — sap_coeff_all bit, per-pair flag array when
    sap_coeff_all = 0, delta_code_time when num_window_groups != 1,
    per-pair HCB_SCALEFAC-coded dpcm_alpha_q deltas).

    • [crate::encoder_asf::write_chparam_info] — emits the 2-bit
      sap_mode selector and dispatches to the matching payload
      branch. Half-built ChparamInfo inputs (rows shorter than
      max_sfb_per_group) zero-fill the missing entries so the writer
      stays total. A sap_mode = 3 input with sap_data = None
      emits a SapData::default() body that the parser walks
      successfully.
    • [crate::encoder_asf::write_sap_data] — emits the sap_coeff_all
      bit, the per-pair flag array (skipped when sap_coeff_all = 1),
      the conditional delta_code_time bit and the per-pair DPCM
      deltas. The DPCM map is the same delta + 60 → HCB_SCALEFAC index the round-49 [crate::encoder_asf::write_scalefac_data]
      uses, with the same [0, 120] clamp policy.
    • Round-trip is bit-exact with [crate::asf::parse_chparam_info]
      and [crate::asf::parse_sap_data] across sap_mode in {0, 1, 2, 3}, including: single- and multi-group ms_used payloads;
      sap_coeff_all = 1 single-group and sap_coeff_all = 0
      partial-pair multi-group bodies with delta_code_time = 1;
      the parser's pair-flag copy semantic (one bit drives both halves
      of (sfb, sfb+1)); asymmetric pair-flag input rows; and the
      full [-60, +60] DPCM delta range. Out-of-range deltas clamp at
      the codebook boundary (±60), matching the existing scale-factor
      writer's policy.
    • Thirteen integration tests in
      tests/round243_chparam_info_writer.rs pin: sap_mode = 0 emits
      exactly 2 bits as a header-only element; sap_mode = 2 (reserved)
      is round-trip stable as a header-only emission; sap_mode = 1
      single-group ms_used recovers entry-for-entry; sap_mode = 1
      multi-group with 3 groups of (3, 4, 1) bands recovers the full
      matrix; missing ms_used rows zero-fill on the wire; sap_mode = 3 sap_coeff_all body recovers the DPCM deltas at even-sfb
      pair starts; sap_mode = 3 partial-pair body with
      sap_coeff_all = 0 recovers both the flag array and the
      selectively-emitted DPCM entries; sap_mode = 3 multi-group
      body with delta_code_time = 1 recovers across two groups;
      sap_mode = 3 with sap_data = None emits a default body that
      parses as a sap_coeff_all = 0 all-false row; out-of-range DPCM
      deltas clamp to ±60; a full sweep of every legal delta in [-60, +60] round-trips exactly; sap_mode = 0 drops a populated
      ms_used / sap_data payload on emission; in-memory sap_mode
      values with high bits set are masked to the on-wire 2-bit field.
    • Total tests 883 (was 870). The encoder now has a single reusable
      chparam-emission helper covering every legal sap_mode, ready
      for the §4.2.10 SAP-mode decisioning work (M/S vs. independent
      vs. joint-MDCT) to feed real per-band ms_used[] / per-pair
      DPCM arrays into the existing 5_X / 7_X channel-element walkers
      in place of today's hard-coded identity-SAP literals.
  • Round 240 — encoder-side HF QMF energy aggregator (dual of
    Pseudocodes 90 + 91).
    Closes the first half of the round-234
    remaining-work note by landing the per-(sbg, atsg) energy
    aggregator that converts an HF QMF matrix into the per-sbg
    scf vector the round-234 envelope-index extractor consumes —
    completing the encoder's q_high → scf → qscf → DPCM → on-wire bytes chain for real ASPX envelope coding.

    • [crate::encoder_acpl3::aggregate_qmf_to_sbg_atsg] — aggregate
      an HF QMF matrix q_high (shape [absolute_sb][ts]) into a
      [sbg][atsg] matrix of average squared magnitudes per
      Pseudocode 90's per-subband energy reduction grouped by
      Pseudocode 91's SBG borders. Tolerates QMF rows shorter than
      tsz (entries past the bounds contribute zero), zero-span ATS
      intervals and zero-span band groups (return 0.0), and
      sbg_borders[i] < sbx (clamps upward to sbx so callers can
      pass spec-shaped absolute borders verbatim).
    • [crate::encoder_acpl3::extract_aspx_sig_envelope_scf_from_qmf]
      / [crate::encoder_acpl3::extract_aspx_noise_envelope_scf_from_qmf]
      — per-side helpers that pick the leading envelope (atsg = 0)
      column of the aggregator output, producing a per-sbg Vec<f32>
      ready to feed the round-234 envelope-index extractor.
    • New public type
      [crate::encoder_acpl3::AspxQmfEnvelopeChannel] — { q_high: &[Vec<(f32, f32)>], sbg_sig_borders: &[u32], sbg_noise_borders: &[u32] } per-channel bundle consumed by the
      QMF-driven envelope builder.
    • [crate::encoder_acpl3::build_aspx_real_envelope_channel_from_qmf]
      — convenience builder that runs the QMF aggregator + the round-234
      extract_aspx_*_envelope_indices extractors + the round-234
      build_aspx_real_envelope_channel builder end-to-end and returns
      owned (sig, noise) Vec<i32> ready to drop into the round-226
      AspxRealEnvelopeChannel { sig: &[i32], noise: &[i32] } slot.
    • Fourteen integration tests in
      tests/round240_aspx_qmf_energy_aggregator.rs pin: constant-
      energy aggregation matches the per-cell mean; per-ATSG
      partitioning recovers a [1.0, 9.0] split; per-SBG partitioning
      recovers a [1.0, 16.0] split; sub-sbx borders clamp upward;
      empty SBG / ATSG borders return empty matrices; zero-span ATSG
      cells return 0.0; the per-side helpers emit per-sbg vectors
      mirroring the aggregator; the QMF-driven convenience builder
      matches the manual aggregator + extractor + builder chain
      entry-for-entry; an integer-quant-grid input (scf = 64 and
      128 for Fine signal) hits the expected [F0 = 0, DF₁ = 2]
      DPCM payload; short QMF rows contribute partial energy without
      panicking; the QMF-driven builder is deterministic across
      repeated invocations; different QMF inputs produce different
      DPCM payloads. Total tests 870 (was 856).
    • Refs ETSI TS 103 190-1 §5.7.6.4.2.1 Pseudocodes 90 + 91.
  • Round 234 — encoder-side ASPX envelope extractor (inverse of
    Pseudocodes 80, 81, 82, 83).
    Closes the round-226 deferral by
    landing the per-(sbg, env) envelope-index extractor that inverts
    Pseudocode 82's scf = n_subbands · 2^(qscf/a) reconstruction and
    Pseudocode 83's scf_noise = 2^(6 − qscf_noise) reconstruction so
    the round-226 write_aspx_data_{1,2}ch_real_envelope builders can
    be chained with caller-supplied envelope-energy scale factors.

    • [crate::encoder_acpl3::quantize_sig_scf] — scf → qscf for one
      signal-envelope band per Pseudocode 82. qmode_env = Fine
      a = 2 (1.5 dB step), Coarsea = 1 (3 dB step);
      num_qmf_subbands mirrors the dequantizer's 64. Non-positive
      scf clamps to a finite quant index instead of producing
      -inf so the spec's scf[0] = scf[1] carry-through path and
      callers passing 0 for silent bands stay well-defined.
    • [crate::encoder_acpl3::quantize_noise_scf] — scf → qscf for
      one noise-envelope band per Pseudocode 83 (qscf = round(6 − log2(scf))).
    • [crate::encoder_acpl3::freq_dpcm_encode_qscf] — invert the
      FREQ-direction DPCM accumulator qscf[sbg] = sum(values[0..=sbg])
      of Pseudocode 80 / 81. Returns [F0, DF₁, DF₂, …] where
      F0 = qscf[0], DF[sbg ≥ 1] = qscf[sbg] − qscf[sbg − 1]. Empty
      input returns an empty vector.
    • [crate::encoder_acpl3::extract_aspx_sig_envelope_indices] /
      [crate::encoder_acpl3::extract_aspx_noise_envelope_indices] —
      per-channel compositions scf[] → qscf[] → [F0, DF₁, …] ready
      for the round-219 value-emitting helpers + the round-226 builder
      pair.
    • New public type
      [crate::encoder_acpl3::AspxEnvelopeScfChannel] — { sig: &[f32], noise: &[f32] } per-channel envelope-energy payload.
    • [crate::encoder_acpl3::build_aspx_real_envelope_channel] —
      convenience wrapper that runs both extractors and returns owned
      (sig, noise) Vec<i32> pairs callers wire into
      AspxRealEnvelopeChannel by slice reference.
    • Round-trip property: feeding caller scf slices through the
      extractor, then the round-226 builder, then re-parsing the body
      through parse_aspx_ec_data + the decoder's delta_decode_sig /
      delta_decode_noise + dequantize_sig_scf /
      dequantize_noise_scf, recovers the input scf vector within
      the per-band rounding of round(a · log2(scf / 64)) /
      round(6 − log2(scf)).
    • Fourteen integration tests in
      tests/round234_aspx_envelope_extractor.rs cover: forward-inverse
      identity at integer-quant grid points for both Fine and Coarse
      signal step sizes; forward-inverse identity for Pseudocode 83
      on the noise side; non-positive scf clamps to a finite quant
      index; FREQ-DPCM encoder produces [5, 2, −4, −4, 1] for
      qscf = [5, 7, 3, −1, 0] with the decoder's accumulator
      recovering the input; empty / single-band inputs pass through;
      end-to-end accumulator + Pseudocode-82 / 83 round-trip from
      caller scf through extractor through Pseudocode-{82, 83};
      build_aspx_real_envelope_channel matches direct calls; full
      encoder→decoder loop wiring build_aspx_real_envelope_channel
      into write_aspx_data_2ch_real_envelope recovers the input
      scf vectors through the decoder's full pipeline; determinism
      across repeated invocations; different inputs produce materially
      different DPCM payloads; empty per-channel slices return empty
      vectors.
    • Total tests 856 (was 842). With this round the encoder now has
      the complete scf[] → on-wire bytes chain for real ASPX
      envelope coding; remaining envelope-coding work is the energy
      estimator that turns input MDCT spectra into the per-sbg
      scf vectors the extractor consumes (the inverse of Pseudocodes
      90 + 91), plus driving the new extractor + builder pair from
      the existing high-level encode entry points. β₃ extraction in
      the 5_X ACPL_3 path and real Table-181 SAP-derived residual
      content for the ACPL_1 paths remain deferred.
  • Round 226 — write_aspx_data_2ch_real_envelope() and
    write_aspx_data_1ch_real_envelope() builders.
    Closes the second
    step of the README's "real ASPX envelope coding" deferral. The
    round-219 value-emitting ASPX-Huffman primitives
    (write_aspx_sig_f0_value / write_aspx_sig_df_value /
    write_aspx_noise_f0_value / write_aspx_noise_df_value) are now
    driven by per-channel envelope builders that emit a full
    ETSI TS 103 190-1 §4.2.12.4 Table 52 (aspx_data_2ch()) or
    §4.2.12.3 Table 51 (aspx_data_1ch()) body with caller-supplied
    F0 + signed DF quant indices.

    • New public type
      [crate::encoder_acpl3::AspxRealEnvelopeChannel] — { sig: &[i32], noise: &[i32] } per-channel envelope payload.
    • [crate::encoder_acpl3::write_aspx_data_2ch_real_envelope] —
      accepts (cfg, ch0, ch1) and writes the Table-52 body with
      aspx_xover_subband_offset = 0, FIXFIX framing (num_env = 1,
      optional aspx_freq_res = 0), aspx_balance = 1 (shared
      channel-0 framing), SIGNAL + NOISE delta-direction bits = FREQ,
      aspx_hfgen_iwc_2ch all-zero trailer, then four aspx_ec_data
      calls (ch0 SIGNAL LEVEL, ch1 SIGNAL BALANCE, ch0 NOISE LEVEL,
      ch1 NOISE BALANCE). qmode is forced Fine on FIXFIX + num_env == 1 per Table 52.
    • [crate::encoder_acpl3::write_aspx_data_1ch_real_envelope] —
      accepts (cfg, ch) and writes the Table-51 body with two
      aspx_ec_data calls (SIGNAL + NOISE, both LEVEL).
    • The SIGNAL band count keys off cfg.signals_freq_res(): low-res
      when the in-band aspx_freq_res = 0 bit is emitted (Signalled
      mode), otherwise the parser's freq_res.get(env) .copied().unwrap_or(true) fallback selects high-res (matching
      the r181 fix in write_aspx_data_2ch_minimal).
    • Caller slices shorter than the derived SBG count zero-pad the
      trailing envelope positions; F0 values outside [0, codebook_length) clamp to the codebook edge; DF values outside
      [-cb_off, +cb_off] saturate to the symmetric edge — matching
      the round-219 helper semantics.
    • Eight integration tests in
      tests/round226_aspx_real_envelope_writers.rs cover:
      deterministic 2ch envelope round-trips through
      parse_aspx_ec_data recovering caller inputs per channel;
      1ch envelope round-trip with LEVEL-only stereo_mode; short
      input slices zero-pad in place; 2ch / 1ch byte determinism;
      all-zero inputs decode to all-zero envelopes; different
      per-channel inputs produce different bytes; out-of-range DF
      saturates at the codebook's +cb_off edge (Fine/Level DF
      cb_off = 70).
    • The minimum-bit-cost write_aspx_data_2ch_minimal /
      write_aspx_data_1ch_minimal writers stay in place; no
      existing call site is touched, so every previous round's
      byte-stream expectations remain valid.
    • Total tests 842 (was 834). The remaining ASPX envelope-coding
      work is the per-(sbg, env) envelope-index extractor that
      inverts Pseudocode 82's scf = n_subbands · 2^(qscf/a)
      reconstruction so the new builders can be chained with input
      MDCT spectra. β3 extraction in the 5_X ACPL_3 path and real
      Table-181 SAP-derived residual content for the ACPL_1 paths
      remain deferred.
  • Round 215 — real per-parameter-band γ₁ / γ₂ / γ₃ / γ₄ extraction
    in the 5_X SIMPLE/ASPX_ACPL_3 encoder.
    Layered on top of the
    round-208 real γ₅ / γ₆ (centre) + the round-196 real α₁ / α₂ +
    real β₁ / β₂ path: the γ₁..γ₄ entropy layers — previously emitted
    as the round-95 zero-delta scaffold codewords — now carry per-band
    magnitudes derived from per-band 2×2 least-squares fits of the
    (L, Ls) and (R, Rs) output channel pairs onto the (L, R) carrier
    pair. In §5.7.7.6.2 Pseudocode 118 step 5 the (L, Ls) pair is
    built by the first ACplModule2 invocation with (a = α₁, b = β₁, y = y₀), and step 11 scales Ls = √2·z1. Forming
    (L + Ls/√2) cancels the y₀·β₁ decorrelator contribution
    exactly, leaving L + Ls/√2 = (γ₁·x0in + γ₂·x1in) which expands
    to (1 + √2)·(γ₁·L + γ₂·R) via the step-1 carrier rescaling. By
    symmetry with step 6 the same fit shape gives (γ₃, γ₄) from
    (R + Rs/√2)/(1 + √2). New
    [encoder_acpl3::extract_gamma_1_2_q_per_band_surround_least_squares]
    and [extract_gamma_3_4_q_per_band_surround_least_squares]
    solve the 2×2 normal equations
    [<L,L> <L,R>; <L,R> <R,R>]·[γ; γ'] = [<L,T>; <R,T>] per
    parameter band. Bands with a degenerate Gram matrix (no L or R
    energy, or perfectly collinear L = ±R within numerical tolerance)
    keep γ = γ' = 0. New
    [encoder_acpl3::build_5_x_acpl3_body_from_pcm_spectra_real_alpha_beta_full_gamma]
    drops all six γ extractors into the acpl_data_2ch() body
    alongside the round-208 γ₅ / γ₆ extractor; β₃ stays zero-delta.
    Caller-facing
    [encoder_ims::Ac4ImsEncoder::encode_frame_pcm_5_0_acpl3_real_alpha_beta_full_gamma]
    / encode_frame_pcm_5_1_acpl3_real_alpha_beta_full_gamma wrap
    the new builder, accepting a 5- / 6-channel
    [L, R, C, Ls, Rs (, LFE)] input (vs the round-208 3- / 4-channel
    [L, R, C (, LFE)] input that could only drive the centre γ
    layer). Nine integration tests in
    tests/round215_5_x_acpl3_real_full_gamma.rs pin: 5.0 round-trip
    to a 5-channel AudioFrame; 5.1 round-trip to a 6-channel
    AudioFrame; silent surround (Ls = 0) yields γ₂_q = 0 in every
    band when probed directly; silent surround (Rs = 0) yields γ₃_q
    = 0 in every band; α/β/γ_scale = 0.0 reproduces the round-95
    zero-delta scaffold byte-for-byte; γ_scale = 0.0 reproduces
    the round-196 real-α-β bytes byte-for-byte; loud-surround vs
    silent-surround inputs produce materially different bytes (the
    round-208 path would emit identical γ₁..γ₄ codewords regardless
    of surround input); the encoder is bit-deterministic for matched
    inputs and fresh state. Total tests 822 (was 813). β₃ extraction
    (requires modelling the unobservable decorrelator output y₂)
    and real ASPX envelope coding remain deferred.

  • Round 208 — real per-parameter-band γ5 / γ6 extraction in the
    5_X SIMPLE/ASPX_ACPL_3 encoder.
    Layered on top of the round-196
    real α₁ / α₂ + real β₁ / β₂ path: the γ5 / γ6 entropy layers now
    carry per-band magnitudes derived from a 2×2 per-band
    least-squares fit of the centre channel. In §5.7.7.6.2
    Pseudocode 118 step 7 the centre output z4 is built by the
    third ACplModule2 invocation with (a = 1, b = 0, y = 0):
    z4 = 0.5 · (γ5·x0in + γ6·x1in). Step 11 scales z4 *= √2
    before QMF synthesis; step 1 rescales the carriers
    x0in = (1 + √2)·L, x1in = (1 + √2)·R. The centre
    reconstruction (β3 = 0, ducker = 1) is therefore
    C ≈ K · (γ5·L + γ6·R) with K = √2·(1+√2)/2 = 1+√(1/2). The
    round-208 extractor solves the 2×2 normal equations per
    parameter band:

      [ <L,L>  <L,R> ] [γ5]   [ <L,C>/K ]
      [ <L,R>  <R,R> ] [γ6] = [ <R,C>/K ]
    

    for (γ5, γ6) that minimise the MDCT-bin-wise residual
    Σ (C/K − γ5·L − γ6·R)². Bands with a degenerate Gram matrix
    (no L or R energy, or perfectly collinear L = ±R within
    numerical tolerance) keep γ5 = γ6 = 0. The quantiser uses the
    Table-208 linear gamma_q = round(γ / gamma_delta) mapping with
    the symmetric ±cb_off clamp (cb_off = 20 Fine / 10 Coarse,
    table magnitude bound ±2.0). γ1..γ4 + β3 stay at the round-95
    scaffold (those parameter sets drive the (L, R, Ls, Rs)
    sub-pipeline plus the ACplModule3 cross-residual — neither of
    which has a per-side surround reference at encode time for the
    5.0 / 5.1 PCM input layouts the real-γ entry point targets).

    • [crate::encoder_acpl3::extract_gamma_5_6_q_per_band_centre_least_squares]
      — per-parameter-band 2×2 least-squares γ5 / γ6 extractor.
    • [crate::encoder_acpl3::build_5_x_acpl3_body_from_pcm_spectra_real_alpha_beta_gamma]
      — drop-in replacement for the round-196
      build_5_x_acpl3_body_from_pcm_spectra_real_alpha_beta with
      additional coeffs_c: Option<&[f32]> + gamma_scale: f32
      parameters. gamma_scale = 0.0 reproduces the round-196 byte
      stream exactly; alpha_scale = beta_scale = gamma_scale = 0.0
      reproduces the round-95 zero-delta scaffold.
    • [crate::encoder_ims::Ac4ImsEncoder::encode_frame_pcm_5_0_acpl3_real_alpha_beta_gamma]
      • ..._5_1_... — high-level entry points accepting [L, R, C]
        (5.0) or [L, R, C, LFE] (5.1) PCM.
    • tests/round208_5_x_acpl3_real_gamma.rs (8 tests) pins: 5.0
      round-trip to 5-channel AudioFrame; 5.1 round-trip to
      6-channel AudioFrame; silent-centre input produces
      γ5_q = γ6_q = 0 in every band; C = (L + R) / 2 produces
      non-zero γ_q in ≥1 tonally-active band; loud-centre vs
      silent-centre inputs produce materially different bytes (the
      round-196 path would emit identical γ codewords regardless of
      centre input); α/β/γ_scale = 0.0 matches the round-95
      scaffold byte-for-byte; γ_scale = 0.0 reproduces the
      round-196 real-α-β bytes exactly; encoder is bit-deterministic
      for matched inputs and fresh state.
    • Real γ1..γ4 extraction (the (L,R,Ls,Rs) sub-pipeline mix
      parameters) requires per-side surround references which the
      5.0 / 5.1 PCM input layout does not carry — these stay at the
      round-95 zero-delta scaffold pending a 5.1+Ls+Rs PCM input
      layout. Real β extraction for the 7_X ACPL_3 paths, real ASPX
      envelope coding, and real Table-181 SAP-derived residual
      content (for the ACPL_1 paths) remain deferred.
  • Round 202 — real per-parameter-band α + β extraction in the
    7.0 / 7.1 SIMPLE/ASPX_ACPL_2 multichannel encoder.
    The 7_X
    (immersive) counterpart to the round-144 5.0 ACPL_2 real-α-β
    path and the real-α-β upgrade of the round-107 / 114 zero-delta
    7_X ACPL_2 encoder. ACPL_2 does not transmit the Ls/Rs surround
    pair on the wire — the decoder reconstructs the surround from
    the L/R carriers + the two acpl_data_1ch() parameter sets per
    §5.7.7.5 Pseudocode 116 + §5.7.7.6.1 Pseudocode 117:
    z0 = 0.5·(x0·(1+α) + y·β), z1 = 0.5·(x0·(1−α) − y·β).

    • [crate::encoder_acpl3::build_7_x_acpl2_body_from_pcm_spectra_real_alpha_beta]
      — real-α-β upgrade of
      [build_7_x_acpl2_body_from_pcm_spectra]; identical body
      schedule (2-bit 7_X_codec_mode = 3, optional LFE
      mono_data(b_lfe = 1), two two_channel_data() pairs, no
      joint-MDCT residual layer, trailing centre mono_data(0),
      aspx_data_2ch + aspx_data_2ch + aspx_data_1ch envelope
      trailer) with the two trailing acpl_data_1ch_minimal writers
      replaced by write_acpl_data_1ch_real_alpha_beta. D0 module
      models (L → Ls); D1 module models (R → Rs). acpl_config_1ch (FULL) carries no qmf_bandstart_band = 0 so every
      parameter band participates.
    • [crate::encoder_ims::Ac4ImsEncoder::encode_frame_pcm_7_0_acpl2_real_alpha_beta]
      • _with_max_sfb — accepts [L, R, C, Ls, Rs, Lb, Rb],
        forces the 7.0 channel_mode prefix (0b1111000, 7 b — Table
        85 channel_mode 5), emits a 7-channel S16 PCM round-trip.
    • [crate::encoder_ims::Ac4ImsEncoder::encode_frame_pcm_7_1_acpl2_real_alpha_beta]
      • _with_max_sfb — accepts [L, R, C, Ls, Rs, Lb, Rb, LFE],
        forces the 7.1 channel_mode prefix (0b1111001, 7 b — Table
        88 channel_mode 6), emits an 8-channel S16 PCM round-trip
        with the LFE element written via the round-80
        write_lfe_mono_data shared emitter.
    • tests/round202_7_x_acpl2_real_alpha_beta.rs (10 tests) pins:
      7.0 / 7.1 AudioFrame round-trip; decoder resolves
      SevenXCodecMode::AspxAcpl2 with both
      acpl_data_1ch_pair[0/1] populated; loud-surround vs
      silence-surround inputs produce materially different bytes;
      silence input round-trips with β_q = 0 in every band; encoder
      is bit-deterministic for matched inputs and fresh state;
      direct body-builder probe diverges from the round-107
      zero-delta scaffold byte stream when the caller's Ls/Rs
      spectra are non-trivial.
    • The back pair Lb / Rb is accepted for layout completeness but
      not carried by the ASPX_ACPL_2 body (the decoder's 7_X ACPL_2
      dispatch populates slots 0..4 + the LFE slot 7 — slots 5/6
      stay silent), matching the round-107 documented Table 202
      channel mapping plus the round-80 LFE PCM render at decode
      time.
    • Real β extraction for the 7_X ACPL_3 paths, real γ extraction,
      real ASPX envelope coding, real Table-181 SAP-derived residual
      content (for the ACPL_1 paths), and back-pair Lb/Rb carriage
      remain deferred.
  • Round 196 — real per-parameter-band α1 / α2 extraction in the
    5_X SIMPLE/ASPX_ACPL_3 encoder.
    Layered on top of the round-193
    real β1 / β2 path: the two ACplModule2 instances in ACPL_3 share
    the (L, R) carrier pair as their (x0, x1) input, so without a
    per-side surround reference at encode time α₁ / α₂ are driven by
    the same L↔R cross-correlation extractor — α[pb] = α_scale · ρ(L, R)[pb] with ρ = E[L·R] / √(E[L²]·E[R²]) — clamped to the
    ALPHA_DQ table magnitude bound (±2.0 Fine / ±2.0 Coarse).

    • [crate::encoder_acpl3::extract_alpha_q_per_band_carrier_correlation]
      — extracts per-band α_q from the L / R MDCT spectra. The α
      parameter modulates the front/back dry-mix balance in
      ACplModule2 (Pseudocode 119): higher α → more dry energy on the
      front pair, lower α → more on the surround pair. Mono-like
      (highly-correlated) bands push α toward +1; decorrelated bands
      stay near α = 0.
    • [crate::encoder_acpl3::build_5_x_acpl3_body_from_pcm_spectra_real_alpha_beta]
      — drop-in replacement for the r193
      build_5_x_acpl3_body_from_pcm_spectra_real_beta that lifts
      α1 / α2 from the zero-delta scaffold in addition to β1 / β2.
      β3 / γ1..γ6 still zero-delta. With α_scale = β_scale = 0.0 the
      output is byte-for-byte identical to the round-95
      build_5_x_acpl3_body_from_pcm_spectra scaffold.
    • [crate::encoder_ims::Ac4ImsEncoder::encode_frame_pcm_5_0_acpl3_real_alpha_beta]
      and encode_frame_pcm_5_1_acpl3_real_alpha_beta — caller-facing
      entry points wrapping the new builder with the same channel-mode
      forcing / MDCT analysis / TOC framing as the existing real-β
      paths.
    • tests/round196_5_x_acpl3_real_alpha_beta.rs (4 tests) pins:
      round-trip to 5-channel AudioFrame; perfect L = R correlation
      (ρ = +1) quantises to α_q = +8 (lane 24 = 1.0); perfect
      anti-correlation L = -R (ρ = -1) quantises to α_q = -8;
      α_scale = β_scale = 0.0 is byte-for-byte identical to the
      round-95 scaffold.
    • Total tests 795 (+4 over r193).
  • Round 193 — real per-parameter-band β1 / β2 extraction in the
    5_X SIMPLE/ASPX_ACPL_3 encoder.
    The round-95 ASPX_ACPL_3 path
    emits all 11 ACPL parameter sets as zero-delta Huffman codewords:
    with α = β = β3 = γ = 0 the §5.7.7.6.2 Pseudocode 118 / 119
    synthesis collapses to a trivial mix that produces silent surround
    outputs from non-silent carrier inputs (structurally correct but
    perceptually inert). This round adds three encoder surface entries
    that lift β1 / β2 out of the zero-delta scaffold while keeping
    α1 / α2 / β3 / γ1..γ6 at the round-95 minimum-bit-cost defaults.

    • [crate::encoder_acpl3::extract_beta_q_per_band_carrier_energy]
      — extracts per-parameter-band β_q from a single carrier's MDCT
      energy distribution. The β parameter in ACplModule2 is the gain
      applied to the decorrelator output; setting it proportional to
      √E[x²] keeps the wet/dry balance bounded so the surround
      reconstruction tracks the carrier RMS per band.
    • [crate::encoder_acpl3::build_5_x_acpl3_body_from_pcm_spectra_real_beta]
      — drop-in replacement for build_5_x_acpl3_body_from_pcm_spectra
      that runs the carrier-energy extractor over the L / R inputs and
      emits real β1 / β2 codewords. Mirrors the round-95 wire layout
      everywhere else so the decoder walks the same Table 25 body.
    • [crate::encoder_ims::Ac4ImsEncoder::encode_frame_pcm_5_0_acpl3_real_beta]
      and encode_frame_pcm_5_1_acpl3_real_beta — caller-facing entry
      points that wrap the new builder with the same channel-mode
      forcing, MDCT analysis and TOC framing the existing
      encode_frame_pcm_5_0_acpl3 / encode_frame_pcm_5_1_acpl3 use.
    • With α1 = α2 = 0 and β3 = 0 the ACplModule2 synthesis at
      parameter band pb reduces to
      z0 = 0.5·(x0·g1 + x1·g2 + y0·β1),
      z1 = 0.5·(x0·g1 + x1·g2 − y0·β1),
      and analogously (z2, z3) with β2 driving the second
      ACplModule2. Non-zero β1 / β2 inject the decorrelator output y
      that gives the Ls / Rs outputs their decorrelated spaciousness.
    • tests/round193_5_x_acpl3_real_beta.rs (7 tests) pins: round-trip
      to 5- / 6-channel AudioFrame for 5.0 / 5.1; silent input →
      all-zero β_q indices; tonal carrier + non-zero beta_scale
      at least one non-zero β_q lane; beta_scale = 0.0 is
      byte-for-byte identical to the round-95 scaffold; silent inputs
      at any beta_scale are scaffold-identical (carrier-energy
      extractor short-circuits to 0); non-silent tonal inputs at
      beta_scale > 0 diverge from the round-95 scaffold (different
      β1 / β2 codeword bit-positions) while keeping the padded
      substream length identical.
    • Total tests 791 (+7 over r190).