1use displaydoc::Display;
21use num_enum::{TryFromPrimitive, TryFromPrimitiveError};
22use thiserror::Error;
23use tracing::debug;
24use winnow::{
25 Parser,
26 binary::{
27 be_u16,
28 bits::{bits, bool, take as bits_take},
29 length_take, u8,
30 },
31 error::{ContextError, StrContext},
32 token::take,
33};
34
35use crate::{
36 Mmc,
37 mmc::{Cdb, MmcDirection, MmcError},
38};
39
40impl Mmc {
42 pub fn audio_status(&self) -> Result<MmcAudioStatus, MmcAudioStatusError> {
44 let data =
45 self.read_subchannel(AddressFormat::Lba, SubchannelParameter::CdCurrentPosition)?;
46 let status = parse_header(&mut data.as_slice())?;
47
48 status.ok_or(MmcAudioStatusError::NotSupported)
49 }
50
51 pub fn media_catalog_number(&self) -> Result<Option<String>, MmcSubchannelError> {
53 let data = self.read_subchannel(AddressFormat::Lba, SubchannelParameter::Mcn)?;
54 let input = &mut data.as_slice();
55 parse_header(input)?;
56
57 let format_code =
58 u8::<_, ContextError>.verify(|code| *code == SubchannelParameter::Mcn.discriminant());
59 let mcval = bits(bool::<_, ContextError>);
60 let (_format_code, _, mcval, mcn, _zero, _aframe) = (
61 format_code,
62 take(RESERVED1_SIZE),
63 mcval,
64 take(MCN_SIZE),
65 u8.verify(|zero| *zero == 0),
66 u8, )
68 .context(StrContext::Label("media catalog number descriptor"))
69 .parse_next(input)?;
70
71 if !mcval {
72 return Ok(None);
73 }
74 let mcn =
75 String::from_utf8(mcn.to_vec()).expect("mcn should not contain non utf8 characters");
76
77 return Ok(Some(mcn));
78
79 const RESERVED1_SIZE: usize = 3; const MCN_SIZE: usize = 13;
81 }
82
83 pub fn isrc(&self, track_number: TrackNumber) -> Result<Option<String>, MmcSubchannelError> {
85 let param = SubchannelParameter::Isrc {
86 track_number: track_number.0,
87 };
88 let data = self.read_subchannel(AddressFormat::Lba, param)?;
89 let input = &mut data.as_slice();
90 parse_header(input)?;
91
92 let format_code = u8::<_, ContextError>.verify(|code| *code == param.discriminant());
93 let adr_and_control = bits((
94 bits_take::<_, u8, _, ContextError>(ADR_BITS),
95 bits_take::<_, u8, _, _>(CONTROL_BITS),
96 ));
97 let tcval = bits(bool::<_, ContextError>);
98 let (_format_code, _adr_and_control, _track, _, tcval, isrc, _zero, _aframe) = (
99 format_code,
100 adr_and_control,
101 u8.verify(|track| *track == track_number.0),
102 take(RESERVED3_SIZE),
103 tcval,
104 take(ISRC_SIZE),
105 u8.verify(|zero| *zero == 0),
106 u8, )
108 .context(StrContext::Label("isrc descriptor"))
109 .parse_next(input)?;
110
111 if !tcval {
112 return Ok(None);
113 }
114 let isrc =
115 String::from_utf8(isrc.to_vec()).expect("isrc should not contain non utf8 characters");
116
117 return Ok(Some(isrc));
118
119 const ADR_BITS: usize = 4;
120 const CONTROL_BITS: usize = 4;
121 const RESERVED3_SIZE: usize = 1;
122 const ISRC_SIZE: usize = 12;
123 }
124
125 pub fn cd_current_position(&self) -> Result<CdCurrentPosition, MmcSubchannelError> {
127 let param = SubchannelParameter::CdCurrentPosition;
128 let data = self.read_subchannel(AddressFormat::Time, param)?;
129 let input = &mut data.as_slice();
130 parse_header(input)?;
131
132 let format_code = u8::<_, ContextError>.verify(|code| *code == param.discriminant());
133 let adr_and_control = bits((
134 bits_take::<_, u8, _, ContextError>(ADR_BITS),
135 bits_take::<_, u8, _, _>(CONTROL_BITS),
136 ));
137 let (_, _adr_and_control, track, index, absolute_address, relative_address) = (
138 format_code,
139 adr_and_control,
140 u8, u8, take(ABSOLUTE_ADDR_SIZE),
143 take(RELATIVE_ADDR_SIZE),
144 )
145 .context(StrContext::Label("cd current position descriptor"))
146 .parse_next(input)?;
147
148 let absolute_position = TimePosition {
149 hour: absolute_address[0],
150 minute: absolute_address[1],
151 second: absolute_address[2],
152 frame: absolute_address[3],
153 };
154 let relative_position = TimePosition {
155 hour: relative_address[0],
156 minute: relative_address[1],
157 second: relative_address[2],
158 frame: relative_address[3],
159 };
160
161 return Ok(CdCurrentPosition {
162 track,
163 index,
164 absolute_position,
165 relative_position,
166 });
167
168 const ADR_BITS: usize = 4;
169 const CONTROL_BITS: usize = 4;
170 const ABSOLUTE_ADDR_SIZE: usize = 4;
171 const RELATIVE_ADDR_SIZE: usize = 4;
172 }
173
174 fn read_subchannel(
176 &self,
177 address_format: AddressFormat,
178 param: SubchannelParameter,
179 ) -> Result<SubchannelData, MmcError> {
180 let mut data = SubchannelData::default();
181 let mut cdb = Cdb::default();
182 cdb[0] = OPCODE;
183 if let AddressFormat::Time = address_format {
184 cdb[1] |= 1 << TIME_BITPOS;
185 }
186 cdb[2] |= 1 << SUBQ_BITPOS; cdb[3] = param.discriminant(); if let SubchannelParameter::Isrc { track_number } = param {
189 cdb[6] = track_number;
190 }
191 cdb[7..9].copy_from_slice(&(data.len() as u16).to_be_bytes());
192
193 self.run_command(Some(MmcDirection::Read), &mut data, cdb)?;
194
195 return Ok(data);
196
197 const OPCODE: u8 = 0x42;
198 const SUBQ_BITPOS: usize = 6;
199 const TIME_BITPOS: usize = 1;
200 }
201}
202
203#[repr(u8)]
205#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive)]
206pub enum MmcAudioStatus {
207 PlayInProgress = 0x11,
208 PlayPaused = 0x12,
209 PlayCompleted = 0x13,
210 PlayStopped = 0x14,
211
212 #[default]
213 NoStatus = 0x15,
214}
215
216#[derive(Debug, Display, Error)]
218pub enum MmcAudioStatusError {
219 NotSupported,
221
222 Cmd(#[from] MmcError),
224
225 InvalidResponse(String),
227}
228impl From<MmcSubchannelError> for MmcAudioStatusError {
229 fn from(value: MmcSubchannelError) -> Self {
230 match value {
231 MmcSubchannelError::Cmd(error) => Self::Cmd(error),
232 MmcSubchannelError::InvalidResponse(error) => Self::InvalidResponse(error),
233 }
234 }
235}
236
237#[allow(unused)]
239enum AddressFormat {
240 Lba,
241 Time,
242}
243
244#[repr(u8)]
245#[allow(unused)]
246#[derive(Clone, Copy, Debug)]
247enum SubchannelParameter {
248 CdCurrentPosition = 0x1,
249 Mcn = 0x2,
250 Isrc { track_number: u8 } = 0x3,
251}
252impl SubchannelParameter {
253 fn discriminant(&self) -> u8 {
254 unsafe { *(&raw const *self).cast::<u8>() }
260 }
261}
262
263type SubchannelData = [u8; 24];
264
265fn parse_header(input: &mut &[u8]) -> Result<Option<MmcAudioStatus>, MmcSubchannelError> {
266 debug!(header = ?input, "parse_header()");
267 let (_, audio_status, remainder) = (u8::<_, ContextError>, u8, length_take(be_u16))
268 .context(StrContext::Label("READ SUB-CHANNEL response header"))
269 .parse_next(input)?;
270 *input = remainder;
271
272 (audio_status != 0)
273 .then(|| MmcAudioStatus::try_from(audio_status))
274 .transpose()
275 .map_err(MmcSubchannelError::from)
276}
277
278#[non_exhaustive]
280#[derive(Debug, Display, Error)]
281pub enum MmcSubchannelError {
282 Cmd(#[from] MmcError),
284
285 InvalidResponse(String),
287}
288impl From<ContextError> for MmcSubchannelError {
289 fn from(err: ContextError) -> Self {
290 Self::InvalidResponse(err.to_string())
291 }
292}
293impl<T: TryFromPrimitive> From<TryFromPrimitiveError<T>> for MmcSubchannelError {
294 fn from(err: TryFromPrimitiveError<T>) -> Self {
295 Self::InvalidResponse(err.to_string())
296 }
297}
298
299#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
304pub struct TrackNumber(u8);
305impl TryFrom<u8> for TrackNumber {
306 type Error = InvalidTrackNumber;
307
308 fn try_from(track_number: u8) -> Result<Self, Self::Error> {
313 if (1..=99).contains(&track_number) {
314 Ok(Self(track_number))
315 } else {
316 Err(InvalidTrackNumber(track_number))
317 }
318 }
319}
320impl Default for TrackNumber {
321 fn default() -> Self {
322 Self(1)
323 }
324}
325#[derive(Debug, Display, Error)]
327pub struct InvalidTrackNumber(u8);
328
329#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
331pub struct CdCurrentPosition {
332 pub track: u8,
334
335 pub index: u8,
337
338 pub absolute_position: TimePosition,
340
341 pub relative_position: TimePosition,
343}
344#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
346pub struct TimePosition {
347 pub hour: u8,
348 pub minute: u8,
349 pub second: u8,
350 pub frame: u8,
351}
352
353#[cfg(test)]
354mod tests {
355 use tracing::info;
356
357 use super::*;
358
359 #[test_log::test(test)]
360 #[ignore = "requires a disc drive with mmc"]
361 fn audio_status() {
362 let audio_status = Mmc::new().unwrap().audio_status();
363 info!(?audio_status);
364 assert!(matches!(
365 audio_status,
366 Ok(_) | Err(MmcAudioStatusError::NotSupported)
367 ));
368 }
369
370 #[test_log::test(test)]
371 #[ignore = "requires a disc drive with mmc"]
372 fn media_catalog_number() {
373 let mcn = Mmc::new().unwrap().media_catalog_number().unwrap();
374 info!(?mcn);
375 }
376
377 #[test_log::test(test)]
378 #[ignore = "requires a disc drive with mmc"]
379 fn isrc() {
380 let isrc = Mmc::new().unwrap().isrc(TrackNumber::default()).unwrap();
381 info!(?isrc);
382 }
383
384 #[test_log::test(test)]
385 #[ignore = "requires a disc drive with mmc"]
386 fn cd_current_position() {
387 let cd_current_position = Mmc::new().unwrap().cd_current_position().unwrap();
388 info!(?cd_current_position);
389 }
390}