Add pluggable AudioSectorReader file backing (1.0) - #58
Conversation
Reconstructs the pluggable file/image backing from the pre-1.0 `file-based-backend` branch on top of main's unified read API, since a literal rebase would collide with main's independently-built data-track support. - backend.rs: AudioSectorReader trait, `read_track` free fn, and TrackReadError, with the CdReader impl now reading via `read_sector_range(.., &ReadOptions::default())`. - Expose the support surface the seam needs: free `create_wav` and a public `lba_to_msf` (for building a Toc from image metadata). - examples: file_backend (dependency-free backing) and save_data_track (detect -> read cooked -> save .iso -> mount). - docs/consuming-cd-da-reader.md: downstream-consumer guide covering the options model, sector formats, detection, the data-track workflow, Mode 1 vs Mode 2, file backing, and a pre-1.0 -> 1.0 migration table. - README: data-track and file-image sections. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The data-track example read the entire track into a Vec before writing — a full data track can be hundreds of MB. Switch it to the streaming API (open_track_stream_with_options) so cooked/raw chunks are written as they arrive and peak memory is one ~64 KB chunk regardless of track size. Both the ISO (Mode1Cooked) and Mode 2 branches now share one stream_track_to_file helper with a progress line, which also showcases that reading and streaming run over the same options/read path. Docs updated to recommend streaming for large images. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First release carrying the breaking 1.0 API (SectorReadFormat, per-read ReadOptions, detect_track_format) plus the AudioSectorReader file backing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@danifunker hey, thanks for the PR. I looked over it but I am a bit confused on how is it supposed to be used. In the description you mentioned the BIN/CUE and network. So is it intended to turn CUE + PCM data into individual files? But if you have that, wouldn't you have tracks as actual tracks and not just PCM data? And if you have a CD image, wouldn't it be easier to mount it into a virtual drive? |
|
Thanks for review it so quickly! I may read like a format-conversion feature, but it's really just an abstraction seam - it parses/converts nothing and pulls in no image-format deps. I am hoping for a native rust-only solution for my projects. AudioSectorReader is a one-method trait: read_audio_sectors(start_lba, count) -> Vec of raw 2352-byte sectors. BIN/CUE, CHD, and "network" were just examples of backings a user could implement - not formats the crate reads. The value is reusing everything above the raw read (track-boundary math incl. the CD-Extra trailing-gap rule, plus create_wav), which is currently welded to the SCSI path. CdReader now implements the trait too, so drive- and file-backed code share one read_track. To answer your questions:
I've already implemented this in one of my cross-platform projects which helps users identify what discs they may be looking at, and provides a way to "listen to the track" in the case users are familiar with the music (think vintage video-game CDs, or older OSTs/Anime CDs). |
|
Alright, I am going to review it properly tomorrow! We definitely don't want to bundle the 1.0 update with this PR, I update all releases as a separate PR/commit |
|
Sounds fair! I thought you may have overlooked that a bit. Do you want me to revert that part of the PR? |
Bloomca
left a comment
There was a problem hiding this comment.
Hey, apologies for the delay. I think it looks pretty good overall, I have 2 main questions:
- is it worth making
fn read_audio_sectorsas&mut self? Or I guess the consumers can wrap the mutating part into their own function - do you think it is worth wrapping around the streaming API part? You said that in some project you provide "listen to", but that would require to read the entire track first.
I also think that creating a ToC is kinda awkward if you can't make it automatically. How do you create that in your project?
Address PR Bloomca#58 review feedback: - Fold the backing error into CdReaderError::Backend(Box<dyn Error + Send + Sync>); read_track now returns CdReaderError and the generic TrackReadError<E> is gone, so file- and drive-backed reads share one error type. The backing's own error is preserved as the boxed source(). - Add TrackBounds { PhysicalDisc, GaplessImage }. The CD-Extra trailing-gap rule is correct for a physical disc but wrong for a gapless CHD/BIN extract, where it would drop ~2.5 min off the last audio track before a data session. Both the buffered path (read_track_with_bounds) and the stream take the geometry; PhysicalDisc stays the default, so existing behaviour is unchanged. - Add AudioTrackStream, the file/image counterpart to TrackStream (next_chunk, seek_to_sector/seconds, current/total_sectors/seconds, with_sectors_per_chunk). Open it with open_track_stream (TOC + physical), open_track_stream_with_bounds (TOC + explicit geometry), or open_track_stream_at (raw absolute sector range, for backings that compute their own layout). "Listen to" can now stream instead of buffering a whole track. - Drop docs/consuming-cd-da-reader.md; the file-backing story lives in the backend module docs. - Demonstrate streaming in examples/file_backend.rs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for working with me on this review! I've addressed all of it - summary below. ToC creation
Streaming One thing worth flagging Since only the backing knows how its own image is laid out, bounds resolution now takes an explicit
It's on both the buffered path ( |
The CD-Extra gap subtraction applies whenever a TOC's addressing includes the inter-session gap — a physical disc OR an image that preserves the disc's real LBAs — and must be skipped only for a gap-stripped contiguous extract. The old PhysicalDisc/GaplessImage names wrongly implied image == gapless. Rename the variants to name that geometry instead of the medium: PhysicalDisc -> SessionGap (addressing includes the gap; default) GaplessImage -> Gapless (tracks contiguous, gap stripped) Reword the module/item docs, the gapless-bounds helper doc, and the example comment to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reading a raw optical device can require more rights than the calling
process has, and open_path() has no way to gain them — the descriptor
has to come from elsewhere. On macOS a plain open("/dev/rdiskN") returns
EPERM for an unprivileged GUI app, and the caller's only recourse today
is to fail.
Add CdReader::from_file(File) (unix only) so a caller that hits EPERM /
EACCES can escalate through /usr/libexec/authopen — which shows the
native macOS auth dialog and passes the fd back over SCM_RIGHTS — and
hand the resulting handle over. Drive::from_file adopts it on both unix
backends and closes it on drop, exactly as if it had opened the node.
Windows is excluded: its Drive wraps a HANDLE, not a fd.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Adds a hardware-independent seam for reading CD-DA tracks from a file/image
(CHD, BIN/CUE, in-memory, network, …) reusing the crate's existing TOC/track
machinery, with no image-format dependencies pulled into the crate. Also bumps
to
1.0.0, the first release carrying the breaking 1.0 API already onmain.This is the
file-based-backendwork rebuilt on top of the 1.0 API rather thangit-rebased:
mainand that branch had independently built data-track support, soa literal rebase would have collided and dragged back the older implementation.
Only the genuinely-unique file backing is ported here.
Public API additions
AudioSectorReadertrait — implementread_audio_sectors(start_lba, count)forany backing that yields raw CD-DA sectors (2352 B, 16-bit signed LE, stereo).
read_track(&src, &toc, n) -> Result<Vec<u8>, TrackReadError<E>>— thefile/image counterpart to
CdReader::read_track;TrackReadErrorkeeps a badtrack request (
Toc) separate from a backing failure (Backend(E)).create_wav(Vec<u8>)and publiclba_to_msf(u32)— the support surface abacking needs (WAV wrapping without naming
CdReader; building aTocfromimage metadata).
CdReaderitself implementsAudioSectorReadeked code share the genericread_track` path.No changes to existing drive behavior.
Examples
examples/file_backend.rs— dependency-free ihe seamwithout a decoder).
examples/save_data_track.rs— detect a data itcooked to a mountable
.iso(bounded memory),t command;Mode 2 falls back to saving complete raw secto
Docs
docs/consuming-cd-da-reader.md— downstream-del,sector formats,
detect_track_format, the dats Mode 2,file backing, and a pre-1.0 → 1.0 migration ta
Verification
cargo fmt --check,cargo clippy --all-targets -- -D warnings, andcargo test(49 unit + 6 doctests) all pass.fnd (validRIFF…WAVE, correct sizes). The drive-dependent examples (save_data_track,read_data_track`) are compit pathneeds a physical mixed-mode disc.
Note for reviewers
The
1.0.0bump rides along with this feature. If you'd rather tag the releaseindependently, land the version commit (`885335ed keep
this PR purely additive — or just squash-merge.