Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions examples/custom_retry.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>> {
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(())
}
29 changes: 29 additions & 0 deletions examples/list_drives.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>> {
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(())
}
34 changes: 34 additions & 0 deletions examples/read_all_tracks.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>> {
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(())
}
23 changes: 23 additions & 0 deletions examples/read_first_track.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>> {
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(())
}
47 changes: 47 additions & 0 deletions examples/read_toc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/// Opens the default CD drive and prints the Table of Contents.
use cd_da_reader::CdReader;

fn main() -> Result<(), Box<dyn std::error::Error>> {
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
}
}
56 changes: 0 additions & 56 deletions examples/read_track.rs

This file was deleted.

30 changes: 30 additions & 0 deletions examples/stream_last_track.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>> {
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(())
}
41 changes: 41 additions & 0 deletions examples/stream_with_progress.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>> {
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(())
}
13 changes: 1 addition & 12 deletions src/windows_read_track.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<u8>, 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(
Expand Down
Loading