diff --git a/examples/custom_retry.rs b/examples/custom_retry.rs new file mode 100644 index 0000000..9366bf6 --- /dev/null +++ b/examples/custom_retry.rs @@ -0,0 +1,40 @@ +/// Reads the first audio track with an aggressive retry configuration +/// suitable for scratched or damaged discs. +/// By default, it already retries multiple times with smaller number +/// of sectors, so this usually should not be necessary, but you can see +/// here that you can tweak details. +use cd_da_reader::{CdReader, RetryConfig}; + +fn main() -> Result<(), Box> { + let reader = CdReader::open_default()?; + let toc = reader.read_toc()?; + + let first_audio = toc + .tracks + .iter() + .find(|t| t.is_audio) + .ok_or("no audio tracks found")?; + + // More attempts, longer backoff, and sector reduction down to 1 + // for maximum resilience on scratched media. + let retry = RetryConfig { + max_attempts: 8, + initial_backoff_ms: 50, + max_backoff_ms: 1000, + reduce_chunk_on_retry: true, + min_sectors_per_read: 1, + }; + + println!( + "Reading track {} with aggressive retry...", + first_audio.number + ); + let data = reader.read_track_with_retry(&toc, first_audio.number, &retry)?; + + let wav = CdReader::create_wav(data); + let filename = format!("track{:02}.wav", first_audio.number); + std::fs::write(&filename, wav)?; + println!("Saved {}", filename); + + Ok(()) +} diff --git a/examples/list_drives.rs b/examples/list_drives.rs new file mode 100644 index 0000000..eaf1919 --- /dev/null +++ b/examples/list_drives.rs @@ -0,0 +1,29 @@ +/// Lists all optical drives detected on the system and whether they contain an audio CD. +/// +/// Note: this does not work on macOS — use `open_default` or `open` with a known path instead. +use cd_da_reader::CdReader; + +fn main() -> Result<(), Box> { + let drives = CdReader::list_drives()?; + + if drives.is_empty() { + println!("No optical drives found."); + return Ok(()); + } + + println!("Found {} drive(s):\n", drives.len()); + for drive in &drives { + let name = drive.display_name.as_deref().unwrap_or("(unknown)"); + let status = if drive.has_audio_cd { + "audio CD inserted" + } else { + "no audio CD" + }; + println!( + "Drive: {}, name: {}, status: [{}]", + drive.path, name, status + ); + } + + Ok(()) +} diff --git a/examples/read_all_tracks.rs b/examples/read_all_tracks.rs new file mode 100644 index 0000000..d3de29d --- /dev/null +++ b/examples/read_all_tracks.rs @@ -0,0 +1,34 @@ +/// Reads every audio track from the default CD drive and saves each as a WAV file. +use cd_da_reader::CdReader; + +fn main() -> Result<(), Box> { + let reader = CdReader::open_default()?; + let toc = reader.read_toc()?; + + let audio_tracks: Vec<_> = toc.tracks.iter().filter(|t| t.is_audio).collect(); + println!("Found {} audio track(s)\n", audio_tracks.len()); + + let mut failed = Vec::new(); + + for track in &audio_tracks { + print!("Reading track {:>2}... ", track.number); + match reader.read_track(&toc, track.number) { + Ok(data) => { + let wav = CdReader::create_wav(data); + let filename = format!("track{:02}.wav", track.number); + std::fs::write(&filename, wav)?; + println!("saved {}", filename); + } + Err(e) => { + println!("FAILED: {}", e); + failed.push(track.number); + } + } + } + + if !failed.is_empty() { + eprintln!("\nFailed to read tracks: {:?}", failed); + } + + Ok(()) +} diff --git a/examples/read_first_track.rs b/examples/read_first_track.rs new file mode 100644 index 0000000..91d73d0 --- /dev/null +++ b/examples/read_first_track.rs @@ -0,0 +1,23 @@ +/// Reads the first audio track from the default CD drive and saves it as a WAV file. +use cd_da_reader::CdReader; + +fn main() -> Result<(), Box> { + let reader = CdReader::open_default()?; + let toc = reader.read_toc()?; + + let first_audio = toc + .tracks + .iter() + .find(|t| t.is_audio) + .ok_or("no audio tracks found")?; + + println!("Reading track {}...", first_audio.number); + let data = reader.read_track(&toc, first_audio.number)?; + + let wav = CdReader::create_wav(data); + let filename = format!("track{:02}.wav", first_audio.number); + std::fs::write(&filename, wav)?; + println!("Saved {}", filename); + + Ok(()) +} diff --git a/examples/read_toc.rs b/examples/read_toc.rs new file mode 100644 index 0000000..cdca7cb --- /dev/null +++ b/examples/read_toc.rs @@ -0,0 +1,47 @@ +/// Opens the default CD drive and prints the Table of Contents. +use cd_da_reader::CdReader; + +fn main() -> Result<(), Box> { + let reader = CdReader::open_default()?; + let toc = reader.read_toc()?; + + println!("Table of Contents\n"); + + println!( + "Tracks {}-{} ({} total), lead-out at LBA {}\n", + toc.first_track, + toc.last_track, + toc.tracks.len(), + toc.leadout_lba, + ); + + for track in &toc.tracks { + let kind = if track.is_audio { "audio" } else { "data " }; + let (m, s, f) = track.start_msf; + let sectors = next_track_lba(&toc, track.number) - track.start_lba; + let duration_secs = sectors as f64 / 75.0; + let mins = (duration_secs / 60.0) as u32; + let secs = (duration_secs % 60.0) as u32; + + println!( + " #{:>2} {} LBA {:>6} MSF {:02}:{:02}.{:02} duration: {:02}:{:02}", + track.number, kind, track.start_lba, m, s, f, mins, secs, + ); + } + + Ok(()) +} + +/// Returns the start LBA of the next track, or the lead-out LBA for the last track. +fn next_track_lba(toc: &cd_da_reader::Toc, track_no: u8) -> u32 { + let idx = toc + .tracks + .iter() + .position(|t| t.number == track_no) + .unwrap(); + if idx + 1 < toc.tracks.len() { + toc.tracks[idx + 1].start_lba + } else { + toc.leadout_lba + } +} diff --git a/examples/read_track.rs b/examples/read_track.rs deleted file mode 100644 index c4a99a2..0000000 --- a/examples/read_track.rs +++ /dev/null @@ -1,56 +0,0 @@ -use cd_da_reader::{CdReader, RetryConfig, TrackStreamConfig}; - -fn main() -> Result<(), Box> { - let drive_path = default_drive_path(); - read_cd(drive_path) -} - -#[cfg(target_os = "windows")] -fn default_drive_path() -> &'static str { - r"\\.\E:" -} - -#[cfg(target_os = "macos")] -fn default_drive_path() -> &'static str { - "disk14" -} - -#[cfg(target_os = "linux")] -fn default_drive_path() -> &'static str { - "/dev/sr0" -} - -fn read_cd(path: &str) -> Result<(), Box> { - let reader = CdReader::open(path)?; - let toc = reader.read_toc()?; - println!("{toc:#?}"); - - let last_audio_track = toc - .tracks - .iter() - .rev() - .find(|track| track.is_audio) - .ok_or_else(|| std::io::Error::other("no audio tracks in TOC"))?; - - println!("Reading track {}", last_audio_track.number); - let stream_cfg = TrackStreamConfig { - sectors_per_chunk: 27, - retry: RetryConfig { - max_attempts: 5, - initial_backoff_ms: 30, - max_backoff_ms: 500, - reduce_chunk_on_retry: true, - min_sectors_per_read: 1, - }, - }; - let mut stream = reader.open_track_stream(&toc, last_audio_track.number, stream_cfg)?; - - let mut pcm = Vec::new(); - while let Some(chunk) = stream.next_chunk()? { - pcm.extend_from_slice(&chunk); - } - let wav = CdReader::create_wav(pcm); - std::fs::write("myfile.wav", wav)?; - - Ok(()) -} diff --git a/examples/stream_last_track.rs b/examples/stream_last_track.rs new file mode 100644 index 0000000..1ed456c --- /dev/null +++ b/examples/stream_last_track.rs @@ -0,0 +1,30 @@ +/// Reads the last audio track using the streaming API and saves it as a WAV file. +use cd_da_reader::{CdReader, TrackStreamConfig}; + +fn main() -> Result<(), Box> { + let reader = CdReader::open_default()?; + let toc = reader.read_toc()?; + + let last_audio = toc + .tracks + .iter() + .rev() + .find(|t| t.is_audio) + .ok_or("no audio tracks found")?; + + println!("Streaming track {}...", last_audio.number); + let mut stream = + reader.open_track_stream(&toc, last_audio.number, TrackStreamConfig::default())?; + + let mut pcm = Vec::new(); + while let Some(chunk) = stream.next_chunk()? { + pcm.extend_from_slice(&chunk); + } + + let wav = CdReader::create_wav(pcm); + let filename = format!("track{:02}.wav", last_audio.number); + std::fs::write(&filename, wav)?; + println!("Saved {}", filename); + + Ok(()) +} diff --git a/examples/stream_with_progress.rs b/examples/stream_with_progress.rs new file mode 100644 index 0000000..95039fc --- /dev/null +++ b/examples/stream_with_progress.rs @@ -0,0 +1,41 @@ +/// Streams the first audio track while printing a live progress line. +use cd_da_reader::{CdReader, TrackStreamConfig}; + +fn main() -> Result<(), Box> { + let reader = CdReader::open_default()?; + let toc = reader.read_toc()?; + + let first_audio = toc + .tracks + .iter() + .find(|t| t.is_audio) + .ok_or("no audio tracks found")?; + + let mut stream = + reader.open_track_stream(&toc, first_audio.number, TrackStreamConfig::default())?; + + let total_secs = stream.total_seconds(); + println!( + "Track {} — {} sectors ({:.0}s)\n", + first_audio.number, + stream.total_sectors(), + total_secs, + ); + + let mut pcm = Vec::new(); + while let Some(chunk) = stream.next_chunk()? { + pcm.extend_from_slice(&chunk); + + let cur = stream.current_seconds(); + let pct = cur / total_secs * 100.0; + eprint!("\r [{:>5.1}s / {:.1}s] {:5.1}%", cur, total_secs, pct,); + } + eprintln!("\r [{:.1}s / {:.1}s] 100.0%", total_secs, total_secs); + + let wav = CdReader::create_wav(pcm); + let filename = format!("track{:02}.wav", first_audio.number); + std::fs::write(&filename, wav)?; + println!("\nSaved {}", filename); + + Ok(()) +} diff --git a/src/windows_read_track.rs b/src/windows_read_track.rs index c558d47..6c9102d 100644 --- a/src/windows_read_track.rs +++ b/src/windows_read_track.rs @@ -10,19 +10,8 @@ use windows_sys::Win32::Storage::IscsiDisc::{ }; use windows_sys::Win32::System::IO::DeviceIoControl; -use crate::utils::get_track_bounds; use crate::windows::SptdWithSense; -use crate::{CdReaderError, RetryConfig, ScsiError, ScsiOp, Toc}; - -pub fn read_track_with_retry( - handle: HANDLE, - toc: &Toc, - track_no: u8, - cfg: &RetryConfig, -) -> Result, CdReaderError> { - let (start_lba, sectors) = get_track_bounds(toc, track_no).map_err(CdReaderError::Io)?; - read_audio_range_with_retry(handle, start_lba, sectors, cfg) -} +use crate::{CdReaderError, RetryConfig, ScsiError, ScsiOp}; // --- READ CD (0xBE): read an arbitrary LBA range as CD-DA (2352 bytes/sector) --- pub fn read_audio_range_with_retry(