oxideav_ac4/lib.rs
1//! Pure-Rust Dolby AC-4 audio decoder foundation.
2//!
3//! AC-4 is a complex, hierarchical codec — multiple presentations,
4//! nested substream descriptors, ASF/ASF-A2/A-SPX coefficient streams
5//! driven by huffman-coded scalefactor data, plus an EMDF metadata
6//! sidecar carrying DRC / downmix / dialog-norm info. Full decode is
7//! weeks of work.
8//!
9//! What this crate lands today (per ETSI TS 103 190-1 V1.4.1):
10//!
11//! * **Sync framing** — `ac4_syncframe()` from Annex G: `0xAC40` plain
12//! and `0xAC41` CRC-protected, 16-bit `frame_size()` with 24-bit
13//! escape, plus a standalone CRC-16 helper. See [`sync`].
14//! * **Table of contents** — full `ac4_toc()` walker in [`toc`]:
15//! bitstream_version, sequence_counter, fs_index, frame_rate_index,
16//! b_iframe_global, payload_base, per-presentation
17//! `ac4_presentation_info()` (single / multi-substream, configs 0..=5
18//! plus extension escape, HSF extension, pre-virtualised flag, extra
19//! EMDF substreams), per-substream `ac4_substream_info()`
20//! (channel_mode prefix decoder, sf_multiplier, bitrate_indicator,
21//! content_type w/ language tag, b_iframe),
22//! `substream_index_table()` byte sizes, and the `variable_bits(n)`
23//! codec.
24//! * **Decoder** — [`decoder::Ac4Decoder`] accepts either a sync-wrapped
25//! packet (`0xAC40` / `0xAC41` prefix) or a bare MP4-style
26//! `raw_ac4_frame` payload, parses the TOC, and emits a silent S16
27//! `AudioFrame` with the correct channel count, sample rate, and
28//! samples-per-frame for the stream configuration.
29//!
30//! * **ASF substream walker** — [`asf::walk_ac4_substream`] reads
31//! `ac4_substream()` (audio_size + variable_bits extension), the
32//! mono/stereo outer `audio_data()` layers (mono_codec_mode /
33//! stereo_codec_mode, spec_frontend, b_enable_mdct_stereo_proc),
34//! `asf_transform_info()` (Tables 99 / 100 / 103) and
35//! `asf_psy_info()` (Table 106 n_msfb_bits + Tables 109/110
36//! n_grp_bits). Surfaces the result through
37//! [`decoder::Ac4Decoder::last_substream`] so downstream tooling can
38//! see the frame's tool mix and MDCT window grouping without touching
39//! Huffman state.
40//!
41//! * **Coefficient pipeline** — [`huffman`] carries the normative
42//! ASF_HCB_SCALEFAC / ASF_HCB_SNF / ASF_HCB_1..11 tables from Annex
43//! A (plus CB_DIM / UNSIGNED_CB); [`sfb_offset`] carries the
44//! Annex B.4-B.7 scale-factor-band offset vectors for the 48 kHz
45//! family. [`asf_data`] walks `asf_section_data()`,
46//! `asf_spectral_data()` (with Pseudocode-19 dim=2/dim=4 split and
47//! Pseudocode-20 codebook-11 extension code),
48//! `asf_scalefac_data()` (dpcm-over-reference scale factors with
49//! `sf_gain = 2^((sf-100)/4)`), and `asf_snf_data()`.
50//! `dequantise_and_scale()` applies `rec_spec = sign(q)|q|^(4/3)`
51//! then multiplies by the band gain.
52//! * **MDCT** — [`mdct`] implements the reference AC-4 IMDCT (§5.5.2
53//! pseudocodes 60-64, naive O(N^2) complex DFT) plus the KBD
54//! window family (§5.5.3, alphas from Table 186) with overlap-add.
55//!
56//! * **A-SPX configuration** — [`aspx::parse_aspx_config`] implements
57//! the 15-bit `aspx_config()` element (Table 50, §4.2.12.1) and
58//! [`aspx::parse_companding_control`] the `companding_control()`
59//! element (Table 49, §4.2.11). The outer `audio_data()` walker in
60//! [`asf`] now consumes these for the mono ASPX, stereo ASPX, and
61//! stereo ASPX_ACPL_{1,2} I-frame paths. For ASPX_ACPL_{1,2} it now
62//! also reads the trailing `acpl_config_1ch(PARTIAL)` /
63//! `acpl_config_1ch(FULL)` element (§4.2.13.1 Table 59) via
64//! [`acpl::parse_acpl_config_1ch`]. Exposes the parsed
65//! `AspxConfig` through
66//! [`decoder::Ac4Decoder::last_substream`]`.tools.aspx_config` and
67//! the A-CPL configs through `acpl_config_1ch_partial` /
68//! `acpl_config_1ch_full` on [`asf::SubstreamTools`].
69//! * **A-SPX framing** — [`aspx::parse_aspx_framing`] implements
70//! `aspx_framing()` (Table 53, §4.2.12.4) end-to-end for all four
71//! interval classes (FIXFIX / FIXVAR / VARFIX / VARVAR) including
72//! the 1/2/3-bit `aspx_int_class` prefix code, the envelope-count
73//! derivation for FIXFIX (`1 << tmp_num_env` with
74//! `envbits = aspx_num_env_bits_fixfix + 1`), Note-1 1-vs-2-bit
75//! field widths driven by `num_aspx_timeslots`, the
76//! `aspx_tsg_ptr` sizing via `ceil(log2(num_env + 2))`, and the
77//! I-frame gate on `aspx_var_bord_left` for VARFIX / VARVAR.
78//! Returns the full [`aspx::AspxFraming`] (int_class, num_env,
79//! num_noise, freq_res vector, border fields, tsg_ptr). Wired into
80//! `asf::walk_ac4_substream` for both the mono ASPX and stereo
81//! ASPX I-frame paths: after `companding_control()` and the
82//! `mono_data()` / `stereo_data()` body the walker reads
83//! `aspx_xover_subband_offset` (3 bits) and then `aspx_framing(0)`
84//! (and, for stereo, `aspx_balance` + conditional
85//! `aspx_framing(1)`). `num_aspx_timeslots` for the Note-1 field
86//! width comes from the TOC's `frame_length` via
87//! [`aspx::num_aspx_timeslots`] (Table 189 × Table 192).
88//! * **A-SPX delta direction** — [`aspx::parse_aspx_delta_dir`]
89//! implements `aspx_delta_dir(ch)` (Table 54, §4.2.12.5): one bit
90//! per signal envelope plus one bit per noise envelope. The per-
91//! channel `AspxDeltaDir` drives which `ASPX_HCB_*_{F0,DF,DT}`
92//! codebook the matching `aspx_ec_data()` path will pull from.
93//! * **A-SPX HF generation / interleaved-waveform coding (mono)** —
94//! [`aspx::parse_aspx_hfgen_iwc_1ch`] implements Table 55
95//! (§4.2.12.6): per-subband-group `tna_mode`, `ah_present` +
96//! conditional `add_harmonic[]`, `fic_present` + conditional
97//! `fic_used_in_sfb[]`, and `tic_present` + conditional
98//! `tic_used_in_slot[]`. Takes `num_sbg_noise`,
99//! `num_sbg_sig_highres`, `num_aspx_timeslots` from the caller.
100//! * **A-SPX HF generation / interleaved-waveform coding (stereo)** —
101//! [`aspx::parse_aspx_hfgen_iwc_2ch`] implements Table 56
102//! (§4.2.12.7). Adds per-channel `tna_mode[ch][]` (with
103//! `aspx_balance == 1` mirroring channel 0 into channel 1),
104//! per-channel `aspx_ah_left` / `_right` gates, `aspx_fic_present`
105//! plus per-channel `fic_left` / `fic_right` gates, and the
106//! `aspx_tic_copy` / `aspx_tic_left` / `aspx_tic_right` TIC
107//! gating (including mirroring left-channel TIC into right when
108//! `tic_copy` is set).
109//!
110//! * **A-SPX Huffman infrastructure** — [`aspx::AspxHcb`] is a
111//! `(len[], cw[], cb_off)` codebook helper: the symbol decoder walks
112//! one bit at a time until a `(len == width, cw == code)` match
113//! lands, then returns `symbol_index - cb_off` as the delta. All 18
114//! Annex A.2 codebooks (Tables A.16..=A.33) are transcribed in
115//! [`aspx_huffman`] — six `(F0, DF, DT)` triples covering
116//! envelope-LEVEL / envelope-BALANCE @ 1.5 dB / 3 dB plus
117//! noise-LEVEL / noise-BALANCE. A [`aspx::HuffmanCodebookId`] enum
118//! plus [`aspx::lookup_aspx_hcb`] resolve the
119//! `get_aspx_hcb(data_type, quant_mode, stereo_mode, hcb_type)`
120//! tuple from §5.7.6.3.4 Pseudocode 79.
121//! * **A-SPX entropy coded data** — [`aspx::parse_aspx_ec_data`]
122//! implements `aspx_ec_data()` (Table 57, §4.2.12.8) on top of
123//! [`aspx::parse_aspx_huff_data`] (Table 58) — per-envelope loop
124//! that picks F0/DF/DT codebook per direction and returns a vector
125//! of [`aspx::AspxHuffEnv`]s.
126//! * **A-SPX master freq-scale derivation** —
127//! [`aspx::derive_aspx_frequency_tables`] implements §5.7.6.3.1
128//! Pseudocodes 67, 68, 69 and 70: picks between
129//! [`aspx::ASPX_SBG_TEMPLATE_HIGHRES`] and
130//! [`aspx::ASPX_SBG_TEMPLATE_LOWRES`] by `aspx_master_freq_scale`,
131//! trims with `aspx_start_freq` / `aspx_stop_freq` into the master
132//! subband-group table, then applies `aspx_xover_subband_offset` to
133//! produce the high-res / low-res signal tables. The noise
134//! subband-group table follows Pseudocode 70's `max(1,
135//! floor(aspx_noise_sbg * log2(sbz/sbx) + 0.5))` count rule and is
136//! clamped to `num_sbg_noise <= 5`. Returns
137//! [`aspx::AspxFrequencyTables`] containing master / high-res /
138//! low-res / noise border tables plus `sba`, `sbz`, `sbx`,
139//! `num_sb_aspx` and an [`aspx::AspxSbgCounts`] ready to feed
140//! [`aspx::parse_aspx_ec_data`].
141//! * **Full A-SPX data-path wiring** — `asf::walk_ac4_substream` now
142//! runs the whole `aspx_data_1ch()` / `aspx_data_2ch()` body on
143//! I-frame ASPX substreams: `aspx_xover_subband_offset` →
144//! `aspx_framing` (+ stereo `aspx_balance` / second framing) →
145//! `aspx_delta_dir` → derived [`aspx::AspxFrequencyTables`] →
146//! `aspx_hfgen_iwc_1ch()` / `aspx_hfgen_iwc_2ch()` →
147//! `aspx_ec_data()` SIGNAL and NOISE per channel. All parsed data
148//! lands on [`asf::SubstreamTools`] alongside the existing framing /
149//! delta-dir / qmode fields.
150//!
151//! * **QMF analysis + synthesis filter bank** — [`qmf::QWIN`] carries
152//! the 640-coefficient QMF prototype window from Annex D.3.
153//! [`qmf::qmf_analysis_slot`] + [`qmf::QmfAnalysisBank`] implement
154//! the §5.7.3.2 Pseudocode 65 forward transform (windowing +
155//! 5-fold time-fold to vector u + 64-point complex modulation);
156//! [`qmf::qmf_synthesis_slot`] + [`qmf::QmfSynthesisBank`] implement
157//! the matching §5.7.4.2 Pseudocode 66 inverse transform (shifted
158//! 1 280-sample `qsyn_filt` delay line + 128-point modulation +
159//! folded-tap 64-way tap sum). The analysis/synthesis pair achieves
160//! ~80 dB PSNR end-to-end roundtrip on sine and noise test signals
161//! (unit tests in [`qmf`]).
162//! * **A-SPX HF regeneration scaffold** — [`aspx::derive_patch_tables`]
163//! implements §5.7.6.3.1.4 Pseudocode 71 (patch subband-group table
164//! derivation). [`aspx::hf_tile_copy`] implements a simplified
165//! §5.7.6.4.1.4 Pseudocode 89 high-band tile copy via the patch
166//! table (no chirp/alpha0/alpha1 tonal adjust). The full TNS body
167//! lives in the dedicated [`aspx_tns`] module — see below.
168//! [`aspx::apply_flat_envelope_gain`] is a one-gain scaffold kept as
169//! a fallback for the §5.7.6.4.2 HF envelope adjustment tool.
170//! Together with the QMF bank these form an end-to-end bandwidth-
171//! extension pipeline: PCM → QMF analysis → low-band truncate → HF
172//! tile-copy → QMF synthesis → non-silent PCM.
173//! * **A-SPX TNS (chirp + α0 + α1)** — [`aspx_tns`] implements the
174//! full §5.7.6.4.1.2 / .1.3 / .1.4 complex-covariance Temporal
175//! Noise Shaping path: pre-flatten gain vector
176//! ([`aspx_tns::compute_preflat_gains`], Pseudocode 85), complex
177//! covariance matrix over `Q_low_ext`
178//! ([`aspx_tns::compute_covariance`], Pseudocode 86), α0 / α1 LPC
179//! coefficients with the EPSILON_INV slack and the |α|≥4 fallback
180//! ([`aspx_tns::compute_alphas`], Pseudocode 87), per-noise-subband-
181//! group chirp factors via the Table 195 `tabNewChirp` lookup with
182//! attack / decay smoothing ([`aspx_tns::chirp_factors`],
183//! Pseudocode 88), and the full HF signal creation that adds
184//! `chirp * α0 * Q_low[n-2]` + `chirp² * α1 * Q_low[n-4]` plus the
185//! optional pre-flatten divide ([`aspx_tns::hf_tile_tns`],
186//! Pseudocode 89). Per-channel state ([`aspx_tns::AspxTnsState`])
187//! carries `aspx_tna_mode_prev[]` / `prev_chirp_array[]` plus the
188//! tail of the previous interval's `Q_low` for the
189//! `ts_offset_hfadj = 4` look-back. The decoder auto-selects the
190//! TNS path when the parsed `aspx_hfgen_iwc_*` provides
191//! `aspx_tna_mode[ch][]` and the framing is FIXFIX; otherwise it
192//! falls back to the bare tile copy.
193//! * **A-SPX HF envelope adjustment (per-envelope gain)** —
194//! [`aspx::AspxEnvelopeAdjuster`] implements §5.7.6.4.2 Pseudocodes
195//! 90 + 91 + 95 (non-harmonic, non-limited path): delta-decode
196//! `aspx_data_sig` / `aspx_data_noise` (Pseudocodes 80 / 81) via
197//! [`aspx::delta_decode_sig`] / [`aspx::delta_decode_noise`],
198//! dequantize to `scf_sig_sbg` / `scf_noise_sbg` (Pseudocodes 82 / 83)
199//! via [`aspx::dequantize_sig_scf`] / [`aspx::dequantize_noise_scf`],
200//! estimate the actual HF envelope energy
201//! ([`aspx::estimate_envelope_energy`]), map subband-group scale
202//! factors onto QMF subbands ([`aspx::map_scf_to_qmf_subbands`]),
203//! then compute per-subband compensatory gains
204//! ([`aspx::compute_sig_gains`]) = `sqrt(scf_sig / ((1 + est) *
205//! (1 + scf_noise)))`. The per-envelope gains are applied via
206//! [`aspx::apply_envelope_gains`] using the FIXFIX Table-194
207//! `atsg_sig` / `atsg_noise` borders derived by
208//! [`aspx::derive_fixfix_atsg`]. The decoder auto-selects the
209//! per-envelope path when the substream parsed FIXFIX framing
210//! plus matching envelope deltas, and falls back to
211//! `apply_flat_envelope_gain(0.5)` otherwise.
212//! * **ASPX decoder wiring** — [`decoder::Ac4Decoder`] now routes
213//! ASPX substreams through the extension pipeline: when an I-frame
214//! ASPX substream produces derived `aspx_frequency_tables`, the
215//! decoder takes the IMDCT low-band PCM, runs QMF analysis →
216//! tile-copy HF regen → per-envelope gain (or flat-gain fallback)
217//! → QMF synthesis, and emits bandwidth-extended PCM instead of
218//! silence.
219//!
220//! * **Dialogue Enhancement (DE) parser** —
221//! [`de::parse_dialog_enhancement`] walks the
222//! `dialog_enhancement(b_iframe)` element (§4.2.14.11 Table 76)
223//! end-to-end: `b_de_data_present` gate, optional I-frame
224//! `de_config()` (§4.2.14.12 Table 77, `de_method` /
225//! `de_max_gain` / `de_channel_config`), and the per-frame
226//! `de_data()` payload (§4.2.14.13 Table 78) including the
227//! cross-channel `de_keep_pos_flag` + `de_mix_coef[12]_idx` panning
228//! parameters, the `de_keep_data_flag` re-use gate, the
229//! `de_ms_proc_flag` M/S processing flag, and the per-channel
230//! per-band `de_par[ch][band]` matrix decoded via the Annex A.4
231//! Huffman codebooks ([`de_huffman::de_abs_huffman`] /
232//! [`de_huffman::de_diff_huffman`], Tables A.58..A.61). The four
233//! codebooks (`DE_HCB_ABS_0` / `DE_HCB_DIFF_0` / `DE_HCB_ABS_1` /
234//! `DE_HCB_DIFF_1`) are transcribed verbatim from the normative ETSI
235//! accompaniment file `ts_10319001v010401p0-tables.c` and are
236//! verified by Kraft-sum = 1 and prefix-code unit tests.
237//!
238//! * **DRC metadata parser** — [`drc::parse_drc_frame`] walks the
239//! `drc_frame()` element (§4.2.14.5 Table 70) end-to-end:
240//! `b_drc_present` gate, optional I-frame `drc_config()` (§4.2.14.6
241//! Table 71) including up to eight `drc_decoder_mode_config()` blocks
242//! with all three branches (`drc_repeat_profile_flag`,
243//! `drc_default_profile_flag`, explicit `drc_compression_curve()`),
244//! the full `drc_compression_curve()` with optional boost / cut
245//! sections and per-mode time-constant block (§4.2.14.8 Table 73),
246//! and the per-frame `drc_data()` payload (§4.2.14.9 Table 74) that
247//! pulls one `drc_gains()` entry per gainset mode.
248//! [`drc::parse_drc_gains`] decodes the seven-bit `drc_gain_val`
249//! seed plus all `(ch, band, sf)` deltas through the Annex A.5
250//! `DRC_HCB` Huffman codebook (`huff_decode_diff` per §4.3.10.8.3),
251//! with the per-band / per-channel `ref_drc_gain` reset semantics
252//! from Table 75 honoured. The Annex A.5 codebook itself
253//! ([`drc_huffman::DRC_HCB_LEN`] / [`drc_huffman::DRC_HCB_CW`]) is
254//! transcribed verbatim from the ETSI accompaniment file
255//! `ts_10319001v010401p0-tables.c` and is verified by a Kraft-sum
256//! = 1 unit test (complete prefix code) plus an explicit prefix-
257//! code check.
258//!
259//! * **Metadata walker** — [`metadata::parse_metadata`] implements the
260//! outer `metadata(b_iframe)` element (§4.2.14.1 Table 66) end-to-end:
261//! `basic_metadata(channel_mode)` (§4.2.14.2 Table 67) including the
262//! `b_more_basic_metadata` block with optional `further_loudness_info`
263//! (§4.2.14.3 Table 68), the stereo / multi-channel downmix info
264//! tracks, the 5.x-only `pre_dmixtyp_5ch` / `pre_upmixtyp_5ch` and
265//! 7.x-only `pre_upmixtyp_3_4` / `pre_upmixtyp_3_2_2` paths, and the
266//! `b_dc_blocking` flag; `extended_metadata(channel_mode,
267//! b_associated, b_dialog)` (§4.2.14.4 Table 69) including the
268//! per-channel `b_*_active` / `b_*_has_dialog` channels-classifier
269//! block; the `tools_metadata_size_value` (7-bit + optional
270//! `variable_bits(3) << 7` extension) hint; dispatch into
271//! [`drc::parse_drc_frame`] and [`de::parse_dialog_enhancement`]; and
272//! the trailing `b_emdf_payloads_substream` flag. The walker chains a
273//! [`metadata::MetadataState`] forward across frames so non-I frames
274//! re-use the most recent `drc_config()` / `de_config()` per the
275//! bitstream's gating semantics. After DRC + DE consume their bytes
276//! the walker reconciles against `tools_metadata_size` and skips any
277//! trailing reserved bits inside the announced envelope, providing
278//! forward compatibility against future tools-metadata extensions.
279//!
280//! * **A-CPL parameter↔QMF subband mapping** —
281//! [`acpl::sb_to_pb`] implements §5.7.7.2 Table 197 for all four
282//! `acpl_num_param_bands` configurations (15 / 12 / 9 / 7), the last
283//! piece needed to wire `acpl_data_*ch()` parameters through to QMF
284//! subbands during synthesis.
285//!
286//! * **A-CPL synthesis math** — [`acpl_synth`] implements the §5.7.7
287//! QMF-domain synthesis pipeline end-to-end:
288//! [`acpl_synth::differential_decode`] (§5.7.7.7 Pseudocode 121)
289//! recovers absolute `acpl_<SET>_q` arrays from the Huffman deltas
290//! produced by [`acpl::parse_acpl_data_1ch`] /
291//! [`acpl::parse_acpl_data_2ch`], with state carried across
292//! parameter sets and AC-4 frames via [`acpl_synth::AcplDiffState`];
293//! [`acpl_synth::dequantize_alpha_beta`] /
294//! [`acpl_synth::dequantize_beta3`] / [`acpl_synth::dequantize_gamma`]
295//! apply Tables 203-208 (with the cross-coupled Tables 203/204 and
296//! 205/206 `ibeta` linkage); [`acpl_synth::interpolate`] (§5.7.7.3
297//! Pseudocode 109) covers both smooth (linear-between-borders) and
298//! steep (timeslot-keyed) per-QMF-subsample interpolation;
299//! [`acpl_synth::InputSignalModifier`] (§5.7.7.4.2 Pseudocode 111)
300//! implements the three frequency-region all-pass IIR decorrelators
301//! `D0` / `D1` / `D2` over Tables 198-201;
302//! [`acpl_synth::TransientDucker`] (§5.7.7.4.3 Pseudocodes 112-114)
303//! carries the per-pb peak-decay / smooth state and emits
304//! `duck_gain[pb]`; [`acpl_synth::acpl_module`] (§5.7.7.5
305//! Pseudocode 116) and [`acpl_synth::run_pseudocode_115_pair`]
306//! (Pseudocode 115) wire the channel-pair element together
307//! end-to-end with the `acpl_qmf_band` M/S split below the
308//! threshold and the alpha/beta-modulated decorrelator mix above.
309//!
310//! * **A-CPL decoder wiring** — [`asf::walk_ac4_substream`] now consumes
311//! the full `aspx_data_1ch()` (Table 51) + `acpl_data_1ch()` (Table
312//! 61) tail of the `ASPX_ACPL_2` stereo path: the mono MDCT body
313//! parser ([`asf::parse_aspx_acpl2_mdct_body`] internal) walks the
314//! `spec_frontend; sf_info; sf_data` shape, then the shared
315//! [`asf::parse_aspx_data_1ch_body`] helper reads the leading
316//! xover-offset + framing + delta-dir + hfgen + ec_data, and finally
317//! [`acpl::parse_acpl_data_1ch`] is invoked with `num_bands` =
318//! `acpl_num_param_bands` and `start_band` =
319//! `sb_to_pb(acpl_qmf_band, num_param_bands)` per Table 61.
320//! The parsed payload lands on [`asf::SubstreamTools::acpl_data_1ch`].
321//! [`decoder::Ac4Decoder`] now drives
322//! [`acpl_synth::run_acpl_1ch_pcm`] (mono PCM → QMF analysis →
323//! §5.7.7.5 channel-pair element → QMF synthesis × 2) when the
324//! substream provides an `acpl_config_1ch` plus `acpl_data_1ch`,
325//! emitting two PCM channels in place of the duplicate-of-primary
326//! fallback. Per-substream
327//! [`acpl_synth::AcplSubstreamState`] (alpha-diff + beta-diff +
328//! `AcplCpeState` decorrelator + ducker) lives on the decoder so
329//! IIR delay-lines and `acpl_<SET>_q_prev` survive across frames.
330//! ASPX_ACPL_1's joint-MDCT residual layer (b_dual_maxsfb = 1 with
331//! `chparam_info()`) now walks end-to-end as of round-18:
332//! [`asf::parse_chparam_info`] (§4.2.10.1 Table 47) +
333//! [`asf::parse_sap_data`] (Table 48) drive the joint shell,
334//! `parse_aspx_acpl1_mdct_body` walks the dual M/S residuals (long
335//! frame + single window group), and the decoder feeds both into
336//! [`acpl_synth::run_acpl_1ch_pcm_stereo`] (x0 = M, x1 = S into
337//! Pseudocode 116) for the full stereo synth.
338//!
339//! * **Speech Spectral Frontend (SSF) tables + arithmetic decoder
340//! core** — the full Annex C scalar-table inventory now lands in
341//! [`ssf_tables`]: `POST_GAIN_LUT` (C.1), `PRED_GAIN_QUANT_TAB`
342//! (C.3), `PRED_RFS_TABLE` / `PRED_RTS_TABLE` (C.4 / C.5), the
343//! 705-entry `CDF_TABLE` (C.7), `PREDICTOR_GAIN_CDF_LUT` (C.8),
344//! `ENVELOPE_CDF_LUT` (C.9), the 256-entry `DITHER_TABLE` (C.10)
345//! and `RANDOM_NOISE_TABLE` (C.11), `STEP_SIZES_Q4_15` (C.12),
346//! `AC_COEFF_MAX_INDEX` (C.13), and the four `SLOPES_*` /
347//! `OFFSETS_*` dB↔linear LUTs (C.14). The 37 SSF prediction-
348//! coefficient matrices from Annex C.6 (~22 KB total) live in
349//! [`ssf_pred_coeff`] addressable via
350//! [`ssf_pred_coeff::ssf_pred_coeff_mat`]. Every byte in every
351//! table is validated against the ETSI accompaniment file by the
352//! `validate_ssf_*` integration tests. The arithmetic decoder
353//! itself ([`ssf_ac::AcState`]) implements §5.2.8 Pseudocodes 41-47
354//! (`AcDecoderInit` / `AcDecodeTarget` / `AcDecode` /
355//! `AcDecodeSymbolExtCdf` / `AcDecodeFinish`) plus Pseudocode 51-53
356//! (`Idx2Reconstruction` + `CdfEst` for the computed-CDF
357//! transform-coefficient path) and the §5.2.8.3 random-number
358//! generator ([`ssf_ac::SsfRandGenState`], Pseudocodes 54-57).
359//! Wired-up convenience functions: [`ssf_ac::decode_envelope_indices`]
360//! (Pseudocode 48), [`ssf_ac::decode_predictor_gain`] (Pseudocode 49),
361//! [`ssf_ac::decode_coefficient_indices`] (Pseudocode 50). The
362//! `ssf_data()` / `ssf_granule()` / `ssf_st_data()` / `ssf_ac_data()`
363//! bitstream walkers (Tables 43-46) are the next round's scope —
364//! the AC decoder building blocks are now in place.
365//!
366//! * **SSF bitstream walker** — [`ssf::parse_ssf_data`] /
367//! [`ssf::parse_ssf_granule`] / [`ssf::parse_ssf_st_data`] /
368//! [`ssf::parse_ssf_ac_data`] implement Tables 43-46 end-to-end. The
369//! walker derives the SSF block layout per Table 112 (48 kHz family)
370//! via [`ssf::SsfFrameConfig::from_frame_len_base`], builds
371//! `start_bin[]` / `end_bin[]` / `num_bins` from the Annex C.1
372//! bandwidths matrix ([`ssf::SSF_BANDWIDTHS`]) per §4.3.7.5
373//! Pseudocode 7, drains the per-block predictor / static fields, and
374//! drives the §5.2.8 arithmetic decoder ([`ssf_ac::AcState`]) for
375//! `env_curr_ac_bits` / `env_startup_ac_bits` /
376//! `predictor_gain_ac_bits[block]` /
377//! `q_mdct_coefficients_ac_bits[block]`. Per-channel
378//! [`ssf::SsfChannelState`] carries forward dither / noise RNG state
379//! (reset per SSF-I-frame per Pseudocode 55) plus
380//! `prev_pred_lag_idx` / `last_num_bands` / `env_prev[]`. The walker
381//! is wired into `asf::walk_ac4_substream` for both the mono path
382//! (`spec_frontend == SSF` no longer returns `Unsupported`) and the
383//! split-MDCT stereo + ASPX_ACPL_1 paths (per-channel SSF/ASF
384//! selection). Parsed payload lands on
385//! [`asf::SubstreamTools::ssf_data_primary`] /
386//! `ssf_data_secondary` for downstream synthesis.
387//!
388//! Known gaps (Unsupported or stubbed):
389//!
390//! * Short / grouped frames (`num_window_groups > 1`) — round 28 lands
391//! the mono / stereo `sf_data(ASF)` walker per Tables 39-42 (each
392//! `asf_*_data()` body has its own outer `for (g; ...)` loop, with a
393//! single `reference_scale_factor` and single `b_snf_data_exists` at
394//! the head). Multichannel layouts (5.X / 7.X) still use the r24
395//! per-group interleaved walker. MDCT synthesis for the per-group
396//! spectra (concatenated group-major in
397//! `tools.scaled_spec_primary` / `tools.scaled_spec_secondary`) is
398//! not yet hooked through the decoder.
399//! * Remaining §5.7.6.4 A-SPX HF regeneration — non-FIXFIX interval
400//! classes (FIXVAR / VARFIX / VARVAR) still fall back to the
401//! flat-gain scaffold; the limiter (§5.7.6.4.2.2) and TNS
402//! (§5.7.6.4.1) paths only run on FIXFIX framing today. The TNS
403//! pipeline is fully implemented in [`aspx_tns`] but per-channel
404//! `master_reset` semantics (resetting `prev_chirp_array` / the
405//! `Q_low_prev` history) when a new substream starts mid-stream
406//! isn't surfaced through the decoder API yet.
407//! * A-CPL data-path decoder hookup — `ASPX_ACPL_2` (mono MDCT body)
408//! was fully wired in round-17, and `ASPX_ACPL_1` (joint-MDCT body
409//! with `chparam_info()` driving M/S coupling) is now wired in
410//! round-18: parser walks the full dual-residual body, decoder
411//! IMDCTs both M and S spectra and feeds them as `x0` / `x1` into
412//! the §5.7.7.5 channel-pair element. Round-21 lands the §5.7.7.6.2
413//! ASPX_ACPL_3 transform-matrix synthesis math (`Transform()`,
414//! `ACplModule2()`, `ACplModule3()`, `run_pseudocode_118_5x()` —
415//! Pseudocodes 118 / 119); the 5_X-walker glue from the bitstream
416//! into the new transform synthesis is the next round's scope.
417//! Multichannel `5_X_codec_mode = ASPX_ACPL_1` / `ASPX_ACPL_2`
418//! wrappers (Pseudocode 117) are still pending — the building blocks
419//! are all in place but the 5-input wrapper is not wired.
420//! * **Speech Spectral Frontend (SSF) PCM synthesis** — round 31 lands
421//! the §5.2.3-5.2.7 chain in [`ssf_synth`]: envelope decoder
422//! ([`ssf_synth::decode_envelope`] / [`ssf_synth::interpolate_envelope`] /
423//! [`ssf_synth::decode_gains`] / [`ssf_synth::refine_envelope`] —
424//! Pseudocodes 4a-4d), predictor parameter calculation
425//! ([`ssf_synth::decode_predictor`] — Pseudocode 4e), helper
426//! variables and lossless-decoding allocation table
427//! ([`ssf_synth::compute_helpers`] / [`ssf_synth::build_alloc_table`] —
428//! Pseudocodes 26 + 31 no-rfu path), inverse quantizer
429//! ([`ssf_synth::inverse_quantize_block`] /
430//! [`ssf_synth::mmse_laplace`] — Pseudocodes 32 / 33), inverse
431//! heuristic scaling ([`ssf_synth::inverse_heuristic_scale`] —
432//! Pseudocode 34), C-matrix reconstruction
433//! ([`ssf_synth::build_c_matrix`] — Pseudocode 39), subband
434//! predictor ([`ssf_synth::SubbandPredictorState::run`] —
435//! Pseudocodes 35-37) and inverse flattening
436//! ([`ssf_synth::inverse_flatten`] — Pseudocode 38). The decoder
437//! ([`decoder::Ac4Decoder::receive_frame`]) now consumes
438//! `tools.ssf_data_primary` / `tools.ssf_data_secondary` and runs
439//! `synthesize_ssf_data` → IMDCT → KBD overlap-add per channel,
440//! producing real PCM in place of silence for SSF substreams. The
441//! heuristic-scaling helpers (§5.2.5.2.2 Pseudocodes 27 / 28 /
442//! 29 / 30) are deferred — the spec's `f_rfu == 0` short-circuit
443//! short-circuits to the no-heuristic path the synth already
444//! supports, covering any block with the predictor disabled.
445//! * Spectral noise fill synthesis — `asf_snf_data()` parses the
446//! Huffman-coded indices but doesn't inject shaped noise into
447//! zero bands yet.
448//! * `emdf_payloads_substream()` parsing — the outer
449//! [`metadata::parse_metadata`] walker errors out when
450//! `b_emdf_payloads_substream == 1` rather than mis-aligning the
451//! bitstream. Implementing §4.2.4.4 / §4.2.14.14 EMDF payloads is the
452//! next step before real-bitstream metadata can fully round-trip.
453//! `walk_ac4_substream` itself doesn't yet invoke the metadata walker
454//! — that wiring is the follow-up to this round.
455//! * TS 103 190-2 IFM (immersive / object) decoding — round 47 lands the
456//! v2 TOC walker (`bitstream_version >= 2` dispatch in
457//! [`toc::parse_ac4_toc`] running `ac4_presentation_v1_info()` per
458//! §6.2.1.3 + `ac4_substream_group_info()` per §6.3.2.5 +
459//! `ac4_substream_info_chan()` per §6.2.1.8) and the matching
460//! [`encoder_ims::Ac4ImsEncoder`] tone-encoder
461//! ([`encoder_ims::Ac4ImsEncoder::encode_frame_mono_tone`] /
462//! [`encoder_ims::Ac4ImsEncoder::encode_frame_mono_tone_at_hz`])
463//! that produces a v2 frame whose mono SIMPLE/ASF audio body
464//! round-trips through [`decoder::Ac4Decoder`] to non-silent PCM.
465//! Object / a-joc substream parsing inside
466//! `ac4_substream_group_info()` is still deferred — the walker
467//! surfaces `Unsupported` on the first object substream.
468//! * Encoder for arbitrary PCM input — round 47 ships a closed-form
469//! canned-tone encoder
470//! ([`encoder_ims::build_mono_simple_asf_tone_body`]). MDCT analysis,
471//! scalefactor selection, and ASF entropy coding for arbitrary input
472//! PCM are the next step.
473//!
474//! The decoder emits real PCM for long-frame, single-window-group
475//! mono and stereo SIMPLE/ASF streams. The stereo path covers both
476//! the split-MDCT layout (two independent ASF spectra) and the joint
477//! `b_enable_mdct_stereo_proc == 1` mode (shared sections +
478//! scalefactors, two residuals, per-sfb `ms_used[]` inverse
479//! L = M + S / R = M - S per §7.5). Everything else falls back to
480//! silence with a correctly-shaped AudioFrame.
481
482#![allow(dead_code)]
483
484pub mod acpl;
485pub mod acpl_huffman;
486pub mod acpl_synth;
487pub mod asf;
488pub mod asf_data;
489pub mod aspx;
490pub mod aspx_huffman;
491pub mod aspx_limiter;
492pub mod aspx_noise;
493pub mod aspx_tns;
494pub mod aspx_tone;
495pub mod de;
496pub mod de_huffman;
497pub mod decoder;
498pub mod drc;
499pub mod drc_huffman;
500pub mod emdf;
501pub mod encoder_acpl3;
502pub mod encoder_asf;
503pub mod encoder_ims;
504pub mod encoder_mdct;
505pub mod huffman;
506pub mod mch;
507pub mod mdct;
508pub mod metadata;
509pub mod qmf;
510pub mod sfb_offset;
511pub mod ssf;
512pub mod ssf_ac;
513pub mod ssf_pred_coeff;
514pub mod ssf_synth;
515pub mod ssf_tables;
516pub mod sync;
517pub mod tables;
518pub mod toc;
519
520use oxideav_core::{CodecCapabilities, CodecId, CodecParameters, CodecTag, Result};
521use oxideav_core::{CodecInfo, CodecRegistry, Decoder};
522
523/// Canonical codec id.
524pub const CODEC_ID_STR: &str = "ac4";
525
526/// Register the AC-4 decoder in a codec registry.
527pub fn register_codecs(reg: &mut CodecRegistry) {
528 let caps = CodecCapabilities::audio("ac4_sw")
529 .with_lossy(true)
530 .with_intra_only(false)
531 // TS 103 190-1 supports up to 24-channel immersive configs via
532 // IFM; the foundation path returns whatever the TOC declares.
533 .with_max_channels(24)
534 .with_max_sample_rate(192_000);
535 reg.register(
536 CodecInfo::new(CodecId::new(CODEC_ID_STR))
537 .capabilities(caps)
538 .decoder(make_decoder)
539 // ISO BMFF sample entry fourcc per ETSI TS 103 190-2 Annex E.
540 .tag(CodecTag::fourcc(b"ac-4")),
541 );
542}
543
544/// Unified registration entry point — installs AC-4 into the codec
545/// sub-registry of the supplied [`oxideav_core::RuntimeContext`].
546pub fn register(ctx: &mut oxideav_core::RuntimeContext) {
547 register_codecs(&mut ctx.codecs);
548}
549
550oxideav_core::register!("ac4", register);
551
552fn make_decoder(params: &CodecParameters) -> Result<Box<dyn Decoder>> {
553 decoder::make_decoder(params)
554}
555
556#[cfg(test)]
557mod tests {
558 use super::*;
559
560 #[test]
561 fn register_installs_decoder() {
562 let mut reg = CodecRegistry::new();
563 register_codecs(&mut reg);
564 assert!(reg.has_decoder(&CodecId::new(CODEC_ID_STR)));
565 }
566
567 #[test]
568 fn iso_bmff_tag_resolves() {
569 let mut reg = CodecRegistry::new();
570 register_codecs(&mut reg);
571 let hits: Vec<_> = reg.all_tag_registrations().collect();
572 assert!(hits
573 .iter()
574 .any(|(t, id)| matches!(t, CodecTag::Fourcc(v) if v == b"AC-4")
575 && id.as_str() == CODEC_ID_STR));
576 }
577
578 #[test]
579 fn register_via_runtime_context_installs_codec_factory() {
580 let mut ctx = oxideav_core::RuntimeContext::new();
581 register(&mut ctx);
582 assert!(
583 ctx.codecs.has_decoder(&CodecId::new(CODEC_ID_STR)),
584 "decoder factory not installed via RuntimeContext"
585 );
586 }
587}