chdlady-disc 0.2.1

Disc geometries and physical media formats for chdlady
//! CD-ROM subcode (P-W) generation, CRC computation, and interleaving.
use crate::types::Msf;

/// Swaps byte pairs in 16-bit PCM audio samples.
///
/// In CHD v5 files, audio samples are stored in big-endian byte order.
/// In standard CUE/BIN files and WAV audio tracks, samples are stored in little-endian byte order.
pub fn swap_audio_endianness(data: &mut [u8]) {
    let (chunks, _) = data.as_chunks_mut::<2>();
    for chunk in chunks {
        chunk.swap(0, 1);
    }
}

/// Computes the standard inverted CCITT CRC-16 checksum used in CD subcode Q channel.
pub fn compute_subcode_crc(data: &[u8]) -> u16 {
    let mut crc: u16 = 0;
    for &b in data {
        let mut byte = b;
        for _ in 0..8 {
            let bit = ((byte >> 7) & 1) as u16;
            let crc_bit = (crc >> 15) & 1;
            crc <<= 1;
            if bit ^ crc_bit != 0 {
                crc ^= 0x1021;
            }
            byte <<= 1;
        }
    }
    !crc
}

/// Converts an 8-bit integer into Binary-Coded Decimal (BCD).
#[inline]
pub fn to_bcd(val: u8) -> u8 {
    ((val / 10) << 4) | (val % 10)
}

/// Generates a 12-byte Q-subcode frame packet.
///
/// - `ctl_adr`: 4 bits control (e.g. 0x01 for audio, 0x41 for data) and 4 bits address mode (0x01 for position).
/// - `track_num`: 1-indexed track number (1..=99).
/// - `index`: index number (0..=99).
/// - `rel_lba`: frame offset relative to start of track index 01.
/// - `abs_lba`: absolute frame offset from start of disc (including 2-second pregap lead-in if applicable).
pub fn generate_q_subcode(
    ctl_adr: u8,
    track_num: u8,
    index: u8,
    rel_lba: u32,
    abs_lba: u32,
) -> [u8; 12] {
    let rel_msf = Msf::from_lba(rel_lba);
    let abs_msf = Msf::from_lba(abs_lba);

    let mut q = [0u8; 12];
    q[0] = ctl_adr;
    q[1] = to_bcd(track_num);
    q[2] = to_bcd(index);
    q[3] = to_bcd(rel_msf.m);
    q[4] = to_bcd(rel_msf.s);
    q[5] = to_bcd(rel_msf.f);
    q[6] = 0x00;
    q[7] = to_bcd(abs_msf.m);
    q[8] = to_bcd(abs_msf.s);
    q[9] = to_bcd(abs_msf.f);

    let crc = compute_subcode_crc(&q[0..10]);
    q[10] = (crc >> 8) as u8;
    q[11] = crc as u8;

    q
}

/// Deinterleaves a 96-byte raw subcode frame into 8 channels of 12 bytes each (P, Q, R, S, T, U, V, W).
pub fn deinterleave_subcode(frame_subcode: &[u8; 96], channels: &mut [[u8; 12]; 8]) {
    for ch in channels.iter_mut() {
        ch.fill(0);
    }

    for (i, &byte) in frame_subcode.iter().enumerate() {
        let byte_idx = i / 8;
        let bit_shift = 7 - (i % 8);
        for (c, ch) in channels.iter_mut().enumerate() {
            let bit = (byte >> (7 - c)) & 1;
            ch[byte_idx] |= bit << bit_shift;
        }
    }
}

/// Interleaves 8 channels of 12 bytes each into a 96-byte raw subcode frame.
pub fn interleave_subcode(channels: &[[u8; 12]; 8], frame_subcode: &mut [u8; 96]) {
    frame_subcode.fill(0);

    for (i, byte) in frame_subcode.iter_mut().enumerate() {
        let byte_idx = i / 8;
        let bit_shift = 7 - (i % 8);
        let mut out_byte = 0u8;
        for (c, ch) in channels.iter().enumerate() {
            let bit = (ch[byte_idx] >> bit_shift) & 1;
            out_byte |= bit << (7 - c);
        }
        *byte = out_byte;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_swap_audio_endianness() {
        let mut data = vec![0x12, 0x34, 0x56, 0x78];
        swap_audio_endianness(&mut data);
        assert_eq!(data, vec![0x34, 0x12, 0x78, 0x56]);
    }

    #[test]
    fn test_subcode_interleave_roundtrip() {
        let mut original = [0u8; 96];
        for (i, b) in original.iter_mut().enumerate() {
            *b = (i as u8).wrapping_mul(31);
        }

        let mut channels = [[0u8; 12]; 8];
        deinterleave_subcode(&original, &mut channels);

        let mut reconstructed = [0u8; 96];
        interleave_subcode(&channels, &mut reconstructed);

        assert_eq!(original, reconstructed);
    }

    #[test]
    fn test_q_subcode_crc() {
        let q = generate_q_subcode(0x01, 1, 1, 0, 150);
        let crc = compute_subcode_crc(&q[0..10]);
        assert_eq!(q[10], (crc >> 8) as u8);
        assert_eq!(q[11], crc as u8);
    }
}