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}