use crate::subcode::{compute_subcode_crc, deinterleave_subcode, interleave_subcode};
use crate::types::Msf;
use std::io::{self, Read};
pub const SUBCODE_FRAME_SIZE: usize = 96;
pub const SUBCODE_CHANNEL_SIZE: usize = 12;
#[inline]
pub fn from_bcd(val: u8) -> u8 {
((val >> 4) * 10) + (val & 0x0F)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubFrame {
pub channels: [[u8; SUBCODE_CHANNEL_SIZE]; 8],
}
impl Default for SubFrame {
fn default() -> Self {
Self {
channels: [[0u8; SUBCODE_CHANNEL_SIZE]; 8],
}
}
}
impl SubFrame {
pub fn new() -> Self {
Self::default()
}
pub fn from_deinterleaved(bytes: &[u8; SUBCODE_FRAME_SIZE]) -> Self {
let mut channels = [[0u8; SUBCODE_CHANNEL_SIZE]; 8];
for (ch, channel) in channels.iter_mut().enumerate() {
let start = ch * SUBCODE_CHANNEL_SIZE;
channel.copy_from_slice(&bytes[start..start + SUBCODE_CHANNEL_SIZE]);
}
Self { channels }
}
pub fn to_deinterleaved(&self, out: &mut [u8; SUBCODE_FRAME_SIZE]) {
for (ch, channel) in self.channels.iter().enumerate() {
let start = ch * SUBCODE_CHANNEL_SIZE;
out[start..start + SUBCODE_CHANNEL_SIZE].copy_from_slice(channel);
}
}
pub fn to_interleaved(&self) -> [u8; SUBCODE_FRAME_SIZE] {
let mut out = [0u8; SUBCODE_FRAME_SIZE];
interleave_subcode(&self.channels, &mut out);
out
}
pub fn from_interleaved(bytes: &[u8; SUBCODE_FRAME_SIZE]) -> Self {
let mut channels = [[0u8; SUBCODE_CHANNEL_SIZE]; 8];
deinterleave_subcode(bytes, &mut channels);
Self { channels }
}
pub fn q_channel(&self) -> &[u8; SUBCODE_CHANNEL_SIZE] {
&self.channels[1]
}
pub fn q_channel_mut(&mut self) -> &mut [u8; SUBCODE_CHANNEL_SIZE] {
&mut self.channels[1]
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubChannelQ {
pub control: u8,
pub adr: u8,
pub track_num: u8,
pub index: u8,
pub rel_msf: Msf,
pub abs_msf: Msf,
pub crc: u16,
pub raw: [u8; 12],
}
fn decode_6bit_char(val: u8) -> Option<char> {
match val {
0..=9 => Some((b'0' + val) as char),
17..=42 => Some((b'A' + (val - 17)) as char),
_ => None,
}
}
impl SubChannelQ {
pub fn parse(q_bytes: &[u8; 12]) -> Self {
let control = (q_bytes[0] >> 4) & 0x0F;
let adr = q_bytes[0] & 0x0F;
let track_num = from_bcd(q_bytes[1]);
let index = from_bcd(q_bytes[2]);
let rel_msf = Msf::new(
from_bcd(q_bytes[3]),
from_bcd(q_bytes[4]),
from_bcd(q_bytes[5]),
);
let abs_msf = Msf::new(
from_bcd(q_bytes[7]),
from_bcd(q_bytes[8]),
from_bcd(q_bytes[9]),
);
let crc = u16::from_be_bytes([q_bytes[10], q_bytes[11]]);
Self {
control,
adr,
track_num,
index,
rel_msf,
abs_msf,
crc,
raw: *q_bytes,
}
}
pub fn verify_crc(&self) -> bool {
let computed = compute_subcode_crc(&self.raw[0..10]);
computed == self.crc
}
pub fn extract_mcn(&self) -> Option<String> {
if self.adr != 0x02 || !self.verify_crc() {
return None;
}
let mut s = String::with_capacity(13);
for &b in &self.raw[1..7] {
let d1 = (b >> 4) & 0x0F;
let d2 = b & 0x0F;
if d1 > 9 || d2 > 9 {
return None;
}
s.push((b'0' + d1) as char);
s.push((b'0' + d2) as char);
}
let d13 = (self.raw[7] >> 4) & 0x0F;
if d13 > 9 {
return None;
}
s.push((b'0' + d13) as char);
Some(s)
}
pub fn extract_isrc(&self) -> Option<String> {
if self.adr != 0x03 || !self.verify_crc() {
return None;
}
let c1 = decode_6bit_char((self.raw[1] >> 2) & 0x3F)?;
let c2 = decode_6bit_char(((self.raw[1] & 0x03) << 4) | ((self.raw[2] >> 4) & 0x0F))?;
let c3 = decode_6bit_char(((self.raw[2] & 0x0F) << 2) | ((self.raw[3] >> 6) & 0x03))?;
let c4 = decode_6bit_char(self.raw[3] & 0x3F)?;
let c5 = decode_6bit_char((self.raw[4] >> 2) & 0x3F)?;
let y1 = (self.raw[5] >> 4) & 0x0F;
let y2 = self.raw[5] & 0x0F;
if y1 > 9 || y2 > 9 {
return None;
}
let s1 = (self.raw[6] >> 4) & 0x0F;
let s2 = self.raw[6] & 0x0F;
let s3 = (self.raw[7] >> 4) & 0x0F;
let s4 = self.raw[7] & 0x0F;
let s5 = (self.raw[8] >> 4) & 0x0F;
if s1 > 9 || s2 > 9 || s3 > 9 || s4 > 9 || s5 > 9 {
return None;
}
let mut res = String::with_capacity(12);
res.push(c1);
res.push(c2);
res.push(c3);
res.push(c4);
res.push(c5);
res.push((b'0' + y1) as char);
res.push((b'0' + y2) as char);
res.push((b'0' + s1) as char);
res.push((b'0' + s2) as char);
res.push((b'0' + s3) as char);
res.push((b'0' + s4) as char);
res.push((b'0' + s5) as char);
Some(res)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubIndexEvent {
pub track_num: u8,
pub point: u8,
pub lba: u32,
pub rel_msf: Msf,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct SubScanSummary {
pub total_sectors: u64,
pub crc_errors: u64,
pub catalog: Option<String>,
pub track_isrcs: std::collections::BTreeMap<u8, String>,
pub sub_indices: Vec<SubIndexEvent>,
}
pub struct SubScanner;
impl SubScanner {
pub fn scan<R: Read>(mut reader: R) -> io::Result<SubScanSummary> {
let mut summary = SubScanSummary::default();
let mut buf = [0u8; 7200]; let mut current_track: u8 = 0;
let mut current_point: u8 = 0;
let mut lba: u32 = 0;
loop {
let mut read_bytes = 0;
while read_bytes < buf.len() {
let n = reader.read(&mut buf[read_bytes..])?;
if n == 0 {
break;
}
read_bytes += n;
}
if read_bytes == 0 {
break;
}
let complete_frames = read_bytes / SUBCODE_FRAME_SIZE;
for f in 0..complete_frames {
let Some(frame_slice) = buf
.get(f * 96..(f + 1) * 96)
.and_then(|s| s.try_into().ok())
else {
break;
};
let frame = SubFrame::from_deinterleaved(frame_slice);
let q = SubChannelQ::parse(frame.q_channel());
summary.total_sectors += 1;
if !q.verify_crc() {
summary.crc_errors += 1;
} else {
match q.adr {
0x01 => {
let tno = q.track_num;
let point = q.index;
if tno != current_track {
current_track = tno;
current_point = point;
} else if point != current_point {
if point > 1 {
summary.sub_indices.push(SubIndexEvent {
track_num: tno,
point,
lba,
rel_msf: q.rel_msf,
});
}
current_point = point;
}
}
0x02 => {
if summary.catalog.is_none() {
if let Some(mcn) = q.extract_mcn() {
summary.catalog = Some(mcn);
}
}
}
0x03 => {
if let Some(isrc) = q.extract_isrc() {
let t = if current_track > 0 { current_track } else { 1 };
summary.track_isrcs.entry(t).or_insert(isrc);
}
}
_ => {}
}
}
lba += 1;
}
if read_bytes % SUBCODE_FRAME_SIZE != 0 {
break;
}
}
Ok(summary)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::subcode::generate_q_subcode;
#[test]
fn test_subframe_channel_slicing_and_roundtrip() {
let mut sample_deint = [0u8; 96];
for (i, b) in sample_deint.iter_mut().enumerate() {
*b = (i as u8).wrapping_mul(17).wrapping_add(3);
}
let frame = SubFrame::from_deinterleaved(&sample_deint);
assert_eq!(frame.q_channel(), &sample_deint[12..24]);
let mut reconstructed_deint = [0u8; 96];
frame.to_deinterleaved(&mut reconstructed_deint);
assert_eq!(sample_deint, reconstructed_deint);
let interleaved = frame.to_interleaved();
let from_inter = SubFrame::from_interleaved(&interleaved);
assert_eq!(frame, from_inter);
}
#[test]
fn test_subchannel_q_parsing_and_crc() {
let raw_q = generate_q_subcode(0x41, 2, 1, 300, 45300);
let q = SubChannelQ::parse(&raw_q);
assert_eq!(q.control, 0x04);
assert_eq!(q.adr, 0x01);
assert_eq!(q.track_num, 2);
assert_eq!(q.index, 1);
assert_eq!(q.rel_msf, Msf::from_lba(300));
assert_eq!(q.abs_msf, Msf::from_lba(45300));
assert!(q.verify_crc());
}
#[test]
fn test_subchannel_q_corrupted_crc() {
let mut raw_q = generate_q_subcode(0x01, 1, 1, 150, 300);
raw_q[3] ^= 0x55;
let q = SubChannelQ::parse(&raw_q);
assert!(!q.verify_crc());
}
#[test]
fn test_subchannel_q_mcn_decode() {
let mut q_bytes = [0u8; 12];
q_bytes[0] = 0x02; q_bytes[1] = 0x12; q_bytes[2] = 0x34; q_bytes[3] = 0x56; q_bytes[4] = 0x78; q_bytes[5] = 0x90; q_bytes[6] = 0x12; q_bytes[7] = 0x30; q_bytes[8] = 0x00;
q_bytes[9] = 0x00;
let crc = compute_subcode_crc(&q_bytes[0..10]);
q_bytes[10] = (crc >> 8) as u8;
q_bytes[11] = crc as u8;
let q = SubChannelQ::parse(&q_bytes);
assert!(q.verify_crc());
assert_eq!(q.extract_mcn(), Some("1234567890123".to_string()));
}
#[test]
fn test_sub_scanner_tolerance_and_collection() {
let mut stream_bytes = Vec::new();
let mut mcn_frame = SubFrame::new();
let mut q_mcn = [0u8; 12];
q_mcn[0] = 0x02;
q_mcn[1] = 0x40;
q_mcn[2] = 0x12;
q_mcn[3] = 0x34;
q_mcn[4] = 0x56;
q_mcn[5] = 0x78;
q_mcn[6] = 0x90;
q_mcn[7] = 0x10;
let crc = compute_subcode_crc(&q_mcn[0..10]);
q_mcn[10] = (crc >> 8) as u8;
q_mcn[11] = crc as u8;
*mcn_frame.q_channel_mut() = q_mcn;
let mut buf96 = [0u8; 96];
mcn_frame.to_deinterleaved(&mut buf96);
stream_bytes.extend_from_slice(&buf96);
for lba in 1..=5 {
let mut f = SubFrame::new();
*f.q_channel_mut() = generate_q_subcode(0x41, 1, 1, lba, lba + 150);
f.to_deinterleaved(&mut buf96);
stream_bytes.extend_from_slice(&buf96);
}
for lba in 6..=7 {
let mut f = SubFrame::new();
let mut bad_q = generate_q_subcode(0x41, 1, 1, lba, lba + 150);
bad_q[4] ^= 0xAA; *f.q_channel_mut() = bad_q;
f.to_deinterleaved(&mut buf96);
stream_bytes.extend_from_slice(&buf96);
}
for lba in 8..=10 {
let mut f = SubFrame::new();
*f.q_channel_mut() = generate_q_subcode(0x41, 1, 2, lba, lba + 150);
f.to_deinterleaved(&mut buf96);
stream_bytes.extend_from_slice(&buf96);
}
let summary = SubScanner::scan(&stream_bytes[..]).expect("scan failed");
assert_eq!(summary.total_sectors, 11);
assert_eq!(summary.crc_errors, 2);
assert_eq!(summary.catalog, Some("4012345678901".to_string()));
assert_eq!(summary.sub_indices.len(), 1);
assert_eq!(summary.sub_indices[0].track_num, 1);
assert_eq!(summary.sub_indices[0].point, 2);
assert_eq!(summary.sub_indices[0].lba, 8);
}
#[test]
fn test_subchannel_q_isrc_decode() {
let mut q_bytes = [0u8; 12];
q_bytes[0] = 0x03; q_bytes[1] = 0x96; q_bytes[2] = 0x38; q_bytes[3] = 0x22; q_bytes[4] = 0x0C; q_bytes[5] = 0x98; q_bytes[6] = 0x01; q_bytes[7] = 0x23; q_bytes[8] = 0x40; q_bytes[9] = 0x00;
let crc = compute_subcode_crc(&q_bytes[0..10]);
q_bytes[10] = (crc >> 8) as u8;
q_bytes[11] = crc as u8;
let q = SubChannelQ::parse(&q_bytes);
assert!(q.verify_crc());
assert_eq!(q.extract_isrc(), Some("USPR39801234".to_string()));
}
#[test]
fn test_sub_scanner_isrc_and_partial_read() {
let mut stream_bytes = Vec::new();
let mut buf96 = [0u8; 96];
let mut f0 = SubFrame::new();
*f0.q_channel_mut() = generate_q_subcode(0x01, 2, 1, 0, 45150);
f0.to_deinterleaved(&mut buf96);
stream_bytes.extend_from_slice(&buf96);
let mut f1 = SubFrame::new();
let mut q_isrc = [0u8; 12];
q_isrc[0] = 0x03;
q_isrc[1] = 0x96;
q_isrc[2] = 0x38;
q_isrc[3] = 0x22;
q_isrc[4] = 0x0C;
q_isrc[5] = 0x98;
q_isrc[6] = 0x01;
q_isrc[7] = 0x23;
q_isrc[8] = 0x40;
let crc = compute_subcode_crc(&q_isrc[0..10]);
q_isrc[10] = (crc >> 8) as u8;
q_isrc[11] = crc as u8;
*f1.q_channel_mut() = q_isrc;
f1.to_deinterleaved(&mut buf96);
stream_bytes.extend_from_slice(&buf96);
stream_bytes.extend_from_slice(&[0xFF; 50]);
let summary = SubScanner::scan(&stream_bytes[..]).expect("scan with trailing bytes failed");
assert_eq!(summary.total_sectors, 2);
assert_eq!(summary.crc_errors, 0);
assert_eq!(
summary.track_isrcs.get(&2),
Some(&"USPR39801234".to_string())
);
}
}