Releases: OxideAV/oxideav-ac4
v0.0.7
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'saspx::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).
Newencoder_acpl3::write_aspx_hfgen_iwc_1ch/
write_aspx_hfgen_iwc_2chtake real per-SBGtna_mode(2 b, masked
to0..=3) plus per-SBG / per-timeslot flag vectors via the public
encoder_acpl3::AspxHfgenIwc1ChPayload/
encoder_acpl3::AspxHfgenIwc2ChPayloadpayloads, and auto-derive
every gate from the payload (*_present/*_left/*_rightset
iff the slice has an active flag in range; the 2ch TIC path uses the
compactaspx_tic_copy = 1form when both channels carry the same
active pattern). Underaspx_balance = 1only channel-0tna_mode
is written (decoder mirrors it); short caller slices zero-pad. The
existingwrite_aspx_data_1ch_minimalHFGEN 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.rspin 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 thedirection_time == true
branch of the decoder'saspx::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
reconstructingqscf[sbg][atsg] = prev[sbg] + delta·values[sbg]
(withprevthe previous envelope's row, orqscf_prev_lastfor the
first envelope). Newencoder_acpl3::time_dpcm_encode_qscfinverts it
exactly (values[sbg] = (qscf[sbg] − prev[sbg]) / delta), with
zero-extend-short-prevand±1-step semantics matching the decoder
(delta = 0treated as1for totality). New
encoder_acpl3::dpcm_encode_qscf_envelopespacks 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_freqreproduces the legacy
single-direction scaffold). Twelve integration tests
(tests/round292_aspx_time_direction_dpcm.rs) pin the bit-exact
round-trip through bothdelta_decode_siganddelta_decode_noise,
step/totality edges, short-prevzero-extension, the min-L1 policy,
force_freqparity withfreq_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 outputy₂; step 10 + step 11 give the centre channel
a wet contributionC_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,
soE[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_residualenergy-
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)
withbeta3_delta = 0.125Fine /0.25Coarse and the symmetric
±cb_offclamp 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 writerswrite_acpl_beta3_f0_value/write_acpl_beta3_df_value
mirror the round-208 γ writers (symbol_index = q + cb_off
addressing); a new fullacpl_data_2ch()emitter
write_acpl_data_2ch_real_alpha_beta_full_gamma_beta3lifts 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_scaledecision 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.0reproduces 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 atbeta3_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-bandbeta3_qrow through
parse_5x_audio_data_outer+differential_decode, IMS
byte-equality with the round-215 entry atbeta3_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-271select_alpha_q_for_pairdecision 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_pairruns the
least-squaresalpha_qdecision 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_qbuilder, falling back to
the header-onlySapMode::Nonerow when no band raises
sap_coeff_used. The pickedalpha_qis 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.
Newencoder_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: thetwo_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'sapply_sap_table_181forward mix reproduces the requested
(L, R, Ls, Rs)preliminaries exactly (up to sf_data quantisation).
Measured: forLs = κ·Lcorrelated 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-Lsresidual — while a no-benefit input
(`L...
v0.0.6
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]) emittedaspx_int_class = FIXFIX
as the wrong prefix code:0b11(2 bits) instead of0b0(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's11start signalled the parser to read the VarFix /
VarVar branch instead. For our config that put the parser in the
VarFix branch withb_iframe = 1: it then read
var_bord_left(2 b),num_rel_left(2 b —num_aspx_timeslots = 15 > 8makes Note-1 fields 2-bit wide), andtsg_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
⇒ theacpl_data_1chbody shape was constant minimum-cost on each
side and thenum_param_sets_codbit positions on both sides
sampled0within the long run of zero codewords). With non-zero
α / β the codewords shift, the pair-1num_param_sets_codbit
position lands on a1, and pair1 readsnum_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 toacpl1_full_round_trips_with_aligned_pair_lengths
and its assertion flipped fromassert_eq!(n1, 2)(pinned
misalignment) toassert_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).
- The r187 test #4 (
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.rssweep
the encoder'sencode_frame_pcm_5_0_acpl1_real_alpha_betaacross
four input combinations and assert the decoder's recovered
acpl_data_1ch_pair[0/1].framing.num_param_setsso 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; thewrite_two_channel_datacarrier writer
is exercised non-trivially while α / β stay quantised to 0
(correlationΣ L · Ls = 0⇒ α extractor returns 0; surround
energyE[Ls²] = 0⇒ β extractor returns 0). - Ls-residual-only (
L = R = 0) → both pair slots still resolve
num_param_sets = 1; thewrite_acpl_1_residual_layerjoint-MDCT
residual writer is exercised non-trivially withmax_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 resolvesnum_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_beta↔parse_acpl_data_1ch
are bit-exact in isolation (pinned by
round181_alpha_desync_fix::standalone_*); back-to-back
invocations into the sameBitWriterwithout 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
insideparse_aspx_acpl_1_2_inner_body's ASPX_ACPL_1 branch, or
in thetwo_channel_data()L/R carrier writer vs
parse_two_channel_data— when L and Ls are simultaneously
non-trivial. Total tests 784 (was 780).
- Silence (all-zero PCM) → both pair slots resolve
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 F0cb_offfix
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'sfor (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 bystart_bandparameter
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_bandsvector indexed by full param-band number — the
spec-alignedacpl_<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, …)readsnum_sbg_sig_lowresSIGNAL bands
when the correspondingaspx_freq_res[env]bit was emitted as 0
andnum_sbg_sig_highreswhen it was 1 or absent (when
freq_res_mode != Signalledthe encoder writes no in-band
aspx_freq_resbit 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-codednum_sbg_sig_lowresregardless — so for the
encoder's defaultfreq_res_mode = DurationDependentconfig
(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
subsequentacpl_data_1ch()α / β codeword in trailing
zero-padding, recovered as length-num_param_bandsall-zero
rows. r181 keys the SIGNAL band count offcfg.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 — confirmsvalues[0..start_band) == 0and
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, assertacpl_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'sdecode_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.
- Layer 1 — §4.2.13.7 Table 65 / §5.7.7.7 Pseudocode 121 parser
-
Round 174 — ALPHA / BETA3 F0 codebook
cb_offcorrected per ETSI
TS 103 190-1 §A.3 Tables A.34 / A.35 (ALPHA) and A.46 / A.47 (BETA3).- Pre-fix
cb_off = 0for ALPHA / BETA3 F0 conflicted with the §5.7.7.7
Pseudocode 121 ...
- Pre-fix
v0.0.5
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
linkmedep - 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'sn_sect_bits=3 / esc=7
long-frame layout: 7 bits forL ∈ 1..=7, 10 bits forL ∈ 8..=14,
13 bits forL ∈ 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::MAXfor 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 anAsfSections
suitable for the existingwrite_section_data/
write_spectral_data_sectionsemitters.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 whosesnf_gain = 2^((idx*1.5 - 84)/4)best matches.
ReturnsSome(per_band_idx)when at least one zero-quant band
has measurable energy;Nonefor fully-silent input.encoder_asf::write_snf_data(bw, snf, sfb_cb, max_quant_idx, max_sfb)
— emitsb_snf_data_exists(1 bit) plus per-zero-quant-band
HCB_SNF Huffman codewords per Table 42 / Pseudocode 105. Round-trips
cleanly through the existingparse_asf_snf_datadecoder 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_sfbnow 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 whoseq_maxcovers 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-20n_extextension 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 singleAsfSections
entry so the emittedasf_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 walkssect_start..sect_endbins with the
section's codebook, handlescb == 0silent bands, and writes
Pseudocode 20 escape bits for HCB11 outliers.Ac4ImsEncoder::encode_frame_pcm_with_max_sfb(frame, max_sfb)—
new public entry point parameterisingmax_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 ofmax_sfb=40for 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=55preserves >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'smdct::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).EncoderMdctStatecarries the previous-frame
NPCM 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 solvesf_min = ceil(100 + 4*log2(max_abs/q_max^(4/3)))
keeping every quantised line within HCB5's ±4 magnitude bound
afterq = round(sign(c)*|c/sf_gain|^(3/4)). Single-section
HCB5 emission across0..max_sfb; reference scalefactor- DPCM-coded per-band deltas via HCB_SCALEFAC;
b_snf_data_exists = 0.
- DPCM-coded per-band deltas via HCB_SCALEFAC;
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-encoderEncoderMdctStateon first
call; bumpssequence_countermodulo 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 viahuff_decode+split_qspec;
build_mono_simple_asf_body_from_pcm_spectrumend-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_pcmbumps
sequence_counterper call.
- sine-wave SNR > 40 dB; quantise/dequantise round-trip;
- 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-validraw_ac4_frame()payload
with the IMS-flavo...
v0.0.4
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) andmap_lin_to_db_q10()
(Pseudocode 30) — Q.10 fixed-point dB↔linear converters using the
Annex C.14SLOPES_DB_TO_LIN/OFFSETS_DB_TO_LIN/
SLOPES_LIN_TO_DB/OFFSETS_LIN_TO_DBLUTs (already shipped in
ssf_tables). Out-of-range inputs clamp to the spec's100 << 10
(dB→lin) and40 << 10(lin→dB) ceilings. - New
heuristic_scaling()(Pseudocode 28) implements the full
HeuristicScaling(iRfu, env_in, ...) -> int_weights_dB[]chain:
dynamic-range compression onenv_in[]when the spread exceeds
the 40 Q.10 threshold, sort-descending ofenv_local[],
Map_dB_to_Linper band, weighted sum scaled byiRfu², reverse
water-filling to findiTCurrLev, 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 theenv_in = 3 * env_allocpre-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:
whenf_rfu > 0 && !variance_preservingAND the SSF bandwidths
table is available, the heuristic-scaling branch fires and
inverse_heuristic_scale()consumes the resultingf_gain_q[]
instead of the previous all-1 stub. Thevariance_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) onf_pred_gain != 0blocks; 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.
- New
-
Round 32 — SSF SHORT_STRIDE
env_prevtracking + 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):SsfSynthStategains a newenv_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_envelopeno longer degrades to a flat-zero
envelope across frame boundaries on real P-frame streams.
Ac4Decoder::run_ssf_channeldrops its zero-vector
state_idx_env_prevstub and passes an empty slice; the synth
pulls fromstate.env_prevautomatically.Ac4DecoderadoptsVec<SsfChannelState>(one per channel,
grown on demand) keyedssf_walker_state. New
walk_ac4_substream_stateful()and the matching
_statefulvariants ofparse_mono_audio_data_outer,
parse_stereo_audio_data_outer,parse_stereo_data_body, and
parse_aspx_acpl1_mdct_bodythread 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 withNoneso the
sibling repos / test fixtures stay binary-compatible.- 4 new lib unit tests (466 → 470):
synthesize_granule_latches_env_prev(verifiesstate.env_prev
holds the post-decode_envelopechain 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'slast_num_bands/last_n_mdct/env_prevpopulated).
-
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_synthmodule turning the per-block indices on
crate::ssf::SsfDatainton_mdctspectral lines per block.
Functions:decode_envelope(Pseudocode 4a — δ-decode chain over
env_curr[]),interpolate_envelope(Pseudocode 4b — SHORT_STRIDE
fixed-point linear interpolation betweenenv_prev[]and the
current granule'senv[]),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) reconstructsf_pred_gainfrom
PRED_GAIN_QUANT_TABandf_pred_lag = 640 * 2^((idx - 509)/170),
withi_prev_pred_lag_idxcarried forward onSsfSynthState.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 == 0noise-RNG path with the
variance-preservingband > 1branch, 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 viammse_laplace
(Pseudocode 33).inverse_heuristic_scale(Pseudocode 34) — currently a no-op
because the no-rfu path leavesf_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 incrate::ssf_pred_coeffusing the
1.1787855 * (q - 146) / 128reconstruction formula and the
spec'ss = (-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-frameinteger_lag = 0clamp.inverse_flatten(Pseudocode 38) sumsf_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, threadingenv_prev[]between them.Ac4DecoderadoptsVec<SsfSynthState>(one per channel) and
consumestools.ssf_data_primary/tools.ssf_data_secondary
after the existing ASF/A-CPL pipeline: each granule's
num_blocks * n_mdctspectrum 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 37tab_idxvalues, 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 throughparse_ssf_datathensynthesize_ssf_data
and verifies finiteness + zero-padding pastnum_bins. - Note: §5.2.5.2.2 Pseudocodes 27 / 28 / 29 / 30 (full Heuristic
Scaling) are deferred — whenf_pred_gain == 0the spec
short-circuits toenv_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 == 0will lose the heuristic envelope
spreading until a follow-up round.
- New
-
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
ssfmodule with the four-table walker family:
parse_ssf_data(Table 43 —b_ssf_iframegate plus 1 / 2
granules perframe_length >= 1536),parse_ssf_granule
(Table 44 —stride_flag, I-framenum_bands_minus12, per-block
predictor_presence_flag/delta_flagloop),parse_ssf_st_data
(Table 45 —env_curr_band0_bits, I-frame
env_startup_band0_bits, per-blockgain_bits/
predictor_lag(_delta)_bits/variance_preserving_flag/
...
- New
v0.0.3
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
_groupedpayload parsers inasf_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 andmax_sfbarrays 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_factorat the head withfirst_scf_foundshared
across groups (DPCM state is continuous over the whole frame), and
asf_snf_data()consumes a single 1-bitb_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 theb_different_framing
half-frame split (Pseudocode 3's grouping-bit shift +
num_windows_0 - 1boundary injection). - New body decoders:
decode_asf_grouped_mono_body[_with_max_sfb]()— wraps the four
_groupedpayload 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-groupms_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 != 1guard. The grouped multichannel walker
inmch.rsfrom 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 inasf.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.
- New spec-correct
-
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 inmch.rsplus a
SevenXCodecModeenum (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 onchannel_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
getscompanding_control(5)).- Cfg0 body:
2ch_mode + two_channel_data + two_channel_data(no
centre mono inside the switch). - Cfg2 body:
four_channel_dataonly (no surround mono inside the
switch). Both centre / surround monos move out to a single
trailingmono_data(0)call gated oncoding_config in {0, 2},
placed after the additional-channel block. - SIMPLE/ASPX-only additional-channel block: 1-bit
b_use_sap_add_chgating optionalchparam_info()×2, then a
mandatorytwo_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_infoand
tools.seven_x_additional_channel_dataslots. - 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_bitsderived 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_1chfor any non-SIMPLE,
plus an extraaspx_data_2chfor ASPX (covering the additional
pair);acpl_data_1ch×2for 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).
- 2-bit
walk_ac4_substreamnow dispatcheschannels == 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_channelsand bails — real Dolby AC-4 streams using
a7_X_channel_elementnow 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 surfacesOk(())to the
caller, leaving already-populatedtools.*slots intact. The
deeperaspx_data/acpl_datatrailers 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 withb_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 zeromax_sfb_masterbails silently, truncated
SIMPLE five_channel_data bails silently, and
SevenXCodecMode::from_u32round-trip.
- New
-
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 inmch.rswalks the
bits past the existingcompanding_control(3) + 1-bit coding_configselector for the 5_XASPX_ACPL_1/ASPX_ACPL_2
paths. The body shape (Table 25):
two_channel_data()ORthree_channel_data()→
[ASPX_ACPL_1 only]max_sfb_master (n_side_bits) + chparam_info()×2 + sf_data(ASF)×2joint-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.1run_acpl_5x_pair_pcm()PCM driver
consumes. n_side_bitsis derived per the §4.2.6.6 NOTE: largest signalled
transform length from the precedingtwo_channel_data()/
three_channel_data()(look uptables::n_msfb_bits_48Table 106
column 2). The joint-MDCT residual sf_data bodies reuse
decode_asf_long_mono_body_with_max_sfb()against a synthesised
long-frameAsfTransformInfoat 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-populatedtools.*slots intact (matching the round-24
ASPX_ACPL_3 walker contract). Deeper aspx_data / acpl_data steps
are gated onb_iframe && tools.aspx_config.is_some()—
non-iframe paths simply consume what they can of the upstream
channel data and stop. - Active
acpl_config_1chfor the pair-extraction step is...
- New
v0.0.2
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
chore: Release package oxideav-ac4 version 0.0.1