use crate::Duration;
use std::ops::Range;
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
pub struct Track {
pub(super) num: u8,
pub(super) pos: TrackPosition,
pub(super) from: u32,
pub(super) to: u32,
}
impl Track {
#[must_use]
pub const fn bytes(self) -> u64 { self.sectors() as u64 * 2352 }
#[must_use]
pub const fn duration(&self) -> Duration { Duration(self.sectors() as u64) }
#[must_use]
pub const fn is_htoa(&self) -> bool {
self.num == 0 &&
self.from == 150 &&
matches!(self.pos, TrackPosition::Invalid)
}
#[must_use]
pub const fn msf(&self) -> (u32, u8, u8) { lba_to_msf(self.from) }
#[must_use]
pub const fn msf_normalized(&self) -> (u32, u8, u8) { lba_to_msf(self.from - 150) }
#[must_use]
pub const fn number(&self) -> u8 { self.num }
#[must_use]
pub const fn position(&self) -> TrackPosition { self.pos }
#[must_use]
pub const fn samples(self) -> u64 { self.duration().samples() }
#[must_use]
pub const fn sectors(&self) -> u32 { self.to - self.from }
#[must_use]
pub const fn sector_range(&self) -> Range<u32> { self.from..self.to }
#[must_use]
pub const fn sector_range_normalized(&self) -> Range<u32> {
self.from - 150..self.to - 150
}
}
#[derive(Debug)]
pub struct Tracks<'a> {
#[expect(clippy::struct_field_names, reason = "They're tracks!")]
tracks: &'a [u32],
leadout: u32,
pos: usize,
}
impl Iterator for Tracks<'_> {
type Item = Track;
#[expect(clippy::cast_possible_truncation, reason = "False positive.")]
fn next(&mut self) -> Option<Self::Item> {
let len = self.tracks.len();
if len <= self.pos { return None; }
let num = (self.pos + 1) as u8;
let pos = TrackPosition::from((self.pos + 1, len));
let from = self.tracks[self.pos];
let to =
if self.pos + 1 < len { self.tracks[self.pos + 1] }
else { self.leadout };
self.pos += 1;
Some(Track { num, pos, from, to })
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
}
impl ExactSizeIterator for Tracks<'_> {
#[inline]
fn len(&self) -> usize { self.tracks.len().saturating_sub(self.pos) }
}
impl std::iter::FusedIterator for Tracks<'_> {}
impl<'a> Tracks<'a> {
pub(super) const fn new(tracks: &'a [u32], leadout: u32) -> Self {
Self { tracks, leadout, pos: 0 }
}
}
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
pub enum TrackPosition {
Invalid,
First,
Middle,
Last,
Only,
}
macro_rules! pos_tuple {
($($ty:ty),+) => ($(
impl From<($ty, $ty)> for TrackPosition {
fn from(src: ($ty, $ty)) -> Self {
if src.0 == 0 || src.1 < src.0 { Self::Invalid }
else if src.0 == 1 {
if src.1 == 1 { Self::Only }
else { Self::First }
}
else if src.0 == src.1 { Self::Last }
else { Self::Middle }
}
}
)+);
}
pos_tuple!(u8, u16, u32, u64, usize);
impl TrackPosition {
#[must_use]
pub const fn is_valid(self) -> bool { ! matches!(self, Self::Invalid) }
#[must_use]
pub const fn is_first(self) -> bool { matches!(self, Self::First | Self::Only) }
#[must_use]
pub const fn is_last(self) -> bool { matches!(self, Self::Last | Self::Only) }
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Invalid => "Invalid",
Self::First => "First",
Self::Middle => "Middle",
Self::Last => "Last",
Self::Only => "Only",
}
}
}
#[expect(clippy::cast_possible_truncation, reason = "False positive.")]
const fn lba_to_msf(sectors: u32) -> (u32, u8, u8) {
let mut s = sectors.wrapping_div(75);
let f = sectors - s * 75;
let m = s.wrapping_div(60);
s -= m * 60;
(m, s as u8, f as u8)
}