Skip to main content

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}