Skip to main content

play_audio_track/
play_audio_track.rs

1//! Reads a short preview of the first audio track from the default drive and
2//! plays it out loud, so you can confirm by ear that audio extraction works —
3//! including on a mixed-mode / enhanced CD where data tracks sit alongside the
4//! audio ones.
5//!
6//! It reads only the first N seconds (30 by default) rather than the whole
7//! track, so it returns quickly. CD-DA is 75 sectors/second and already raw PCM
8//! (44100 Hz, 16-bit signed little-endian, stereo), which is exactly WAV's
9//! native format, so `create_wav` just prepends a 44-byte RIFF header and the
10//! result is directly playable — no codecs, no extra crates.
11//!
12//! Usage:
13//!   cargo run --example play_audio_track            # first 30 seconds
14//!   cargo run --example play_audio_track -- 60      # first 60 seconds
15//!
16//! The WAV is written under `target/example-output` and then handed to the
17//! platform's built-in player (`afplay` on macOS, `Media.SoundPlayer` on
18//! Windows). On other platforms it is saved for playback with another player.
19mod common;
20
21use std::path::Path;
22
23use cd_da_reader::{CdReader, ReadOptions, create_wav};
24
25/// CD-DA plays 75 sectors (each 2352 bytes) per second.
26const SECTORS_PER_SECOND: u32 = 75;
27
28fn main() -> Result<(), Box<dyn std::error::Error>> {
29    let output_dir = common::fresh_output_dir("play_audio_track")?;
30    let seconds: u32 = match std::env::args().nth(1) {
31        Some(a) => a.parse()?,
32        None => 30,
33    };
34
35    let reader = CdReader::open_default()?;
36    let toc = reader.read_toc()?;
37
38    let track = toc
39        .tracks
40        .iter()
41        .find(|t| t.is_audio)
42        .ok_or("no audio tracks found on this disc")?;
43
44    // Clamp the preview to what the track actually holds: the track ends where
45    // the next track starts, or at the lead-out if it's the last one.
46    let track_end = toc
47        .tracks
48        .iter()
49        .map(|t| t.start_lba)
50        .filter(|&lba| lba > track.start_lba)
51        .min()
52        .unwrap_or(toc.leadout_lba);
53    let track_sectors = track_end - track.start_lba;
54    let sectors = (seconds * SECTORS_PER_SECOND).min(track_sectors);
55    let actual_seconds = sectors / SECTORS_PER_SECOND;
56
57    println!(
58        "Reading first {actual_seconds}s ({sectors} sectors) of audio track #{}...",
59        track.number
60    );
61    let pcm = reader.read_sector_range(track.start_lba, sectors, &ReadOptions::default())?;
62    println!(
63        "Read {} bytes of PCM ({:.1} MiB)",
64        pcm.len(),
65        pcm.len() as f64 / (1024.0 * 1024.0)
66    );
67
68    let output_path = output_dir.join(format!("track{:02}_preview.wav", track.number));
69    std::fs::write(&output_path, create_wav(pcm))?;
70    println!("Saved {}", output_path.display());
71
72    play(&output_path)
73}
74
75/// Hand the WAV to the OS's built-in player and block until it finishes.
76#[cfg(target_os = "macos")]
77fn play(path: &Path) -> Result<(), Box<dyn std::error::Error>> {
78    println!("Playing (Ctrl+C to stop)...");
79    let status = std::process::Command::new("afplay").arg(path).status()?;
80    if !status.success() {
81        return Err("afplay exited with an error".into());
82    }
83    Ok(())
84}
85
86#[cfg(target_os = "windows")]
87fn play(path: &Path) -> Result<(), Box<dyn std::error::Error>> {
88    println!("Playing (this blocks until the preview ends)...");
89    // SoundPlayer.PlaySync plays a WAV synchronously using the built-in player.
90    let escaped_path = path.display().to_string().replace('\'', "''");
91    let script = format!("(New-Object Media.SoundPlayer '{escaped_path}').PlaySync()");
92    let status = std::process::Command::new("powershell")
93        .args(["-NoProfile", "-Command", &script])
94        .status()?;
95    if !status.success() {
96        return Err("powershell SoundPlayer exited with an error".into());
97    }
98    Ok(())
99}
100
101#[cfg(not(any(target_os = "macos", target_os = "windows")))]
102fn play(path: &Path) -> Result<(), Box<dyn std::error::Error>> {
103    println!(
104        "Saved the WAV — play it with your audio player, e.g. `aplay {}`.",
105        path.display()
106    );
107    Ok(())
108}