Skip to content

Releases: OxideAV/oxideav-ac4

v0.0.7

15 Jun 05:14
b8fcdb0

Choose a tag to compare

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
    (`L...

Read more

v0.0.6

30 May 03:23

Choose a tag to compare

Other

  • ac4 r190: close ASPX_ACPL_1 desync — fix aspx_framing() FIXFIX prefix
  • ac4 round 187 — pin ACPL_1 residual / α-β desync follow-up
  • round 181 — close r128 alpha_q desync at parser indexing + aspx_data_2ch SIGNAL band count
  • round 174 — fix ALPHA / BETA3 F0 cb_off (latent #1121 desync)
  • round 144 — real per-band α + β extraction for 5_X ASPX_ACPL_2
  • round 139 — 7.1-with-LFE ACPL_1 real per-band α + β
  • round 135 — real per-band α + β extraction for 7_X ASPX_ACPL_1
  • round 132 — real per-band β extraction in ACPL_1 5.0 encoder
  • round 128 — real per-band α extraction in ACPL_1 5.0 encoder
  • round 125 — 7.0 (3/4/0) SIMPLE/Cfg3Five multichannel encoder
  • round 118 — 7.0/7.1 SIMPLE/ASPX_ACPL_1 multichannel encoder
  • Round 114: 7.1 (3/4/0.1) SIMPLE/ASPX_ACPL_2 multichannel encoder (LFE)
  • Round 107: 7.0 SIMPLE/ASPX_ACPL_2 multichannel encoder
  • round 103 — 5_X SIMPLE/ASPX_ACPL_1 multichannel encoder path
  • round 100 — 5_X SIMPLE/ASPX_ACPL_2 multichannel encoder path
  • ac4 round 95: 5_X SIMPLE/ASPX_ACPL_3 multichannel encoder path
  • ac4 round 91: 7.1 (3/4/0.1) SIMPLE/Cfg3Five encoder (7 SCE + LFE)
  • ac4 round 80: 5.1 SIMPLE/Cfg3Five encoder (5 SCE + LFE) + decoder LFE PCM render
  • ac4 round 74: 5.0 SIMPLE/Cfg3Five multichannel forward analysis (5 SCE)
  • ac4 round 52: joint M/S CPE (Path B, b_enable_mdct_stereo_proc=1)
  • ac4 round 51: stereo SIMPLE/ASF split-MDCT (Path A: 2x SCE) encoder

Fixed

  • Round 190 — close the 5_X ASPX_ACPL_1 desync the r187 tests
    pinned.
    Two minimal A-SPX writers
    ([crate::encoder_acpl3::write_aspx_data_2ch_minimal] and
    [write_aspx_data_1ch_minimal]) emitted aspx_int_class = FIXFIX
    as the wrong prefix code: 0b11 (2 bits) instead of 0b0 (1 bit)
    per ETSI TS 103 190-1 Table 126. The decoder's
    [crate::aspx::AspxIntClass::read] correctly walks the prefix —
    0 → FixFix, 10 → FixVar, 110 / 111 → VarFix / VarVar — so
    the writer's 11 start signalled the parser to read the VarFix /
    VarVar branch instead. For our config that put the parser in the
    VarFix branch with b_iframe = 1: it then read
    var_bord_left (2 b), num_rel_left (2 b — num_aspx_timeslots = 15 > 8 makes Note-1 fields 2-bit wide), and tsg_ptr (2 b).
    Net: parser consumed 9 bits in the framing where the writer
    only emitted 3 bits, a 6-bit upstream drift that the
    silence / L-only / Ls-only test paths masked (α / β quantised to 0
    ⇒ the acpl_data_1ch body shape was constant minimum-cost on each
    side and the num_param_sets_cod bit positions on both sides
    sampled 0 within the long run of zero codewords). With non-zero
    α / β the codewords shift, the pair-1 num_param_sets_cod bit
    position lands on a 1, and pair1 reads num_param_sets = 2
    the r187 symptom. Fix is one line per writer: emit
    bw.write_bit(false) for the FIXFIX prefix, matching Table 126.
    • The r187 test #4 (acpl1_combined_l_and_ls_pair1_currently_misaligns)
      was renamed to acpl1_full_round_trips_with_aligned_pair_lengths
      and its assertion flipped from assert_eq!(n1, 2) (pinned
      misalignment) to assert_eq!(n1, 1) (post-fix). All four
      combinations now round-trip with
      pair0.num_param_sets = pair1.num_param_sets = 1.
    • Total tests 784 (unchanged from r187 — r190 fixed the third pin
      in place rather than adding new ones; the bit-level diagnosis is
      carried in the test file's module doc-comment instead).

Added

  • Round 187 — characterisation tests pinning the remaining
    5_X ASPX_ACPL_1 residual / α-β-writer desync the r181 follow-up
    flagged.
    Four end-to-end pinning tests in
    tests/round187_acpl1_residual_desync_characterization.rs sweep
    the encoder's encode_frame_pcm_5_0_acpl1_real_alpha_beta across
    four input combinations and assert the decoder's recovered
    acpl_data_1ch_pair[0/1].framing.num_param_sets so the next round
    can iterate on the residual-layer / α-β writers without regressing
    the aligned silence / L-only / Ls-only paths.
    • Silence (all-zero PCM) → both pair slots resolve
      num_param_sets = 1.
    • L-carrier-only (Ls = Rs = 0) → both pair slots resolve
      num_param_sets = 1; the write_two_channel_data carrier writer
      is exercised non-trivially while α / β stay quantised to 0
      (correlation Σ L · Ls = 0 ⇒ α extractor returns 0; surround
      energy E[Ls²] = 0 ⇒ β extractor returns 0).
    • Ls-residual-only (L = R = 0) → both pair slots still resolve
      num_param_sets = 1; the write_acpl_1_residual_layer joint-MDCT
      residual writer is exercised non-trivially with max_sfb_master
      non-zero band budget but α / β stay 0 because carrier energy is 0.
    • Combined L-carrier + Ls-residual (L = 0.5, Ls = 0.05) →
      pair0 still resolves num_param_sets = 1, but pair1 drifts to
      num_param_sets = 2. The pin captures this as the currently
      expected
      behaviour so the next round's residual-layer fix can
      flip the assertion back to 1 once aligned.
    • Diagnostic narrative in the test file's module doc-comment
      triangulates the bug surface: the writer→parser pairs for
      write_acpl_data_1ch_real_alpha_betaparse_acpl_data_1ch
      are bit-exact in isolation (pinned by
      round181_alpha_desync_fix::standalone_*); back-to-back
      invocations into the same BitWriter without byte alignment
      between them also round-trip cleanly. The drift therefore sits
      upstream of pair0 — either in the joint-MDCT residual writer
      (write_acpl_1_residual_layer) vs the inline residual walk
      inside parse_aspx_acpl_1_2_inner_body's ASPX_ACPL_1 branch, or
      in the two_channel_data() L/R carrier writer vs
      parse_two_channel_data — when L and Ls are simultaneously
      non-trivial. Total tests 784 (was 780).

Fixed

  • Round 181 — A-CPL acpl_huff_data() Pseudocode-121 indexing +
    aspx_data_2ch() SIGNAL band count.
    Closes the user's "alpha_q
    desync" follow-up the round-174 ALPHA / BETA3 F0 cb_off fix
    deferred. Two distinct layers were involved.

    • Layer 1 — §4.2.13.7 Table 65 / §5.7.7.7 Pseudocode 121 parser
      indexing.
      Pre-r181 [crate::acpl::parse_acpl_huff_data] packed
      the (num_param_bands - start_band) Huffman-decoded values into
      a vector starting at index 0. Per the spec the same array is the
      input to Pseudocode 121's for (i = 0; i < num_bands; i++)
      accumulation — so positions [0..start_band) are zero (the
      encoder did not transmit those bands) and the F0 codeword lands at
      values[start_band]. The packed-from-0 layout silently shifted
      the §5.7.7.7 DIFF_FREQ accumulation by start_band parameter
      bands for the 5_X SIMPLE/ASPX_ACPL_1 PARTIAL path
      (acpl_qmf_band > 0, start_band > 0). The r181 fix rewrites
      [crate::acpl::parse_acpl_huff_data] to return a length-
      num_param_bands vector indexed by full param-band number — the
      spec-aligned acpl_<SET>[ps][i] shape Pseudocode 121 reads. The
      [AcplHuffParam`] doc comment now spells out the new indexing
      contract.
    • Layer 2 — §4.2.12.4 Table 52 aspx_data_2ch() SIGNAL band
      count.
      Per ETSI TS 103 190-1 §4.3.10.4.9 (Table 124 NOTE 3)
      aspx_ec_data(SIGNAL, …) reads num_sbg_sig_lowres SIGNAL bands
      when the corresponding aspx_freq_res[env] bit was emitted as 0
      and num_sbg_sig_highres when it was 1 or absent (when
      freq_res_mode != Signalled the encoder writes no in-band
      aspx_freq_res bit and the decoder's
      freq_res.get(env).copied().unwrap_or(true) fallback selects
      high-res). Pre-r181 [crate::encoder_acpl3::write_aspx_data_2ch_minimal]
      hard-coded num_sbg_sig_lowres regardless — so for the
      encoder's default freq_res_mode = DurationDependent config
      (20-band high-res vs 10-band low-res, no in-band freq_res bit)
      the writer emitted 10 SIGNAL F0+DF codewords per channel while
      the parser read 20. The 20-vs-10 mismatch buried every
      subsequent acpl_data_1ch() α / β codeword in trailing
      zero-padding, recovered as length-num_param_bands all-zero
      rows. r181 keys the SIGNAL band count off cfg.signals_freq_res()
      matching the writer's own freq_res-bit gate.
    • 4 new round-181 unit / integration tests in
      tests/round181_alpha_desync_fix.rs:
      [standalone_alpha_writer_round_trips_through_parser] (Layer 1,
      standalone writer→parser→differential_decode),
      [parser_values_are_indexed_by_full_param_band_number] (Layer 1,
      structural — confirms values[0..start_band) == 0 and
      values[start_band..] carries the F0 + DF accumulation),
      [end_to_end_acpl2_asymmetric_surround_recovers_nonzero_alpha]
      (Layer 2 — encode 5.0 ASPX_ACPL_2 with asymmetric L/Ls energy,
      decode, assert acpl_data_1ch_pair[0/1].alpha1[0].values
      carries non-zero entries),
      [end_to_end_acpl2_silence_still_round_trips] (Layer 2 regression
      guard — silence input still yields all-zero α/β). Total tests
      780 (was 776).
    • The r132 acpl_data_1ch_real_alpha_beta_round_trips_byte_exact
      test is re-shaped to apply the spec-aligned Pseudocode 121 DIFF_FREQ
      accumulation directly on the parser's length-num_param_bands
      output (instead of cumulating the pre-r181 packed
      (num_bands - start_band)-length slice).
    • The 5_X SIMPLE/ASPX_ACPL_1 PARTIAL end-to-end path retains a
      separate joint-MDCT residual-layer alignment issue (the residual
      sf_data writer and the decoder's decode_asf_long_mono_body_with_max_sfb
      appear to read different total bit counts on non-trivial inputs)
      that the r181 ACPL_2 end-to-end tests do not exercise. Tracking
      as the remaining "alpha_q desync" follow-up — the structural
      Layer 1 + Layer 2 fixes are independent of it and land here.
  • Round 174 — ALPHA / BETA3 F0 codebook cb_off corrected per ETSI
    TS 103 190-1 §A.3 Tables A.34 / A.35 (ALPHA) and A.46 / A.47 (BETA3).

    • Pre-fix cb_off = 0 for ALPHA / BETA3 F0 conflicted with the §5.7.7.7
      Pseudocode 121 ...
Read more

v0.0.5

10 May 03:22
7feced4

Choose a tag to compare

Other

  • ac4 round 50: section-boundary DP optimiser + SNF emission
  • ac4 round 49: HCB1..11 codebook-selection optimiser + parameterised max_sfb
  • ac4 round 48: forward MDCT + ASF entropy encoder for arbitrary PCM
  • ac4 round 47: IMS bitstream_version=2 TOC parser + mono SIMPLE/ASF tone encoder
  • round 46: AC-4 IMS encoder scaffold + ACPL_1 surround Ls/Rs spec audit
  • ac4 round 45: stereo-CPE M=2 synced companding for ACPL_3 surround pair
  • ac4 round 44: companding sync_flag=1 cross-channel exact synchronisation
  • ac4 round 43: companding sync_flag=1/avg branches + ACPL_1 sb0 hookup
  • ac4 round 42: cfg0/cfg1/cfg3 trailer-aware ASPX + §5.7.5 companding
  • ac4 round 41: 5_X cfg2 ASPX trailers + Table 181 SAP for ACPL_1
  • ac4 round 40: SAP a/b/c/d (Pseudocode 59) + Table 183 + Ls/Rs walker
  • ac4 round 39: 5_X cfg0/cfg1/cfg3 dispatch + 7_X additional-channel pair
  • ac4 round 38: LFE body decoder + cfg2_back_mono end-to-end + ACPL_3 centre
  • ac4 round 37: wire 7_X ACPL_1/_2 dispatch + cfg0 centre end-to-end decode
  • ac4 round 36: wire 5_X ASPX_ACPL_1 / ACPL_2 Pseudocode 117 into decoder
  • Round 35: extend ETSI validation suite to float reference tables
  • drop dead linkme dep
  • round 35 — EMDF payloads_substream parser + DRC PCM gain application
  • cargo fmt pass after round 34
  • update round 34 status (SNF + FIXVAR/VARFIX/VARVAR atsg + ACPL_3)
  • ac4 round 34: FIXVAR/VARFIX/VARVAR atsg + SNF inject + 5_X ACPL_3 wiring
  • auto-register via oxideav_core::register! macro (linkme distributed slice)
  • unify entry point on register(&mut RuntimeContext) (#502)

Added

  • Round 50 — Section-boundary DP optimiser + Spectral Noise Fill
    (SNF) emission
    (TS 103 190-1 §5.7.4 section_data + §5.7.6 SNF +
    Pseudocodes 100 + 105 + Table 39/42 + Table SCFB):

    • encoder_asf::dp_optimise_sections(cost_band_cb, max_sections)
      dynamic-program over scale-factor bands that finds the globally
      cheapest sequence of (start, end, cb) sections, paying the
      per-section header cost (4 + 3 * (floor((L-1)/7)+1) bits per
      Table 39) against each band's per-codebook bit cost. Section
      count capped at 16 per ETSI Table SCFB. Supersedes the round-49
      greedy run-length codebook-merge optimiser.
    • encoder_asf::section_overhead_bits(len) — closed-form per-section
      overhead in bits matching the spec's n_sect_bits=3 / esc=7
      long-frame layout: 7 bits for L ∈ 1..=7, 10 bits for L ∈ 8..=14,
      13 bits for L ∈ 15..=21, etc.
    • encoder_asf::build_band_codebook_cost_table(natural_q_per_band)
      — precomputed per-band per-codebook bit cost (rows of length 12;
      cb=0 cost 0 only for all-zero bands; HCB1..11 costs from
      bit_cost_for_band; u32::MAX for codebooks that can't represent
      the band's natural quant magnitudes). Drives the DP via O(1)
      prefix sums where every band is feasible for the codebook.
    • encoder_asf::build_sections_from_dp(sections, max_sfb) — lowers
      the DP-derived (start, end, cb) triples into an AsfSections
      suitable for the existing write_section_data /
      write_spectral_data_sections emitters.
    • encoder_asf::compute_snf_dpcm_for_zero_quant_bands(coeffs, sfb_offset, max_sfb, sfb_cb, max_quant_idx) — for each band that
      quantises to all-zero (cb == 0 || mqi == 0), estimates the band's
      RMS energy from the original MDCT coefficients and picks the
      HCB_SNF index whose snf_gain = 2^((idx*1.5 - 84)/4) best matches.
      Returns Some(per_band_idx) when at least one zero-quant band
      has measurable energy; None for fully-silent input.
    • encoder_asf::write_snf_data(bw, snf, sfb_cb, max_quant_idx, max_sfb)
      — emits b_snf_data_exists (1 bit) plus per-zero-quant-band
      HCB_SNF Huffman codewords per Table 42 / Pseudocode 105. Round-trips
      cleanly through the existing parse_asf_snf_data decoder path
      (round 36+).
    • encoder_asf::measure_greedy_vs_dp_bits(transform_length, max_sfb, coeffs) — diagnostic helper returning (greedy_bits, dp_bits)
      for any input spectrum so callers can quantify the section-boundary
      optimiser's contribution to total frame size.
    • Ac4ImsEncoder::encode_frame_pcm_with_max_sfb now drives the DP
      optimiser + SNF emission internally; existing call sites get the
      new path automatically with no API change. White-noise spectral
      SNR holds at 27.5 dB (round-49 baseline) with section overhead
      reduced and SNF emission turned on for high-frequency zero-quant
      content.
  • Round 49 — Codebook-selection optimiser (HCB1..11) + parameterised
    max_sfb
    (TS 103 190-1 §5.7 + Pseudocodes 17 + 19 + 20 + Annex A.0
    huff_codes + Table SCFB):

    • encoder_asf::pick_best_codebook_for_band — per-band codebook
      optimiser sweeping HCB1..11 and choosing the lowest-bit-cost
      codebook whose q_max covers the band's natural quantised range.
      Anchor scalefactor targets peak quant ≈ 12 (HCB9/10's q_max → 3×
      more quantisation levels per band than the round-48 HCB5-only
      baseline). HCB11 always qualifies via its Pseudocode 20 escape so
      very-high-energy bands don't clip.
    • encoder_asf::bit_cost_for_band — precise bit-counter modelling
      HCB1..11 codeword length + sign bits for unsigned codebooks +
      per-Pseudocode-20 n_ext extension bits for HCB11 escapes.
      Mirrors the encoder emitter's exact bit shape (inline magnitude
      saturates at 16 for HCB11, sign bit per non-zero post-saturation
      line for unsigned codebooks).
    • encoder_asf::build_sections_from_per_band_cb — collapses runs
      of consecutive same-codebook bands into a single AsfSections
      entry so the emitted asf_section_data() honours the spec's
      grouping pseudocode without spurious cb-switch overhead.
    • encoder_asf::write_section_data + write_spectral_data_sections
      — multi-section asf_section_data + asf_spectral_data emitters.
      Per-section emission walks sect_start..sect_end bins with the
      section's codebook, handles cb == 0 silent bands, and writes
      Pseudocode 20 escape bits for HCB11 outliers.
    • Ac4ImsEncoder::encode_frame_pcm_with_max_sfb(frame, max_sfb)
      new public entry point parameterising max_sfb (round-48 default
      was hard-coded to 40 → ~6.4 kHz at tl=1920 / 48 kHz). Pad target
      scales with max_sfb (2KB / 4KB / 8KB tiers). encode_frame_pcm
      keeps the round-48 default of max_sfb=40 for backwards
      compatibility.
    • White-noise round-trip SNR jumps from 13.6 dB (round-48 HCB5-only
      baseline) to 27.5 dB (round-49 HCB1..11 optimiser, q_target=12,
      max_sfb=50) — measured spectrally against the encoder's own MDCT
      coefficients pre/post quantisation. 1 kHz tone reconstruction at
      max_sfb=55 preserves >100% of input energy (vs ~40% at the
      round-48 max_sfb=40 default).
  • Round 48 — Forward MDCT analysis + ASF entropy encoder for arbitrary
    PCM input
    (TS 103 190-1 §5.5 MDCT + §5.7 SIMPLE + §5.8 ASF +
    Pseudocodes 17-19 + Annex A.0 huff_codes):

    • encoder_mdct::mdct_naive + EncoderMdctState — forward MDCT
      direction complementing the decoder's mdct::imdct. Naive
      O(N²) direct-summation cosine basis (correctness-first; encoder
      isn't on a hot path). Sign convention + scaling matched against
      the decoder's IMDCT through a Princen-Bradley TDAC round-trip
      test (constant-signal recovery in steady-state middle frame
      within 1% error). EncoderMdctState carries the previous-frame
      N PCM samples for cross-frame 50% TDAC overlap.
    • encoder_asf::quantise_coeff + pick_scalefactor_for_band +
      encode_pair + write_sect_len_incr + write_scalefac_data +
      build_mono_simple_asf_body_from_pcm_spectrum — closed-form
      forward ASF entropy encoder for the long-frame, single-window-
      group, mono SIMPLE channel case. Per-band scalefactor selection
      via the closed-form solve sf_min = ceil(100 + 4*log2(max_abs/q_max^(4/3)))
      keeping every quantised line within HCB5's ±4 magnitude bound
      after q = round(sign(c)*|c/sf_gain|^(3/4)). Single-section
      HCB5 emission across 0..max_sfb; reference scalefactor
      • DPCM-coded per-band deltas via HCB_SCALEFAC; b_snf_data_exists = 0.
    • Ac4ImsEncoder::encode_frame_pcm(input: &[f32]) — public entry
      point taking arbitrary float PCM input (range [-1.0, 1.0])
      and emitting a structurally-valid IMS v2 frame end-to-end:
      forward MDCT analysis → per-band scalefactor + quantisation →
      HCB5 entropy coding → wrap in v2 IMS TOC + audio_size header.
      Lazily initialises a per-encoder EncoderMdctState on first
      call; bumps sequence_counter modulo 1024. Mono / 48 kHz / 24 fps
      by default (frame_len = 1920 samples, max_sfb = 40 covering
      bins 0..600 ≈ 7.5 kHz).
    • 13 new unit tests across the three modules cover: forward MDCT
      zero-in-zero-out + linearity + Princen-Bradley constant-signal
      • sine-wave SNR > 40 dB; quantise/dequantise round-trip;
        pick_scalefactor q_max bound; encode_pair signed/unsigned
        round-trip via huff_decode + split_qspec;
        build_mono_simple_asf_body_from_pcm_spectrum end-to-end parse
        via the existing ASF decoder; full encode → decode round-trip
        for 1 kHz tone (peak amplitude > 1000 i16), multi-tone
        250+500+1000 Hz (SNR > 10 dB on steady-state frame), and
        silence (peak < 50 i16); encode_frame_pcm bumps
        sequence_counter per call.
    • Codebook-selection optimiser (try HCB1..11 per section, pick
      min-bits), section-boundary optimiser (split bands by
      codebook), spectral noise fill, and stereo / multichannel
      forward analysis remain deferred for round 49+.
  • Round 46 — AC-4 IMS encoder scaffold + ACPL_1 surround Ls/Rs
    ASPX-extension spec audit
    (TS 103 190-2 §6.2.1.1 / §6.3.2.5,
    TS 103 190-1 §4.2.6.6 Table 25):

    • encoder_ims::Ac4ImsEncoder — Auditor-mode AC-4 IMS encoder
      skeleton. Emits a structurally-valid raw_ac4_frame() payload
      with the IMS-flavo...
Read more

v0.0.4

05 May 06:14
c0a81ed

Choose a tag to compare

Other

  • land §5.2.5.2.2 Heuristic Scaling (round 33)
  • env_prev tracking + walker state hoisting (round 32)
  • land §5.2.3-5.2.7 PCM synthesis chain (round 31)
  • land Tables 43-46 bitstream walker (round 30)
  • land Annex C tables + arithmetic decoder core (round 29)

Added

  • Round 33 — §5.2.5.2.2 Heuristic Scaling (Pseudocodes 27/28/29/30)
    (TS 103 190-1 §5.2.5.2.0 selector + §5.2.5.2.2):

    • New map_db_to_lin_q10() (Pseudocode 29) and map_lin_to_db_q10()
      (Pseudocode 30) — Q.10 fixed-point dB↔linear converters using the
      Annex C.14 SLOPES_DB_TO_LIN / OFFSETS_DB_TO_LIN /
      SLOPES_LIN_TO_DB / OFFSETS_LIN_TO_DB LUTs (already shipped in
      ssf_tables). Out-of-range inputs clamp to the spec's 100 << 10
      (dB→lin) and 40 << 10 (lin→dB) ceilings.
    • New heuristic_scaling() (Pseudocode 28) implements the full
      HeuristicScaling(iRfu, env_in, ...) -> int_weights_dB[] chain:
      dynamic-range compression on env_in[] when the spread exceeds
      the 40 Q.10 threshold, sort-descending of env_local[],
      Map_dB_to_Lin per band, weighted sum scaled by iRfu², reverse
      water-filling to find iTCurrLev, and a final per-band
      Map_Lin_to_dB(iTCurrLev) - env_local[band] weight that's clamped
      to [0, 15 << 10].
    • New apply_heuristic_scaling() (Pseudocode 27) wraps Pseudocode 28
      with the env_in = 3 * env_alloc pre-multiply, the LF-boost
      threshold (i_w_dB[0] knocked down by 3), and the
      env_alloc_mod = (env_alloc - i_w_dB).clamp(ENV_MIN, ENV_MAX) +
      f_gain_q = pow(10, 1.5 / 20 * f_w_dB) post-processing. Returns
      (env_alloc_mod[band], f_gain_q[band]).
    • synthesize_granule() now dispatches the §5.2.5.2.0 selector:
      when f_rfu > 0 && !variance_preserving AND the SSF bandwidths
      table is available, the heuristic-scaling branch fires and
      inverse_heuristic_scale() consumes the resulting f_gain_q[]
      instead of the previous all-1 stub. The variance_preserving
      block also correctly skips the inverse-scale call per
      §5.2.5.2.0 step 5. Pre-r33 the synth crashed (well, silently
      bailed) on f_pred_gain != 0 blocks; now they decode all the way
      through.
    • 11 new lib unit tests (470 → 481):
      map_db_to_lin_zero_input / map_db_to_lin_out_of_range_clamps /
      map_db_to_lin_monotone_within_table /
      map_lin_to_db_zero_input / map_lin_to_db_out_of_range_clamps /
      heuristic_scaling_zero_envelope_yields_zero_weights /
      heuristic_scaling_clamps_to_max /
      apply_heuristic_scaling_short_circuits_on_empty /
      apply_heuristic_scaling_clamps_env_alloc_mod /
      synthesize_granule_runs_with_heuristic_scaling_branch /
      synthesize_granule_variance_preserving_skips_heuristic.
  • Round 32 — SSF SHORT_STRIDE env_prev tracking + walker state
    hoisting
    (TS 103 190-1 §5.2.3.0 Note 2, §5.2.3.0b Pseudocode 4b,
    §4.3.7.4.2 Pseudocodes 54-57):

    • SsfSynthState gains a new env_prev: Vec<i32> field that
      synthesize_granule() latches at the end of each granule with the
      resolved envelope (post-decode_envelope δ-chain), not the raw
      delta symbols. SHORT_STRIDE P-granules now use this latch as the
      env_prev[] interpolation input when the caller doesn't supply
      one — interpolate_envelope no longer degrades to a flat-zero
      envelope across frame boundaries on real P-frame streams.
      Ac4Decoder::run_ssf_channel drops its zero-vector
      state_idx_env_prev stub and passes an empty slice; the synth
      pulls from state.env_prev automatically.
    • Ac4Decoder adopts Vec<SsfChannelState> (one per channel,
      grown on demand) keyed ssf_walker_state. New
      walk_ac4_substream_stateful() and the matching
      _stateful variants of parse_mono_audio_data_outer,
      parse_stereo_audio_data_outer, parse_stereo_data_body, and
      parse_aspx_acpl1_mdct_body thread an
      Option<&mut [SsfChannelState]> through the SSF body parses so
      the walker's dither / noise RNGs (Pseudocodes 54-57) and
      prev_pred_lag_idx / last_num_bands / env_prev (raw symbol
      snapshot) persist across frames. The original public functions
      keep their pre-r32 signatures and delegate with None so the
      sibling repos / test fixtures stay binary-compatible.
    • 4 new lib unit tests (466 → 470):
      synthesize_granule_latches_env_prev (verifies state.env_prev
      holds the post-decode_envelope chain after each granule),
      short_stride_p_frame_uses_state_env_prev (proves a P-granule
      interpolates against the latched I-granule envelope, not zero),
      synthesize_ssf_data_chains_env_prev_across_granules (two-granule
      end-to-end), and
      walk_ac4_substream_stateful_persists_ssf_walker_state
      (round-trip through the substream walker leaves the channel-0
      state's last_num_bands / last_n_mdct / env_prev populated).
  • Round 31 — Speech Spectral Frontend (SSF) PCM synthesis chain (TS
    103 190-1 §5.2.3 / §5.2.4 / §5.2.5 / §5.2.6 / §5.2.7 +
    §5.2.8.1 — Pseudocodes 4a / 4b / 4c / 4d / 4e / 26 / 31 / 32 / 33 /
    34 / 35 / 36 / 37 / 38 / 39):

    • New ssf_synth module turning the per-block indices on
      crate::ssf::SsfData into n_mdct spectral lines per block.
      Functions: decode_envelope (Pseudocode 4a — δ-decode chain over
      env_curr[]), interpolate_envelope (Pseudocode 4b — SHORT_STRIDE
      fixed-point linear interpolation between env_prev[] and the
      current granule's env[]), decode_gains (Pseudocode 4c —
      pow(10, gain_idx * 0.1) per block, LONG_STRIDE clamp to 1.0),
      refine_envelope (Pseudocode 4d — band-≥2 gain application + the
      round(2 * gain_idx / 3) allocation tweak with [-64, 63] clamp).
    • decode_predictor (Pseudocode 4e) reconstructs f_pred_gain from
      PRED_GAIN_QUANT_TAB and f_pred_lag = 640 * 2^((idx - 509)/170),
      with i_prev_pred_lag_idx carried forward on SsfSynthState.
    • compute_helpers (Pseudocode 26 — f_rfu,
      i_alloc_dithering_threshold, adaptive noise gains) +
      build_alloc_table (Pseudocode 31 — no-rfu path:
      env_alloc_mod = env_alloc).
    • inverse_quantize_block (Pseudocode 32) implements all three
      branches: i_alloc == 0 noise-RNG path with the
      variance-preserving band > 1 branch, dithered branch via
      Idx2Reconstruction + POST_GAIN_LUT[i_alloc - 1] + the
      f_post_gain_var_pres = sqrt(post_gain) * f_adaptive_noise_gain_var_pres
      rule, and the no-dither MMSE branch via mmse_laplace
      (Pseudocode 33).
    • inverse_heuristic_scale (Pseudocode 34) — currently a no-op
      because the no-rfu path leaves f_gain_q == 1.
    • build_c_matrix (Pseudocode 39) reconstructs the per-tab_idx
      (2*Rf+1, 65, Rt) prediction-coefficient matrix from the
      quantized bytes in crate::ssf_pred_coeff using the
      1.1787855 * (q - 146) / 128 reconstruction formula and the
      spec's s = (-1)^(k+1) mirror rule for negative-η.
    • SubbandPredictorState::run (Pseudocodes 35 / 36 / 37) maintains
      f_spec_buffer[NUM_SPEC_BUF=5] + f_env_buffer[NUM_ENV_BUF=4]
      histories, runs the model-based extractor (f_period, k_s,
      tab_idx, Z-matrix even-reflection, the per-bin
      Σ_{ν,k} s * C[ν][f][k] * Z[bin+ν][k] summation), then applies
      Pseudocode 37's per-band shaper (f_envelope * f_pred_gain)
      with the I-frame integer_lag = 0 clamp.
    • inverse_flatten (Pseudocode 38) sums f_spec_res + f_spec_pred
      and multiplies by the per-band signal envelope.
    • synthesize_granule() runs the chain across every block in one
      granule; synthesize_ssf_data() runs it across both granules of
      one frame, threading env_prev[] between them.
    • Ac4Decoder adopts Vec<SsfSynthState> (one per channel) and
      consumes tools.ssf_data_primary / tools.ssf_data_secondary
      after the existing ASF/A-CPL pipeline: each granule's
      num_blocks * n_mdct spectrum is split per-block, fed into the
      per-channel KBD-windowed IMDCT + overlap-add, then truncated /
      padded to the frame's sample count. SSF substreams now emit real
      PCM instead of silence.
    • 16 new lib unit tests (450 → 466) covering: empty/empty-tail
      envelope decode, low-band-no-gain refinement, allocation-table
      clamping (min + max), zero-RFU + unit-window helpers, predictor
      presence/absence + delta-lag carry, full C-matrix dimensions
      across all 37 tab_idx values, the negative-η mirror rule, the
      subband-predictor zero-gain pass-through and finite-output smoke
      tests, the per-band envelope-gain inverse-flattening test, and a
      LONG_STRIDE I-granule synthesis end-to-end smoke (synthesize_granule
      on a synthetic granule). Plus one decoder-level integration test
      (ssf_synth_long_stride_iframe_end_to_end) that walks a synthetic
      SSF bitstream through parse_ssf_data then synthesize_ssf_data
      and verifies finiteness + zero-padding past num_bins.
    • Note: §5.2.5.2.2 Pseudocodes 27 / 28 / 29 / 30 (full Heuristic
      Scaling) are deferred — when f_pred_gain == 0 the spec
      short-circuits to env_alloc_mod = env_alloc + f_gain_q = 1,
      which is the no-rfu path landed here. Synthesis of streams that
      enable the predictor across many bands at once with
      variance_preserving == 0 will lose the heuristic envelope
      spreading until a follow-up round.
  • Round 30 — Speech Spectral Frontend (SSF) bitstream walker (TS 103
    190-1 §4.2.9 / §4.3.7 + §4.3.7.5 + Tables 43-46 / 111-113):

    • New ssf module with the four-table walker family:
      parse_ssf_data (Table 43 — b_ssf_iframe gate plus 1 / 2
      granules per frame_length >= 1536), parse_ssf_granule
      (Table 44 — stride_flag, I-frame num_bands_minus12, per-block
      predictor_presence_flag / delta_flag loop), parse_ssf_st_data
      (Table 45 — env_curr_band0_bits, I-frame
      env_startup_band0_bits, per-block gain_bits /
      predictor_lag(_delta)_bits / variance_preserving_flag /
      ...
Read more

v0.0.3

03 May 12:32
1f9795a

Choose a tag to compare

Other

  • skip etsi_table_validation when docs sibling absent
  • replace never-match regex with semver_check = false
  • migrate to centralized OxideAV/.github reusable workflows
  • round 28 — mono / stereo short-frame sf_data(ASF) walker
  • round 27 — 7_X channel-element walker (immersive 7.0 / 7.1)
  • round 26 — add per-codebook decode roundtrip sweeps
  • round 25 — ASPX_ACPL_1 / ASPX_ACPL_2 inner body walker
  • round 24 — grouped multichannel sf_data(ASF) walker + ASPX_ACPL_3 inner body walker
  • round 23 — multichannel sf_data(ASF) Huffman codebook table walk
  • round 22 — ASPX_ACPL_1/2 multichannel wrapper (Pseudocode 117) + 5_X-walker glue
  • round 21 — ASPX_ACPL_3 transform synthesis (Pseudocodes 118/119)
  • round 20 — ETSI Huffman table audit + 5.X cfg0/1/2 + sf_info_lfe
  • round 19 — design 5_X channel-element walker family
  • round 18 — wire ASPX_ACPL_1 joint-MDCT residual layer
  • round 17 — wire A-CPL synthesis into Ac4Decoder
  • adopt slim AudioFrame shape
  • land §5.7.7 A-CPL QMF synthesis math (round-16)
  • outer §4.2.14.1 metadata() walker + §5.7.7.2 sb_to_pb
  • A.4 Huffman codebooks + dialog_enhancement parser
  • A.3 Huffman codebooks + acpl_data_*ch parser
  • A.5 Huffman codebook + drc_frame parser
  • implement complex-covariance TNS (chirp + α0 + α1) — round-11
  • land §5.7.6.4.2.2 A-SPX limiter (P72 + P96..101) — round-10
  • pin release-plz to patch-only bumps

Added

  • Round 28 — mono / stereo short-frame sf_data(ASF) walker (TS 103
    190-1 §4.2.8.3-6 Tables 39-42, §4.3.6.2.6 Pseudocodes 2/3/5):

    • New spec-correct _grouped payload parsers in asf_data.rs
      parse_asf_section_data_grouped(),
      parse_asf_spectral_data_grouped(),
      parse_asf_scalefac_data_grouped(),
      parse_asf_snf_data_grouped() — each takes per-group transform-
      length and max_sfb arrays and walks the spec's outer
      for (g = 0; g < num_window_groups; g++) loop. Critically:
      asf_scalefac_data() consumes a single 8-bit
      reference_scale_factor at the head with first_scf_found shared
      across groups (DPCM state is continuous over the whole frame), and
      asf_snf_data() consumes a single 1-bit b_snf_data_exists
      gate at the head. This matches Tables 41 / 42 verbatim.
    • New helpers in asf.rs:
      derive_per_group() / derive_per_group_with_max_sfb() resolve
      per-group (transf_length_idx, transform_length, max_sfb) from
      (AsfTransformInfo, AsfPsyInfo) per Pseudocodes 2 (get_transf_length)
      and 5 (get_max_sfb), including the b_different_framing
      half-frame split (Pseudocode 3's grouping-bit shift +
      num_windows_0 - 1 boundary injection).
    • New body decoders:
      • decode_asf_grouped_mono_body[_with_max_sfb]() — wraps the four
        _grouped payload parsers; returns the per-group dequantised
        spectra concatenated group-major.
      • decode_asf_grouped_stereo_joint_body() — joint-MDCT residual
        layer with shared section, two independent spectral bodies (L/M
        then R/S), shared scalefactors (band-wise max_quant_idx over
        both channels), per-group ms_used[g][sfb] flag arrays, then
        snf. Inverse M/S applied per-group: L = M+S, R = M-S for bands
        with ms_used set.
      • decode_asf_mono_body_dispatch() /
        decode_asf_mono_body_for_max_sfb() — long-frame vs grouped
        dispatch wrappers used by all per-channel call sites.
    • Wired into the four mono / stereo call sites:
      • parse_mono_audio_data_outer() — mono SIMPLE / ASPX path.
      • parse_aspx_acpl2_mdct_body() — single-channel ASPX_ACPL_2
        MDCT residual.
      • parse_aspx_acpl1_mdct_body() joint + split — ASPX_ACPL_1
        joint-MDCT residual layer (two independent mono bodies with
        max_sfb_0 / max_sfb_side_0) and the split case.
      • parse_stereo_data_body() joint + split — stereo CPE body
        with both joint MDCT (shared section + ms_used) and split MDCT
        (two independent mono bodies).
    • Real Dolby AC-4 mono / stereo streams that include short-frame
      sf_data(ASF) (i.e. the encoder picks short-window sub-frames)
      now decode end-to-end without bailing at the
      num_window_groups != 1 guard. The grouped multichannel walker
      in mch.rs from r24 (per-group interleaved
      section + spectral + scalefac + snf) is left untouched — its
      pinned tests continue to pass.
    • 9 new tests: 4 in asf_data.rs (grouped section / scalefac
      reference-once / scalefac DPCM-state-carries / snf gate-once) and
      5 in asf.rs (decode_asf_grouped_mono two-group + truncated;
      parse_mono_audio_data_outer SIMPLE short-frame; parse_stereo_data_body
      split + joint short-frame). 425 tests (414 lib + 5 + 6
      integration), up from 416.
  • Round 27 — 7_X channel-element walker (immersive 7.0 / 7.1)
    (TS 103 190-1 §4.2.6.14 Table 33 + §4.3.5.7 Table 98):

    • New parse_7x_audio_data_outer() walker in mch.rs plus a
      SevenXCodecMode enum (Table 98 — 2 bits, 4 codepoints: SIMPLE /
      ASPX / ASPX_ACPL_1 / ASPX_ACPL_2; no ASPX_ACPL_3 in 7.X). The
      walker mirrors the 5_X SIMPLE/ASPX path's coding_config selector
      but with the 7.X-specific shape:
      • 2-bit 7_X_codec_mode (vs 3-bit for 5_X — no Reserved values).
      • LFE mono_data(1) gated on channel_mode == "7.1" (mapped from
        the parent substream's channel count: 7 → 7.0, 8 → 7.1).
      • companding_control(5) for ASPX_ACPL_{1,2} only — SIMPLE/ASPX in
        7.X have no leading companding (different from 5_X where ASPX
        gets companding_control(5)).
      • Cfg0 body: 2ch_mode + two_channel_data + two_channel_data (no
        centre mono inside the switch).
      • Cfg2 body: four_channel_data only (no surround mono inside the
        switch). Both centre / surround monos move out to a single
        trailing mono_data(0) call gated on coding_config in {0, 2},
        placed after the additional-channel block.
      • SIMPLE/ASPX-only additional-channel block: 1-bit
        b_use_sap_add_ch gating optional chparam_info()×2, then a
        mandatory two_channel_data() for the front-extension /
        back-surround pair beyond the 5.X core. Lands in new
        tools.seven_x_b_use_sap_add_ch,
        tools.seven_x_add_chparam_info and
        tools.seven_x_additional_channel_data slots.
      • ASPX_ACPL_1-only joint-MDCT residual layer (max_sfb_master +
        chparam_info×2 + sf_data×2) — same shape as the 5_X path,
        n_side_bits derived per the Table 33 NOTE from the largest
        signalled transform length across all preceding
        two_channel_data / three_channel_data / four_channel_data /
        five_channel_data (including the additional-channel one when
        it's the largest).
      • Trailers: aspx_data_2ch×2 + aspx_data_1ch for any non-SIMPLE,
        plus an extra aspx_data_2ch for ASPX (covering the additional
        pair); acpl_data_1ch×2 for ASPX_ACPL_{1,2} landing in
        tools.acpl_data_1ch_pair[0/1] (shared with the 5_X
        §5.7.7.6.1 pair walker).
    • walk_ac4_substream now dispatches channels == 7 (7.0) and
      channels == 8 (7.1) into the new walker. Previously these
      channel counts fell through to the catch-all that just records
      channel_mode_channels and bails — real Dolby AC-4 streams using
      a 7_X_channel_element now parse end-to-end without hitting the
      catch-all.
    • Walker is try-and-bail with the same contract as the 5_X
      walker: any inner Huffman / parse miss surfaces Ok(()) to the
      caller, leaving already-populated tools.* slots intact. The
      deeper aspx_data / acpl_data trailers are gated on
      b_iframe && tools.aspx_config.is_some().
    • 11 new lib tests (394 → 405 total): SIMPLE Cfg3 (no SAP), 7.1
      SIMPLE LFE walk, SIMPLE Cfg0 (two pairs + trailing centre mono),
      SIMPLE Cfg2 (four-channel + back surround mono), SIMPLE Cfg1 (no
      trailer), SIMPLE with b_use_sap_add_ch == 1 (chparam pair
      populated), ASPX_ACPL_2 non-iframe Cfg1 (no additional-channel
      block), ASPX_ACPL_1 I-frame Cfg0 (residual layer + Cfg0 trailer),
      ASPX_ACPL_1 zero max_sfb_master bails silently, truncated
      SIMPLE five_channel_data bails silently, and
      SevenXCodecMode::from_u32 round-trip.
  • Round 25 — ASPX_ACPL_1 / ASPX_ACPL_2 inner body walker
    (TS 103 190-1 §4.2.6.6 Table 25 + §5.7.7.6.1 Pseudocode 117):

    • New parse_aspx_acpl_1_2_inner_body() helper in mch.rs walks the
      bits past the existing companding_control(3) + 1-bit coding_config selector for the 5_X ASPX_ACPL_1 / ASPX_ACPL_2
      paths. The body shape (Table 25):
      two_channel_data() OR three_channel_data()
      [ASPX_ACPL_1 only] max_sfb_master (n_side_bits) + chparam_info()×2 + sf_data(ASF)×2 joint-MDCT residual layer →
      [coding_config==0 only] mono_data(0) centre/surround trailer →
      aspx_data_2ch() + aspx_data_1ch() + acpl_data_1ch()×2.
      The two acpl_data_1ch payloads land in
      tools.acpl_data_1ch_pair[0] (D0-side) and
      tools.acpl_data_1ch_pair[1] (D1-side) per Pseudocode 117 — the
      same pair the §5.7.7.6.1 run_acpl_5x_pair_pcm() PCM driver
      consumes.
    • n_side_bits is derived per the §4.2.6.6 NOTE: largest signalled
      transform length from the preceding two_channel_data() /
      three_channel_data() (look up tables::n_msfb_bits_48 Table 106
      column 2). The joint-MDCT residual sf_data bodies reuse
      decode_asf_long_mono_body_with_max_sfb() against a synthesised
      long-frame AsfTransformInfo at the dominant transform length.
    • The walker is try-and-bail: every step returns Ok(()) to the
      outer walker on any inner Huffman / parse miss, leaving the
      already-populated tools.* slots intact (matching the round-24
      ASPX_ACPL_3 walker contract). Deeper aspx_data / acpl_data steps
      are gated on b_iframe && tools.aspx_config.is_some()
      non-iframe paths simply consume what they can of the upstream
      channel data and stop.
    • Active acpl_config_1ch for the pair-extraction step is...
Read more

v0.0.2

25 Apr 07:41
48903e1

Choose a tag to compare

Other

  • fix clippy 1.95 lints
  • drop oxideav-codec/oxideav-container shims, import from oxideav-core
  • wire §5.7.6.4.3 noise + §5.7.6.4.4 tone into aspx_extend_pcm (round-9)
  • end-to-end FFT probe for A-SPX noise + tone HF injection
  • wire §5.7.6.4.2 per-envelope HF envelope adjustment (P90+P91+P95)
  • land §5.7.6.4.3 noise + §5.7.6.4.4 tone generators
  • land §5.7.4 QMF synthesis + A-SPX HF regen pipeline (round-7)
  • add §5.7.3 QMF analysis scaffold — QWIN + single-slot transform
  • derive §5.7.6.3.1 master-freq-scale and wire aspx_ec_data()
  • wire aspx_delta_dir + effective qmode into substream walker
  • transcribe all 18 A-SPX Huffman tables + aspx_ec_data() walker
  • A-SPX Huffman scaffolding + Annex A.2 table metadata
  • implement aspx_hfgen_iwc_2ch() per Table 56
  • implement aspx_hfgen_iwc_1ch() per Table 55
  • parse aspx_delta_dir() per-channel delta-direction bits
  • wire aspx_framing into the ASF substream walker
  • parse aspx_framing() end-to-end for all four interval classes
  • parse aspx_config + companding_control sidecar
  • stereo joint M/S test + refresh lib-level doc
  • stereo CPE decoder test — different tones on L and R
  • stereo CPE body decode (split + joint M/S) and per-channel IMDCT
  • refresh lib-level doc for new coefficient pipeline
  • wire ASF data path into decoder — real mono PCM output
  • Huffman-driven ASF data parsers and dequantisation
  • implement IMDCT + KBD window + overlap-add
  • add sfb_offset tables for 48 kHz family (Annex B.4-B.7)
  • transcribe ASF Huffman codebooks and add decoder helpers
  • document sfb_offset tables (B.4/B.5/B.6) as next-up work
  • parse asf_psy_info + Annex B num_sfb / Table 106 n_msfb_bits
  • land ASF substream walker (ac4_substream + audio_data outer layers)
  • switch workflows to master branch

v0.0.1

23 Apr 21:23

Choose a tag to compare

chore: Release package oxideav-ac4 version 0.0.1