[CdTime] type for more ergonomic handling of CD-ROM times and addresses
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 10pub struct CdTime { 11 pub minutes: u8, 12 pub seconds: u8, 13 pub frames: u8, 14} 15 16impl CdTime { 17 pub const ZERO: Self = Self { minutes: 0, seconds: 0, frames: 0 }; 18 pub const SECTOR_0_START: Self = Self { minutes: 0, seconds: 2, frames: 0 }; 19 pub const DISC_END: Self = Self { minutes: 60, seconds: 3, frames: 74 }; 20 21 pub const MAX_MINUTES: u8 = 80; 22 pub const SECONDS_PER_MINUTE: u8 = 60; 23 pub const FRAMES_PER_SECOND: u8 = 75; 24 25 pub const MAX_SECTORS: u32 = 360000;
Create a new CdTime value.
This function will panic if any field is out of bounds. Call [CdTime::new_checked] if
this behavior is not desired.
Panics
This function will panic if any field is out of bounds:
- Minutes must be less than 80
- Seconds must be less than 60
- Frames must be less than 75
38 #[must_use] 39 pub fn new(minutes: u8, seconds: u8, frames: u8) -> Self { 40 assert!(minutes < Self::MAX_MINUTES, "Minutes must be less than {}", Self::MAX_MINUTES); 41 assert!( 42 seconds < Self::SECONDS_PER_MINUTE, 43 "Seconds must be less than {}", 44 Self::SECONDS_PER_MINUTE 45 ); 46 assert!( 47 frames < Self::FRAMES_PER_SECOND, 48 "Frames must be less than {}", 49 Self::FRAMES_PER_SECOND 50 ); 51 52 Self { minutes, seconds, frames } 53 }
55 #[must_use] 56 pub fn new_checked(minutes: u8, seconds: u8, frames: u8) -> Option<Self> { 57 (minutes < Self::MAX_MINUTES 58 && seconds < Self::SECONDS_PER_MINUTE 59 && frames < Self::FRAMES_PER_SECOND) 60 .then_some(Self { minutes, seconds, frames }) 61 } 62 63 #[must_use] 64 pub fn to_sector_number(self) -> u32 { 65 (u32::from(Self::SECONDS_PER_MINUTE) * u32::from(self.minutes) + u32::from(self.seconds)) 66 * u32::from(Self::FRAMES_PER_SECOND) 67 + u32::from(self.frames) 68 }
Convert an absolute sector number to a CdTime value.
Panics
This function will panic if sector_number is greater than 360_000, which is the number
of sectors in an 80-minute CD.
76 #[must_use] 77 pub fn from_sector_number(sector_number: u32) -> Self { 78 // All Sega CD sector numbers are less than 360,000 (80 minutes) 79 assert!(sector_number < Self::MAX_SECTORS, "Invalid sector number: {sector_number}"); 80 81 let frames = sector_number % u32::from(Self::FRAMES_PER_SECOND); 82 let seconds = (sector_number / u32::from(Self::FRAMES_PER_SECOND)) 83 % u32::from(Self::SECONDS_PER_MINUTE); 84 let minutes = sector_number 85 / (u32::from(Self::FRAMES_PER_SECOND) * u32::from(Self::SECONDS_PER_MINUTE)); 86 87 Self::new(minutes as u8, seconds as u8, frames as u8) 88 }
90 #[must_use] 91 pub fn to_frames(self) -> u32 { 92 let seconds: u32 = self.seconds.into(); 93 let minutes: u32 = self.minutes.into(); 94 let frames: u32 = self.frames.into(); 95 96 let frames_per_second: u32 = Self::FRAMES_PER_SECOND.into(); 97 let seconds_per_minute: u32 = Self::SECONDS_PER_MINUTE.into(); 98 99 frames + frames_per_second * (seconds + seconds_per_minute * minutes) 100 } 101 102 #[must_use] 103 pub fn from_frames(frames: u32) -> Self { 104 let minutes = 105 frames / (u32::from(Self::FRAMES_PER_SECOND) * u32::from(Self::SECONDS_PER_MINUTE)); 106 let seconds = 107 (frames / u32::from(Self::FRAMES_PER_SECOND)) % u32::from(Self::SECONDS_PER_MINUTE); 108 let frames = frames % u32::from(Self::FRAMES_PER_SECOND); 109 110 Self::new(minutes as u8, seconds as u8, frames as u8) 111 } 112 113 #[must_use] 114 pub fn saturating_sub(self, other: Self) -> Self { 115 if self <= other { CdTime::ZERO } else { self - other } 116 } 117} 118 119impl Add for CdTime { 120 type Output = Self; 121 122 fn add(self, rhs: Self) -> Self::Output { 123 let (frames, carried) = add(self.frames, rhs.frames, false, Self::FRAMES_PER_SECOND); 124 let (seconds, carried) = add(self.seconds, rhs.seconds, carried, Self::SECONDS_PER_MINUTE); 125 let (minutes, _) = add(self.minutes, rhs.minutes, carried, Self::MAX_MINUTES); 126 127 Self { minutes, seconds, frames } 128 } 129} 130 131impl AddAssign for CdTime { 132 fn add_assign(&mut self, rhs: Self) { 133 *self = *self + rhs; 134 } 135} 136 137impl Sub for CdTime { 138 type Output = Self; 139 140 fn sub(self, rhs: Self) -> Self::Output { 141 let (frames, borrowed) = sub(self.frames, rhs.frames, false, Self::FRAMES_PER_SECOND); 142 let (seconds, borrowed) = 143 sub(self.seconds, rhs.seconds, borrowed, Self::SECONDS_PER_MINUTE); 144 let (minutes, _) = sub(self.minutes, rhs.minutes, borrowed, Self::MAX_MINUTES); 145 146 Self { minutes, seconds, frames } 147 } 148} 149 150impl SubAssign for CdTime { 151 fn sub_assign(&mut self, rhs: Self) { 152 *self = *self - rhs; 153 } 154} 155 156impl PartialOrd for CdTime { 157 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 158 Some(self.cmp(other)) 159 } 160} 161 162impl Ord for CdTime { 163 fn cmp(&self, other: &Self) -> Ordering { 164 self.minutes 165 .cmp(&other.minutes) 166 .then(self.seconds.cmp(&other.seconds)) 167 .then(self.frames.cmp(&other.frames)) 168 } 169} 170 171fn add(a: u8, b: u8, overflow: bool, base: u8) -> (u8, bool) { 172 let sum = a + b + u8::from(overflow); 173 (sum % base, sum >= base) 174} 175 176fn sub(a: u8, b: u8, overflow: bool, base: u8) -> (u8, bool) { 177 let operand_r = b + u8::from(overflow); 178 if a < operand_r { (base - (operand_r - a), true) } else { (a - operand_r, false) } 179} 180 181impl FromStr for CdTime { 182 type Err = String; 183 184 fn from_str(s: &str) -> Result<Self, Self::Err> { 185 let bytes = s.as_bytes(); 186 if bytes.len() != 8 { 187 return Err(format!("Invalid time length: {}", bytes.len())); 188 } 189 190 if bytes[2] != b':' || bytes[5] != b':' { 191 return Err(format!("Unexpected time format: {s}")); 192 } 193 194 let err_fn = |_err| format!("Invalid time string: {s}"); 195 let minutes: u8 = s[0..2].parse().map_err(err_fn)?; 196 let seconds: u8 = s[3..5].parse().map_err(err_fn)?; 197 let frames: u8 = s[6..8].parse().map_err(err_fn)?; 198 199 Ok(CdTime { minutes, seconds, frames }) 200 } 201} 202 203impl Display for CdTime { 204 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 205 write!(f, "{:02}:{:02}:{:02}", self.minutes, self.seconds, self.frames) 206 } 207} 208 209#[cfg(test)] 210mod tests { 211 use super::*; 212 213 #[test] 214 fn cd_time_add() { 215 // No carries 216 assert_eq!(CdTime::new(10, 20, 30) + CdTime::new(15, 25, 35), CdTime::new(25, 45, 65)); 217 218 // Frames carry 219 assert_eq!(CdTime::new(10, 20, 30) + CdTime::new(15, 25, 55), CdTime::new(25, 46, 10)); 220 221 // Seconds carry 222 assert_eq!(CdTime::new(10, 20, 30) + CdTime::new(15, 55, 35), CdTime::new(26, 15, 65)); 223 } 224 225 #[test] 226 fn cd_time_sub() { 227 // No borrows 228 assert_eq!(CdTime::new(12, 13, 14) - CdTime::new(7, 7, 7), CdTime::new(5, 6, 7)); 229 230 // Frames borrow 231 assert_eq!(CdTime::new(5, 4, 3) - CdTime::new(1, 1, 10), CdTime::new(4, 2, 68)); 232 233 // Seconds borrow 234 assert_eq!(CdTime::new(15, 5, 39) - CdTime::new(13, 16, 25), CdTime::new(1, 49, 14)); 235 } 236}