Skip to main content

open_track_stream_with_bounds

Function open_track_stream_with_bounds 

Source
pub fn open_track_stream_with_bounds<'a, R: AudioSectorReader>(
    src: &'a R,
    toc: &Toc,
    track_no: u8,
    bounds: TrackBounds,
) -> Result<AudioTrackStream<'a, R>, CdReaderError>
Expand description

Open a streaming reader for a track with an explicit TrackBounds geometry. Use TrackBounds::Gapless for a contiguous, gap-stripped layout.

§Errors

Returns CdReaderError::Io if the track is absent or its bounds are invalid.

Examples found in repository?
examples/bin_cue_backend.rs (line 145)
105fn main() -> Result<(), Box<dyn Error>> {
106    let output_dir = common::fresh_output_dir("bin_cue_backend")?;
107
108    let cue_path = match std::env::args().nth(1) {
109        Some(path) => PathBuf::from(path),
110        None => {
111            println!("No .cue argument given — writing a demo image to work against.\n");
112            write_demo_image(&output_dir)?
113        }
114    };
115
116    let (bin_path, toc) = parse_cue(&cue_path)?;
117    println!("Cue:   {}", cue_path.display());
118    println!("Bin:   {}", bin_path.display());
119    println!(
120        "Toc:   {} tracks, leadout at LBA {}\n",
121        toc.tracks.len(),
122        toc.leadout_lba
123    );
124
125    let image = BinImage {
126        bin: File::open(&bin_path)?,
127    };
128
129    // A single-FILE cue is a contiguous run of sectors, so the CD-Extra
130    // inter-session gap is not part of the addressing. See `explain_bounds`.
131    let bounds = TrackBounds::Gapless;
132    explain_bounds(&image, &toc)?;
133
134    for track in toc.tracks.iter().filter(|track| track.is_audio) {
135        let pcm = read_track_with_bounds(&image, &toc, track.number, bounds)?;
136        let wav_path = output_dir.join(format!("track{:02}.wav", track.number));
137        std::fs::write(&wav_path, create_wav(pcm))?;
138
139        println!("track {:02}: wrote {}", track.number, wav_path.display());
140    }
141
142    // The same backing streams instead of buffering — for a 74-minute image
143    // that is the difference between one chunk and ~650 MB resident.
144    if let Some(first_audio) = toc.tracks.iter().find(|track| track.is_audio) {
145        let mut stream = open_track_stream_with_bounds(&image, &toc, first_audio.number, bounds)?;
146        let (mut chunks, mut bytes) = (0u32, 0usize);
147        while let Some(chunk) = stream.next_chunk()? {
148            chunks += 1;
149            bytes += chunk.len();
150        }
151        println!(
152            "\nstreamed track {:02}: {bytes} bytes in {chunks} chunks ({:.1}s)",
153            first_audio.number,
154            stream.total_seconds()
155        );
156    }
157
158    Ok(())
159}
160
161/// Show what the two [`TrackBounds`] policies do on this disc.
162///
163/// They differ on exactly one track — the last audio track before a trailing
164/// data session — so on a plain audio disc this prints nothing.
165fn explain_bounds(image: &BinImage, toc: &Toc) -> Result<(), Box<dyn Error>> {
166    let Some(track) = last_audio_before_data(toc) else {
167        return Ok(());
168    };
169
170    let gapless =
171        open_track_stream_with_bounds(image, toc, track, TrackBounds::Gapless)?.total_sectors();
172    let physical = match open_track_stream_with_bounds(image, toc, track, TrackBounds::SessionGap) {
173        Ok(stream) => format!("{} sectors", stream.total_sectors()),
174        // The gap is larger than the track, so subtracting it underflows.
175        Err(e) => format!("fails ({e})"),
176    };
177
178    println!(
179        "Track {track:02} is the last audio track before a data track, the one track \
180         the two bounds policies disagree on:\n  \
181         Gapless (used here): {gapless} sectors\n  \
182         SessionGap:          {physical}\n"
183    );
184    Ok(())
185}