ptcow 0.1.0

Library for editing and playback of PxTone (.ptcop) music
Documentation
use {
    super::IoOggv,
    crate::{Bps, ChNum, PcmData, ProjectReadError, ReadResult, VoiceData},
    bytemuck::Contiguous,
};

pub fn read(
    rd: &mut crate::io::Reader,
    io_oggv: &IoOggv,
    size: usize,
    unit: &mut crate::voice::VoiceUnit,
) -> ReadResult {
    let mut dec = vorbis_rs::VorbisDecoder::<&[u8]>::new(&rd.data[rd.cur..rd.cur + size])
        .map_err(|_| ProjectReadError::OggvReadError)?;
    let mut pcm = PcmData::new();
    pcm.sps = dec.sampling_frequency().into_integer();
    pcm.ch = match dec.channels().into_integer() {
        1 => ChNum::Mono,
        2 => ChNum::Stereo,
        _ => panic!("Vorbis channel number >2 not supported."),
    };
    pcm.bps = Bps::B16;
    let mut i16_samples: Vec<i16> = Vec::new();
    while let Some(block) = dec.decode_audio_block().map_err(|_| ProjectReadError::OggvReadError)? {
        let interleaved = planar_to_interleaved(block.samples());
        for sample in interleaved {
            #[expect(clippy::cast_possible_truncation)]
            i16_samples.push((sample * 32768.0).round_ties_even() as i16);
        }
    }
    pcm.smp = bytemuck::pod_collect_to_vec(&i16_samples);
    #[expect(clippy::cast_possible_truncation)]
    (pcm.num_samples = pcm.smp.len() as u32 / 2);
    rd.cur += size;
    unit.data = VoiceData::Pcm(pcm);
    unit.flags = io_oggv.voice_flags;
    unit.basic_key = i32::from(io_oggv.basic_key);
    unit.tuning = io_oggv.tuning;
    Ok(())
}

fn planar_to_interleaved(planar: &[&[f32]]) -> Vec<f32> {
    let channels = planar.len();
    let frames = planar[0].len();

    let mut out = Vec::with_capacity(channels * frames);

    for i in 0..frames {
        for ch in planar {
            out.push(ch[i]);
        }
    }

    out
}