Skip to main content

cd_da_reader/
parse_toc.rs

1use crate::{Toc, Track};
2
3pub(crate) fn parse_toc(data: Vec<u8>) -> std::io::Result<Toc> {
4    // TOC data format:
5    // Bytes 0-1: TOC data length
6    // Byte 2: First track number
7    // Byte 3: Last track number
8    // Bytes 4+: Track descriptors (8 bytes each)
9
10    if data.len() < 4 {
11        return Err(std::io::Error::new(
12            std::io::ErrorKind::InvalidData,
13            "TOC data too short",
14        ));
15    }
16
17    let toc_length = u16::from_be_bytes([data[0], data[1]]) as usize;
18    let first_track = data[2];
19    let last_track = data[3];
20
21    let mut tracks = vec![];
22    let mut offset = 4;
23
24    let mut lead_out_lba: Option<u32> = None;
25
26    while offset + 8 <= data.len() && offset < toc_length + 2 {
27        let track_num = data[offset + 2];
28        let control = data[offset + 1];
29
30        // LBA is in bytes 4-7 of descriptor
31        let lba = u32::from_be_bytes([
32            data[offset + 4],
33            data[offset + 5],
34            data[offset + 6],
35            data[offset + 7],
36        ]);
37
38        let msf = lba_to_msf(lba);
39
40        if track_num != 0xAA {
41            tracks.push(Track {
42                number: track_num,
43                start_lba: lba,
44                start_msf: msf,
45                is_audio: (control & 0x04) == 0,
46            });
47        } else {
48            lead_out_lba = Some(lba);
49        }
50
51        offset += 8;
52    }
53
54    if let Some(leadout) = lead_out_lba {
55        Ok(Toc {
56            first_track,
57            last_track,
58            tracks,
59            leadout_lba: leadout,
60        })
61    } else {
62        Err(std::io::Error::new(
63            std::io::ErrorKind::InvalidData,
64            "Didn't find 0xAA",
65        ))
66    }
67}
68
69/// Convert a Logical Block Address to its Minutes/Seconds/Frames address.
70///
71/// MSF addresses include the fixed 2-second (150-frame) lead-in offset, so
72/// `lba_to_msf(0)` is `(0, 2, 0)`. This is handy when building a [`Toc`] for a
73/// file/image backing (see [`AudioSectorReader`](crate::AudioSectorReader)),
74/// where you have sector indices but need to populate [`Track::start_msf`].
75pub fn lba_to_msf(lba: u32) -> (u8, u8, u8) {
76    let total_frames = lba + 150; // MSF addresses are offset by 150
77    let minutes = (total_frames / 75 / 60) as u8;
78    let seconds = ((total_frames / 75) % 60) as u8;
79    let frames = (total_frames % 75) as u8;
80    (minutes, seconds, frames)
81}