Skip to main content

lba_to_msf

Function lba_to_msf 

Source
pub fn lba_to_msf(lba: u32) -> (u8, u8, u8)
Expand description

Convert a Logical Block Address to its Minutes/Seconds/Frames address.

MSF addresses include the fixed 2-second (150-frame) lead-in offset, so lba_to_msf(0) is (0, 2, 0). This is handy when building a Toc for a file/image backing (see AudioSectorReader), where you have sector indices but need to populate Track::start_msf.

Examples found in repository?
examples/file_backend.rs (line 53)
37fn main() -> Result<(), Box<dyn std::error::Error>> {
38    let output_dir = common::fresh_output_dir("file_backend")?;
39
40    // A real backing derives this TOC from the image's own track metadata.
41    // Here we fabricate a 2-track disc: 2 seconds + 3 seconds of audio.
42    let track1_sectors = 75 * 2;
43    let track2_sectors = 75 * 3;
44    let total_sectors = track1_sectors + track2_sectors;
45
46    let toc = Toc {
47        first_track: 1,
48        last_track: 2,
49        tracks: vec![
50            Track {
51                number: 1,
52                start_lba: 0,
53                start_msf: lba_to_msf(0),
54                is_audio: true,
55            },
56            Track {
57                number: 2,
58                start_lba: track1_sectors,
59                start_msf: lba_to_msf(track1_sectors),
60                is_audio: true,
61            },
62        ],
63        leadout_lba: total_sectors,
64    };
65
66    // Silence, just for the demo — a real backing decodes actual audio here.
67    let disc = InMemoryDisc {
68        pcm: vec![0u8; total_sectors as usize * 2352],
69    };
70
71    for track in &toc.tracks {
72        let pcm = read_track(&disc, &toc, track.number)?;
73        println!(
74            "track {}: {} bytes ({} sectors)",
75            track.number,
76            pcm.len(),
77            pcm.len() / 2352
78        );
79
80        let wav = create_wav(pcm);
81        let output_path = output_dir.join(format!("track{:02}.wav", track.number));
82        std::fs::write(&output_path, wav)?;
83        println!("  wrote {}", output_path.display());
84    }
85
86    // The same backing can be streamed instead of buffered: pull sector-aligned
87    // chunks so a player never holds a whole track in memory at once. (A backing
88    // whose tracks are addressed contiguously — a gap-stripped extract — would
89    // open with `TrackBounds::Gapless`, or supply its own bounds via
90    // `open_track_stream_at`; this demo TOC has no trailing data track, so plain
91    // `open_track_stream` is equivalent.)
92    let mut stream = open_track_stream(&disc, &toc, 1)?;
93    let (mut chunks, mut bytes) = (0u32, 0usize);
94    while let Some(chunk) = stream.next_chunk()? {
95        chunks += 1;
96        bytes += chunk.len();
97    }
98    println!(
99        "streamed track 1: {bytes} bytes in {chunks} chunks ({:.1}s of audio)",
100        stream.total_seconds()
101    );
102
103    Ok(())
104}
More examples
Hide additional examples
examples/bin_cue_backend.rs (line 272)
209fn parse_cue(cue_path: &Path) -> Result<(PathBuf, Toc), Box<dyn Error>> {
210    let text = std::fs::read_to_string(cue_path)?;
211    let cue_dir = cue_path.parent().unwrap_or(Path::new("."));
212
213    let mut bin_path: Option<PathBuf> = None;
214    let mut pending: Option<(u8, bool)> = None;
215    let mut tracks: Vec<Track> = Vec::new();
216
217    for line in text.lines() {
218        let line = line.trim();
219        let mut fields = line.split_whitespace();
220        let Some(keyword) = fields.next() else {
221            continue;
222        };
223
224        match keyword.to_ascii_uppercase().as_str() {
225            "FILE" => {
226                if bin_path.is_some() {
227                    return Err(
228                        "multi-FILE cue sheets (one file per track) are not handled by \
229                                this example; it assumes a single .bin addressed by sector"
230                            .into(),
231                    );
232                }
233                bin_path = Some(cue_dir.join(quoted_or_first_field(line)?));
234            }
235
236            "TRACK" => {
237                // e.g. `TRACK 03 MODE1/2352`
238                let number: u8 = fields.next().ok_or("TRACK line has no number")?.parse()?;
239                let mode = fields.next().ok_or("TRACK line has no mode")?;
240
241                // The whole image is addressed as a uniform grid of 2352-byte
242                // sectors, so a cooked data track (MODE1/2048) would desync
243                // every offset after it. Refuse rather than read garbage.
244                let sector_size = mode_sector_size(mode);
245                if sector_size != Some(SECTOR_SIZE) {
246                    return Err(format!(
247                        "track {number} is `{mode}`, which is not stored as 2352-byte sectors; \
248                         this example needs a fully raw image"
249                    )
250                    .into());
251                }
252
253                pending = Some((number, mode.eq_ignore_ascii_case("AUDIO")));
254            }
255
256            // `INDEX 01 MM:SS:FF` is where the track proper begins.
257            "INDEX" => {
258                let index = fields.next().ok_or("INDEX line has no number")?;
259                let msf = fields.next().ok_or("INDEX line has no timestamp")?;
260                if index != "01" {
261                    continue;
262                }
263
264                let (number, is_audio) = pending
265                    .take()
266                    .ok_or("INDEX 01 appeared before any TRACK line")?;
267                let start_lba = msf_to_frames(msf)?;
268
269                tracks.push(Track {
270                    number,
271                    start_lba,
272                    start_msf: lba_to_msf(start_lba),
273                    is_audio,
274                });
275            }
276
277            _ => {}
278        }
279    }
280
281    let bin_path = bin_path.ok_or("cue sheet has no FILE line")?;
282    if tracks.is_empty() {
283        return Err("cue sheet declares no tracks".into());
284    }
285    tracks.sort_by_key(|track| track.start_lba);
286
287    // The cue has no leadout; the image's own length is the end of the disc.
288    let bin_bytes = std::fs::metadata(&bin_path)
289        .map_err(|e| format!("cannot open {}: {e}", bin_path.display()))?
290        .len();
291    let leadout_lba = (bin_bytes / SECTOR_SIZE as u64) as u32;
292
293    let toc = Toc {
294        first_track: tracks.first().map_or(1, |track| track.number),
295        last_track: tracks.last().map_or(1, |track| track.number),
296        tracks,
297        leadout_lba,
298    };
299
300    Ok((bin_path, toc))
301}