truehd 0.7.0

Research implementation of Dolby TrueHD parser/decoder
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
use anyhow::{Result, bail};

use crate::process::extract::Frame;
use crate::process::{MAX_PRESENTATIONS, PresentationMap};
use crate::structs::access_unit::AccessUnit;
use crate::structs::restart_header::Guards;
use crate::utils::bitstream_io::BsIoSliceReader;
use crate::utils::crc::{
    CRC_MAJOR_SYNC_INFO_ALG, CRC_RESTART_BLOCK_HEADER_ALG, CRC_SUBSTREAM_ALG, Crc8, Crc16,
};
use crate::utils::diagnostic::{
    Diagnostic, DiagnosticMode, DiagnosticSink, Location, Rule, bit_position,
};
use crate::utils::errors::ParseError;
use crate::utils::fifo::{ACCUMULATORS, FifoDepthState, FifoPeak, SUBSTREAMS};
// Re-exported so the type is nameable where the method returning it lives
pub use crate::utils::perf::ParserPerfStats;
use crate::utils::timing::HiresOutputTimingState;

/// How many restart gaps [`ParserState::restart_gap`] keeps.
pub const RESTART_GAP_HISTORY: usize = 4;

/// Parses audio frames into structured access units.
///
/// Converts raw frame data into [`AccessUnit`] objects containing
/// parsed metadata, audio blocks, and timing information.
#[derive(Default)]
pub struct Parser {
    state: ParserState,
    resyncing: bool,
}

impl Parser {
    /// Parses an audio frame into a structured access unit.
    ///
    /// Returns an [`AccessUnit`] containing parsed metadata, audio blocks,
    /// and timing information. Handles both major sync frames (with stream
    /// configuration) and continuation frames (audio data only).
    pub fn parse(&mut self, frame: &Frame) -> Result<AccessUnit> {
        self.parse_inner(frame).0
    }

    fn parse_inner(&mut self, frame: &Frame) -> (Result<AccessUnit>, Option<u64>) {
        self.state.perf = ParserPerfStats::default();
        self.state.au_index = frame.index;
        self.state.au_offset = frame.offset;

        let reader = &mut BsIoSliceReader::from_slice(frame.as_ref());
        let access_unit = AccessUnit::read(&mut self.state, reader);

        let bit_offset = match access_unit {
            Ok(_) => None,
            Err(_) => bit_position(reader),
        };

        (access_unit, bit_offset)
    }

    /// Parses a frame, recording what fails instead of returning it.
    ///
    /// For [`DiagnosticMode::Collect`]. A check that ends the access unit is recorded
    /// like any other, the parser is then reset, and parsing resumes at the next frame
    /// carrying a major sync; frames skipped while resynchronising are not parsed and
    /// raise no diagnostics of their own. Returns `None` for an access unit that did not
    /// parse.
    ///
    /// A paired [`Decoder`](crate::process::decode::Decoder) must be reset with
    /// `reset_for_next_major_sync` whenever this returns `None`, or its state will
    /// silently diverge from the parser's.
    pub fn parse_recovering(&mut self, frame: &Frame) -> Option<AccessUnit> {
        if self.resyncing && !frame.is_major_sync() {
            self.state.au_index = frame.index;
            self.state.au_offset = frame.offset;

            return None;
        }

        let recorded = self.state.diagnostics.len();

        match self.parse_inner(frame) {
            (Ok(access_unit), _) => {
                self.resyncing = false;

                Some(access_unit)
            }
            (Err(error), bit_offset) => {
                self.record_parse_failure(recorded, error, bit_offset);
                self.reset_for_next_major_sync();
                self.resyncing = true;

                None
            }
        }
    }

    /// Keeps the error that ended an access unit.
    ///
    /// A check that fails at or below the fail level records itself on the way out, but
    /// leaves [`Diagnostic::source`] empty because the error is still travelling to here.
    /// An error raised outside the check macro has no diagnostic at all yet.
    fn record_parse_failure(
        &mut self,
        recorded: usize,
        error: anyhow::Error,
        bit_offset: Option<u64>,
    ) {
        if !self.state.is_collecting() {
            return;
        }

        if self.state.diagnostics.len() > recorded
            && let Some(diagnostic) = self.state.diagnostics.last_mut()
            && diagnostic.source.is_none()
        {
            diagnostic.source = Some(error);

            return;
        }

        let diagnostic = Diagnostic {
            rule: error.rule_id(),
            severity: log::Level::Error,
            location: self.state.location(bit_offset),
            message: error.to_string(),
            source: Some(error),
        };

        self.state.diagnostics.push(diagnostic);
    }

    /// What happens to a failed conformance check.
    pub fn set_diagnostic_mode(&mut self, mode: DiagnosticMode) {
        self.state.diagnostic_mode = mode;
    }

    pub fn diagnostic_mode(&self) -> DiagnosticMode {
        self.state.diagnostic_mode
    }

    /// Checks that have fired so far, oldest first.
    ///
    /// Always empty in [`DiagnosticMode::FailFast`], which records nothing.
    pub fn diagnostics(&self) -> &[Diagnostic] {
        &self.state.diagnostics
    }

    /// Takes the collected diagnostics, leaving none behind.
    pub fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
        std::mem::take(&mut self.state.diagnostics)
    }

    pub fn set_required_presentations(
        &mut self,
        required_presentations: &[bool; MAX_PRESENTATIONS],
    ) {
        self.state.required_presentations = *required_presentations;

        if let Some(presentation_map) = &self.state.presentation_map {
            self.state.substream_mask =
                presentation_map.substream_mask_by_required_presentations(required_presentations);
        }
    }

    pub fn hires_output_timing(&self) -> Option<usize> {
        self.state.hires_output_timing
    }

    /// Where parse time went for the most recent access unit.
    ///
    /// Every duration is zero unless the `perf` feature is enabled.
    pub fn last_parse_stats(&self) -> ParserPerfStats {
        self.state.perf
    }

    /// Number of seamless branch points that failed the buffer-model checks.
    ///
    /// These are conformance failures: the decoded samples are unaffected,
    /// but the stream is not a conformant splice.
    /// Every point where the stream's timing restarted, as a splice does.
    pub fn branches(&self) -> &[Branch] {
        &self.state.branches
    }

    pub fn invalid_branches(&self) -> usize {
        self.state.branches.iter().filter(|b| !b.is_valid()).count()
    }

    /// Read-only view of the parser state for substream `i`.
    ///
    /// Gives realtime consumers access to per-substream DRC state
    /// (`drc_*`, `heavy_drc_*`) without exposing the parser internals
    /// mutably. Returns `None` for an out-of-range index.
    pub fn substream_state(&self, i: usize) -> Option<&ParserSubstreamState> {
        self.state.substream_i_state(i).ok()
    }

    /// Sets the failure level for validation errors.
    ///
    /// - `log::Level::Error`: Only fail on Error level messages (default)
    /// - `log::Level::Warn`: Fail on Warning level and above (strict mode)
    pub fn set_fail_level(&mut self, level: log::Level) {
        self.state.fail_level = level;
    }

    /// Whether a major sync may excuse the timing and output-timing checks.
    ///
    /// `true` (the default) treats a discontinuity at a major sync as a splice and skips
    /// the checks that a splice legitimately breaks. `false` evaluates them everywhere,
    /// which is what a conformance pass over a stream that is not spliced wants.
    pub fn set_allow_seamless_branch(&mut self, allow: bool) {
        self.state.allow_seamless_branch = allow;
    }

    /// Deepest each byte-domain FIFO accumulator has been, in bytes.
    ///
    /// Indexed by [`Accumulator`](crate::utils::fifo::Accumulator): substream 0, the
    /// substream sums the 6-, 8- and 16-channel decoders read, then the whole stream.
    /// All zero unless FIFO checks are enabled.
    pub fn fifo_depth_peaks(&self) -> [usize; ACCUMULATORS] {
        self.state.fifo_depth.peaks()
    }

    /// Each accumulator's deepest point, split into the stream's own substream-segment
    /// bytes and the container overhead standing with them at that moment.
    ///
    /// The two parts always add back up to [`FifoPeak::total`], which is the figure
    /// [`fifo_depth_peaks`](Self::fifo_depth_peaks) reports and the cap applies to.
    pub fn fifo_depth_records(&self) -> [FifoPeak; ACCUMULATORS] {
        self.state.fifo_depth.peak_records()
    }

    /// Deepest each substream's own payload bytes have been, with no overhead priced in.
    ///
    /// A substream's own allowance is 15000 bytes per channel it carries, which
    /// [`ParserRestartState::min_chan`] and `max_chan` give.
    pub fn fifo_substream_peaks(&self) -> [usize; SUBSTREAMS] {
        self.state.fifo_depth.substream_peaks()
    }

    /// Highest instantaneous data rate the stream reached, in bits per second.
    ///
    /// Measured over the input-timing interval an access unit was delivered in, and
    /// taken only across intervals the timing checks did not flag as a jump.
    pub fn max_data_rate(&self) -> usize {
        self.state.max_data_rate
    }

    /// Access unit the maximum data rate was measured at.
    pub fn max_data_rate_au(&self) -> usize {
        self.state.max_data_rate_au_index
    }

    /// Deepest FIFO latency seen, in samples: how far an access unit's playback trails
    /// its delivery.
    pub fn max_fifo_latency(&self) -> usize {
        self.state.max_latency
    }

    /// Largest access unit seen, in bytes.
    pub fn max_access_unit_size(&self) -> usize {
        self.state.max_access_unit_size
    }

    /// Bytes of every access unit that parsed, for an average over their count.
    pub fn total_access_unit_bytes(&self) -> usize {
        self.state.total_access_unit_length << 1
    }

    /// Resets stream state after a fatal parse failure.
    ///
    /// After [`parse`](Self::parse) returns an error, internal state may be
    /// partially updated and is not safe to continue from. Calling this drops
    /// all stream state while preserving configuration (fail level, seamless
    /// branch tolerance, FIFO checks, required presentations), so parsing can
    /// resume at the next frame carrying a major sync.
    ///
    /// Any paired [`Decoder`](crate::process::decode::Decoder) must be reset
    /// with its own `reset_for_next_major_sync` at the same point in the frame
    /// sequence, or its state will silently diverge from the parser's.
    pub fn reset_for_next_major_sync(&mut self) {
        self.state.reset_for_next_major_sync();
    }
}

/// Per-substream parser state that outlives a restart header.
///
/// Everything a restart header re-establishes lives in [`ParserRestartState`] under
/// [`restart`](Self::restart) instead, so a field placed here keeps its value across
/// restarts by construction rather than by appearing in a list. Which of the two
/// structs a new field goes in is the whole decision; the reset never changes.
#[derive(Clone, Copy, Debug)]
pub struct ParserSubstreamState {
    pub crc_present: bool,
    pub substream_end_ptr: u16,

    pub drc_active: bool,
    pub drc_gain_update: i16,
    pub drc_time_update: u8,
    pub drc_count: usize,

    pub heavy_drc_active: bool,
    pub heavy_drc_present: bool,
    pub heavy_drc_gain_update: i16,
    pub heavy_drc_time_update: u8,
    pub heavy_drc_count: usize,

    /// Discarded wholesale by every restart header.
    pub restart: ParserRestartState,

    /// The bit this substream's last restart header carried.
    pub hires_output_timing: bool,
    pub hires_output_timing_state: HiresOutputTimingState,

    pub latency: usize,
    pub prev_latency: usize,

    pub output_timing_history: [usize; 128],
    pub substream_size_history: [usize; 128],
    pub history_index: usize,
}

/// Per-substream parser state a restart header re-establishes.
///
/// [`ParserState::reset_parser_substream_state`] replaces this with
/// [`Default`](Default::default) and touches nothing else, so a field added here is
/// reset at the next restart header with no other edit, and a field that must survive
/// one belongs in [`ParserSubstreamState`].
#[derive(Clone, Copy, Debug)]
pub struct ParserRestartState {
    pub block_index: usize,

    pub restart_sync_word: u16,
    pub min_chan: usize,
    pub max_chan: usize,
    pub max_matrix_chan: usize,
    pub max_shift: i8,
    pub max_lsbs: u32,
    pub error_protect: bool,

    pub guards: Guards,
    pub block_size: usize,

    pub primitive_matrices: usize,
    pub matrix_ch: [u8; 16],
    pub frac_bits: [u8; 16],
    pub lsb_bypass_used: [bool; 16],

    pub cf_mask: [u16; 16],
    pub delta_bits: [u8; 16],
    pub lsb_bypass_bit_count: [u8; 16],

    pub huff_offset: [i32; 16],
    pub huff_type: [usize; 16],
    pub huff_lsbs: [u32; 16],

    pub output_shift: [i8; 16],
    pub quantiser_step_size: [u32; 16],
}

impl Default for ParserSubstreamState {
    fn default() -> Self {
        Self {
            crc_present: false,
            substream_end_ptr: 0,

            drc_active: false,
            drc_gain_update: 0,
            drc_time_update: 0,
            drc_count: 0,

            hires_output_timing: false,

            heavy_drc_active: false,
            heavy_drc_present: false,
            heavy_drc_gain_update: 0,
            heavy_drc_time_update: 0,
            heavy_drc_count: 0,

            restart: ParserRestartState::default(),

            hires_output_timing_state: HiresOutputTimingState::default(),

            latency: 0,
            prev_latency: 0,

            output_timing_history: [0; 128],
            substream_size_history: [0; 128],
            history_index: 0,
        }
    }
}

impl Default for ParserRestartState {
    fn default() -> Self {
        Self {
            block_index: 0,
            restart_sync_word: 0,
            min_chan: 0,
            max_chan: 0,
            max_matrix_chan: 0,
            max_shift: 0,
            max_lsbs: 0,
            error_protect: false,

            guards: Guards::default(),
            block_size: 8,

            primitive_matrices: 0,
            matrix_ch: [0; 16],
            frac_bits: [0; 16],
            lsb_bypass_used: [false; 16],

            cf_mask: [0; 16],
            delta_bits: [0; 16],
            lsb_bypass_bit_count: [0; 16],

            huff_offset: [0; 16],
            huff_type: [0; 16],
            huff_lsbs: [24; 16],

            output_shift: [0; 16],
            quantiser_step_size: [0; 16],
        }
    }
}

/// The four buffer-model conditions a branch must meet to be seamless. A decoder that
/// starts at the branch has to be able to play across it without its input running dry or
/// overflowing, and each condition bounds one way that can fail.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct BranchConditions {
    /// The advance grew by no more than three quarters of an access unit.
    pub advance_step: bool,
    /// The advance stays inside what the previous access unit had buffered.
    pub fifo_duration: bool,
    /// The advance stays inside the 75 ms the buffer model allows.
    pub within_75ms: bool,
    /// The access unit before the branch fits the peak data rate over the interval.
    pub data_rate: bool,
}

impl BranchConditions {
    pub const fn is_valid(&self) -> bool {
        self.advance_step && self.fifo_duration && self.within_75ms && self.data_rate
    }

    /// The conditions that failed, named as the report names them.
    pub fn failed(&self) -> Vec<&'static str> {
        [
            (self.advance_step, "advance step"),
            (self.fifo_duration, "FIFO duration"),
            (self.within_75ms, "75 ms limit"),
            (self.data_rate, "peak data rate"),
        ]
        .into_iter()
        .filter_map(|(met, name)| (!met).then_some(name))
        .collect()
    }
}

/// A point where the stream's timing restarts, which is what a splice leaves behind.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Branch {
    /// Access unit the branch was found at.
    pub au_index: usize,
    /// Byte offset of that access unit from the start of the stream.
    pub byte_offset: u64,
    /// Samples played before it.
    pub sample: u64,
    /// Samples the decoder is running ahead of playback at the branch.
    pub advance: usize,
    pub conditions: BranchConditions,
}

impl Branch {
    pub const fn is_valid(&self) -> bool {
        self.conditions.is_valid()
    }
}

#[derive(Debug)]
#[repr(C)]
pub struct ParserState {
    // hyper
    pub fail_level: log::Level,
    pub allow_seamless_branch: bool,
    pub check_fifo: bool,
    pub diagnostic_mode: DiagnosticMode,

    /// Checks that fired, in the order they fired. Only filled in
    /// [`DiagnosticMode::Collect`].
    pub diagnostics: Vec<Diagnostic>,

    /// Location of the access unit being parsed, taken from its [`Frame`].
    pub au_index: u64,
    pub au_offset: u64,

    /// Access units between each of the last few major syncs, most recent first.
    pub restart_gap: [usize; RESTART_GAP_HISTORY],
    pub last_major_sync_index: usize,
    pub au_counter: usize,
    pub is_major_sync: bool,
    pub fifo_depth: FifoDepthState,
    /// Synthetic output clock for the FIFO model: the unwrapped output timing of the
    /// first access unit, advanced one access unit per access unit, never re-synchronised.
    pub fifo_output_clock: Option<usize>,
    pub has_parsed_au: bool,
    pub segment_start: bool,

    pub au_start_pos: usize,

    pub access_unit_length: usize,
    pub prev_access_unit_length: usize,
    pub total_access_unit_length: usize,

    pub au_end_pos_bit: usize,

    pub max_data_rate: usize,
    pub max_data_rate_au_index: usize,
    /// Deepest FIFO latency seen, in samples.
    pub max_latency: usize,
    /// Largest access unit seen, in bytes.
    pub max_access_unit_size: usize,

    pub advance: usize,
    pub prev_advance: usize,

    pub fifo_duration: usize,
    pub prev_fifo_duration: usize,

    pub input_timing: usize,
    pub first_input_timing: usize,
    pub prev_input_timing: usize,
    pub wrapped_input_timing: usize,

    pub output_timing: usize,
    /// First `output_timing` of the access unit being parsed and the substream that
    /// carried it, which every other substream in the access unit must agree with.
    pub au_output_timing: Option<(usize, u16)>,
    pub first_output_timing: usize,
    /// 1456
    pub output_timing_deviation: usize,
    pub hires_output_timing: Option<usize>,

    pub unwrapped_input_timing: usize,
    pub prev_unwrapped_input_timing: usize,
    pub first_unwrapped_input_timing: usize,

    pub input_timing_jump: bool,
    pub output_timing_jump: bool,
    /// 1452
    pub peak_data_rate_jump: bool,
    pub has_valid_branch: bool,
    pub has_substream_info_changed: bool,
    pub branches: Vec<Branch>,

    /// Parse timing for the current access unit; see the `perf` feature.
    pub perf: ParserPerfStats,

    pub variable_rate: bool,
    pub peak_data_rate: usize,
    pub prev_peak_data_rate: usize,

    // pub quantization_word_length_1: u8,
    // pub quantization_word_length_2: u8,
    pub audio_sampling_frequency_1: u32,
    // pub audio_sampling_frequency_2: u32,
    pub samples_per_au: usize,
    pub format_sync: u32,
    pub flags: u16,

    pub presentation_map: Option<PresentationMap>,
    pub required_presentations: [bool; MAX_PRESENTATIONS],

    pub substreams: Option<usize>,
    pub extended_substream_info: u8,
    pub substream_info: u8,

    pub has_parsed_substream: bool,

    pub substream_segment_start_pos: u64,
    pub substream_index: usize,
    pub substream_mask: u8,
    pub substream_state: [ParserSubstreamState; MAX_PRESENTATIONS],

    pub crc_restart_block_header: Crc8,
    pub crc_substream: Crc8,
    pub crc_major_sync_info: Crc16,

    pub bypassed_lsb: [[i32; 16]; 160],
    pub sample_buffer: [[i32; 16]; 160],
}

impl Default for ParserState {
    fn default() -> Self {
        Self {
            fail_level: log::Level::Error,
            allow_seamless_branch: true,
            check_fifo: true,
            diagnostic_mode: DiagnosticMode::default(),
            diagnostics: Vec::new(),
            au_index: 0,
            au_offset: 0,
            restart_gap: [0, 8, 8, 8],

            last_major_sync_index: 0,
            au_counter: 0,
            is_major_sync: false,
            fifo_depth: FifoDepthState::default(),
            fifo_output_clock: None,
            segment_start: false,
            has_parsed_au: false,

            au_start_pos: 0,

            access_unit_length: 0,
            prev_access_unit_length: 0,
            total_access_unit_length: 0,

            au_end_pos_bit: 0,

            max_data_rate: 0,
            max_data_rate_au_index: 0,
            max_latency: 0,
            max_access_unit_size: 0,

            advance: 0,
            prev_advance: 0,

            fifo_duration: 0,
            prev_fifo_duration: 0,

            input_timing: 0,
            first_input_timing: 0,
            prev_input_timing: 0,
            wrapped_input_timing: 0,

            output_timing: 0,
            au_output_timing: None,
            first_output_timing: 0,
            output_timing_deviation: 0,
            hires_output_timing: None,

            // quantization_word_length_1: 0,
            // quantization_word_length_2: 0,
            unwrapped_input_timing: 0,
            prev_unwrapped_input_timing: 0,
            first_unwrapped_input_timing: 0,

            input_timing_jump: false,
            output_timing_jump: false,
            peak_data_rate_jump: false,
            has_valid_branch: false,
            has_substream_info_changed: false,
            branches: Vec::new(),
            perf: ParserPerfStats::default(),

            variable_rate: false,
            peak_data_rate: 0,
            prev_peak_data_rate: 0,

            audio_sampling_frequency_1: 0,
            // audio_sampling_frequency_2: 0,
            samples_per_au: 0,
            format_sync: 0,
            flags: 0,

            presentation_map: None,
            required_presentations: [true; MAX_PRESENTATIONS],

            substreams: None,
            extended_substream_info: 0,
            substream_info: 0,

            has_parsed_substream: false,

            substream_segment_start_pos: 0,
            substream_index: 0,
            substream_mask: 0,
            substream_state: [ParserSubstreamState::default(); MAX_PRESENTATIONS],

            crc_restart_block_header: Crc8::new(&CRC_RESTART_BLOCK_HEADER_ALG),
            crc_substream: Crc8::new(&CRC_SUBSTREAM_ALG),
            crc_major_sync_info: Crc16::new(&CRC_MAJOR_SYNC_INFO_ALG),

            bypassed_lsb: [[0; 16]; 160],
            sample_buffer: [[0; 16]; 160],
        }
    }
}

impl DiagnosticSink for ParserState {
    fn fail_level(&self) -> log::Level {
        self.fail_level
    }

    fn diagnostic_mode(&self) -> DiagnosticMode {
        self.diagnostic_mode
    }

    fn location(&self, bit_offset: Option<u64>) -> Location {
        Location {
            au_index: self.au_index,
            au_offset: self.au_offset,
            bit_offset,
        }
    }

    fn push_diagnostic(&mut self, diagnostic: Diagnostic) {
        self.diagnostics.push(diagnostic);
    }
}

impl ParserState {
    /// Records a branch at the current access unit. A jump is found once per substream
    /// that reads its restart header, so the record is merged rather than repeated, and a
    /// condition that fails for any substream fails for the branch.
    pub fn record_branch(&mut self, advance: usize, conditions: BranchConditions) {
        if let Some(branch) = self.branches.last_mut()
            && branch.au_index == self.au_counter
        {
            let met = branch.conditions;
            branch.conditions = BranchConditions {
                advance_step: met.advance_step && conditions.advance_step,
                fifo_duration: met.fifo_duration && conditions.fifo_duration,
                within_75ms: met.within_75ms && conditions.within_75ms,
                data_rate: met.data_rate && conditions.data_rate,
            };

            return;
        }

        self.branches.push(Branch {
            au_index: self.au_counter,
            byte_offset: self.au_offset,
            sample: self.au_counter as u64 * self.samples_per_au as u64,
            advance,
            conditions,
        });
    }

    pub fn reset_for_next_major_sync(&mut self) {
        let diagnostics = std::mem::take(&mut self.diagnostics);

        *self = Self {
            fail_level: self.fail_level,
            allow_seamless_branch: self.allow_seamless_branch,
            check_fifo: self.check_fifo,
            required_presentations: self.required_presentations,
            branches: self.branches.clone(),
            diagnostic_mode: self.diagnostic_mode,
            diagnostics,
            au_index: self.au_index,
            au_offset: self.au_offset,
            ..Default::default()
        };
    }

    pub fn expected_au_end_pos(&self) -> usize {
        self.au_start_pos + (self.access_unit_length << 4)
    }

    pub fn substream_state_mut(&mut self) -> Result<&mut ParserSubstreamState> {
        self.substream_i_state_mut(self.substream_index)
    }

    pub fn substream_state(&self) -> Result<&ParserSubstreamState> {
        self.substream_i_state(self.substream_index)
    }

    pub fn substream_i_state_mut(&mut self, i: usize) -> Result<&mut ParserSubstreamState> {
        self.check_substream(i)?;
        Ok(&mut self.substream_state[i])
    }

    pub fn substream_i_state(&self, i: usize) -> Result<&ParserSubstreamState> {
        self.check_substream(i)?;
        Ok(&self.substream_state[i])
    }

    pub fn has_jump(&self) -> bool {
        self.peak_data_rate_jump || self.input_timing_jump || self.output_timing_jump
    }

    // TODO: provide iterator for sss here

    pub fn reset_parser_substream_state(&mut self) {
        self.substream_state[self.substream_index].restart = ParserRestartState::default();
    }

    /// Restarts the timing and FIFO model as if the stream began at this access unit.
    pub fn restart_stream_for_branch(&mut self, output_timing: usize) {
        self.output_timing_deviation = 0;
        self.unwrapped_input_timing = self.input_timing;
        self.prev_unwrapped_input_timing = 0;
        self.first_input_timing = self.input_timing;
        self.first_unwrapped_input_timing = self.unwrapped_input_timing;

        let mut output_timing = output_timing;
        if output_timing < self.input_timing {
            output_timing += 0x10000;
        }
        self.output_timing = output_timing;
        self.first_output_timing = output_timing;
        self.fifo_output_clock = None;

        for ss_state in &mut self.substream_state {
            ss_state.history_index = 0;
        }

        self.fifo_depth.restart();
        self.segment_start = true;
    }

    pub fn reset_for_branch(&mut self) {
        for ss_state in &mut self.substream_state {
            ss_state.hires_output_timing_state.reset_for_branch()
        }
    }

    fn check_substream(&self, i: usize) -> Result<()> {
        let Some(substreams) = self.substreams else {
            bail!(ParseError::NoSubstream);
        };

        if substreams <= i {
            bail!(ParseError::InvalidSubstreamIndex(i + 1, substreams));
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::Parser;
    use log::Level;

    #[test]
    fn reset_preserves_configuration() {
        let mut parser = Parser::default();
        parser.set_fail_level(Level::Warn);
        parser.set_required_presentations(&[true, false, true, false]);

        // Simulate accumulated stream state
        parser.state.has_parsed_au = true;
        parser.state.au_counter = 42;
        parser.state.input_timing = 1234;

        parser.reset_for_next_major_sync();

        assert_eq!(parser.state.fail_level, Level::Warn);
        assert_eq!(
            parser.state.required_presentations,
            [true, false, true, false]
        );
        assert!(!parser.state.has_parsed_au);
        assert_eq!(parser.state.au_counter, 0);
        assert_eq!(parser.state.input_timing, 0);
    }

    /// FBB streams bailed with `unimplemented!` at three sites, so the crate could not read
    /// them at all even though the format info, substream and restart header paths were
    /// already written.
    fn first_au(data: &[u8]) -> Option<crate::structs::access_unit::AccessUnit> {
        let mut extractor = crate::process::extract::Extractor::default();
        extractor.push_bytes(data);
        let frame = extractor.by_ref().next()?.ok()?;
        Parser::default().parse(&frame).ok()
    }

    #[test]
    fn fbb_stream_parses() {
        let au = first_au(crate::process::EXAMPLE_DATA_FBB).expect("an access unit");
        let ms = au.major_sync_info.as_ref().expect("a major sync");
        assert_eq!(ms.format_sync, crate::structs::sync::MAJOR_SYNC_FBB);
        assert_eq!(ms.format_info.sampling_frequency_1().unwrap(), 48000);
    }

    /// The FBB `channel_meaning` is its own 64-bit structure. It is the same width as the
    /// FBA one and in the same place, so the CRC and everything after it come out right
    /// either way; only the field values say which layout was read.
    #[test]
    fn fbb_channel_meaning_is_read_with_the_fbb_layout() {
        let au = first_au(crate::process::EXAMPLE_DATA_FBB).expect("an access unit");
        let ms = au.major_sync_info.as_ref().expect("a major sync");

        assert!(ms.channel_meaning.fba().is_none(), "not the FBA layout");
        let cm = ms.channel_meaning.fbb().expect("the FBB layout");

        assert_eq!(cm.fs, 10);
        assert_eq!(cm.wordwidth, 24);
        assert_eq!(cm.channel_occupancy, 0x3F);
        assert_eq!(cm.mlp_multi_channel_type, 0);
        assert_eq!(cm.speaker_layout, 0);
        assert_eq!(cm.copy_protection, 0);
        assert_eq!(cm.level_control, 0x8080);
        assert!(!cm.hdcd_process);
        assert_eq!(cm.reserved2, 0);
        assert_eq!(cm.source_format, 0);
        assert_eq!(cm.summary_info, 0);

        // The last bit of the block is the last bit of summary_info, not the FBA
        // extra_channel_meaning_present, so nothing follows it but the CRC.
        assert!(ms.channel_meaning.extra_channel_meaning().is_none());
    }

    /// FBA is unaffected by the FBB work.
    #[test]
    fn fba_stream_still_parses() {
        let au = first_au(crate::process::EXAMPLE_DATA).expect("an access unit");
        let ms = au.major_sync_info.as_ref().expect("a major sync");
        assert_eq!(ms.format_sync, crate::structs::sync::MAJOR_SYNC_FBA);
    }

    /// A single-substream FBB access unit carrying `substream_info` 0x05. Judged by the
    /// FBA rules, its low two bits looked like reserved bits set and the last bit of its
    /// channel_meaning looked like `extra_channel_meaning_present`, which read the block
    /// that follows past the major sync info CRC, so no frame came out at all.
    #[test]
    fn fbb_unextractable_stream_extracts() {
        let au = first_au(crate::process::EXAMPLE_DATA_FBB_UNEXTRACTABLE).expect("an access unit");
        let ms = au.major_sync_info.as_ref().expect("a major sync");
        assert_eq!(ms.format_sync, crate::structs::sync::MAJOR_SYNC_FBB);
        assert_eq!(ms.substream_info, 0x05);
        assert_eq!(ms.channel_meaning.fbb().expect("the FBB layout").fs, 10);
    }

    /// The major-sync repetition limit differs by format: 128 access units for FBA, 32 for
    /// FBB.
    #[test]
    fn fbb_sync_interval_limit_is_stricter_than_fba() {
        use crate::utils::errors::AccessUnitError;
        assert_eq!(
            AccessUnitError::FbbSyncTooFar.to_string(),
            "FBB stream major syncs must occur at intervals not exceeding 32 access units"
        );
        assert_eq!(
            AccessUnitError::FbaSyncTooFar.to_string(),
            "FBA stream major syncs must occur at intervals not exceeding 128 access units"
        );
    }
}