diff --git a/src/Tools/psxfileconv/src/audio/my_vag/mod.rs b/src/Tools/psxfileconv/src/audio/my_vag/mod.rs index c735bc14..d1f39101 100644 --- a/src/Tools/psxfileconv/src/audio/my_vag/mod.rs +++ b/src/Tools/psxfileconv/src/audio/my_vag/mod.rs @@ -2,20 +2,31 @@ pub mod types; use std::io::Write; use tool_helper::{Error, Input}; -use types::MonoADPCMIterator; +use types::{LPC, MonoADPCMIterator, VAGADPCM, VAGHeader}; -pub fn convert(input: Input, _output: &mut dyn Write) -> Result<(), Error> { +pub fn convert(input: Input, output: &mut dyn Write) -> Result<(), Error> { let mut wav_file = hound::WavReader::new(input)?; let wav_header = wav_file.spec(); validate(&wav_header)?; + let mut samples = Vec::new(); let mut sample_count = 0; - for _adpcm_samples in MonoADPCMIterator::create(wav_file.samples::()) { + + let mut lpc = LPC::empty(); + for adpcm_sample in MonoADPCMIterator::create(wav_file.samples::()) { + let (vagadpcm, new_lpc) = VAGADPCM::create(adpcm_sample?, lpc); + samples.push(vagadpcm); + lpc = new_lpc; + sample_count += 1; } - println!("Parsed {} vag samples", sample_count); - Err(Error::not_implemented("my vag convert")) + // TODO: Restructure + tool_helper::raw::write_raw(output, &VAGHeader::create(sample_count, wav_header.sample_rate, "Planschi"))?; + for sample in samples { + tool_helper::raw::write_raw(output, &sample)?; + } + Ok(()) } fn validate(wav_header: &hound::WavSpec) -> Result<(), Error> { diff --git a/src/Tools/psxfileconv/src/audio/my_vag/types.rs b/src/Tools/psxfileconv/src/audio/my_vag/types.rs index a61d5660..d47be849 100644 --- a/src/Tools/psxfileconv/src/audio/my_vag/types.rs +++ b/src/Tools/psxfileconv/src/audio/my_vag/types.rs @@ -1,17 +1,148 @@ -use tool_helper::Error; +use tool_helper::{raw::RawConversion, Error}; + +#[repr(packed)] +// TODO: Move logic of fixed values into the raw function, including the BE/LE stuff? +pub struct VAGHeader { + _id: [u8; 4], + _version: u32, + _reserved: u32, + _data_size: u32, + _sampling_frequency: u32, + _reserved2: [u8; 12], + _name: [u8; 16] +} + +impl VAGHeader { + const SIZE: usize = std::mem::size_of::(); + const ID: [u8; 4] = ['V' as u8, 'A' as u8, 'G' as u8, 'p' as u8]; + const VERSION: u32 = 0x02; + const RESERVED: u32 = 0; + const RESERVED2: [u8; 12] = [0; 12]; + + // TODO: Ensure big endianes + pub fn create(vagpcm_samples: usize, sampling_frequency: u32, _name: &str) -> VAGHeader { + // TODO: Support naming feature + let data_size = (vagpcm_samples*VAGADPCM::SIZE) as u32; + let name = ['A' as u8; 16]; + + println!("Got {} samples; Writing {} bytes", vagpcm_samples, data_size); + + VAGHeader{ + _id: VAGHeader::ID, + _version: VAGHeader::VERSION.to_be(), + _reserved: VAGHeader::RESERVED, + _data_size: data_size.to_be(), + _sampling_frequency: sampling_frequency.to_be(), + _reserved2: VAGHeader::RESERVED2, + _name: name + } + } +} + +impl RawConversion<{VAGHeader::SIZE}> for VAGHeader { + fn convert_to_raw(&self) -> [u8; VAGHeader::SIZE] { + unsafe { + let data: [u8; VAGHeader::SIZE] = std::mem::transmute_copy(&self as &VAGHeader); + data + } + } +} pub struct VAGADPCM { data: [u32; 4] } impl VAGADPCM { + const SIZE: usize = std::mem::size_of::(); const ADPCM_SAMPLES_PER_VAGADPCM:usize = 28; - pub fn create() -> VAGADPCM { - VAGADPCM{data: [0; 4]} + pub fn empty() -> VAGADPCM { + VAGADPCM{data: [0; 4]} + } + + pub fn create_for_filter_shift(filter: u32, shift: u32) -> VAGADPCM { + VAGADPCM{data: [(12 - shift) | filter << 4, 0, 0, 0]} + } + + pub fn create(samples: ADPCMSampleForVag, lpc_tap: LPC) -> (VAGADPCM, LPC) { + fn cap_value(value: i32, min: i32, max: i32) -> i32 { + if value < min { + min + } + + else if value > max { + max + } + + else { + value + } + } + let mut best_frame = VAGADPCM::empty(); + let mut best_tap = LPC::empty(); + let mut best_error = std::u64::MAX as u64; + + for (filter_id, filter) in LPC::FILTERS.iter().enumerate() { + for shift in 0..=12 { + let mut this_frame = VAGADPCM::create_for_filter_shift(filter_id as u32, shift); + let this_tap = lpc_tap.clone(); + let mut this_error = 0; + + for (sample_id, sample) in samples.iter().enumerate() { + let x = *sample as i32; + let p = (this_tap.first*filter.first + this_tap.second*filter.second + 32) >> 6; + let r = x - p; + let q = cap_value((r + (((1 << shift) - (r < 0) as i32) >> 1)) >> shift, -8, 7); + let y = cap_value(p + (q << shift), std::i16::MIN as i32, std::i16::MAX as i32); + let e = y - x; + + this_frame.data[(sample_id+4)/8] |= ((q&0xF) << (((sample_id + 4)%8)*4)) as u32; + this_error += (e*e) as u64; + } + + if this_error < best_error { + best_tap = this_tap; + best_error = this_error; + best_frame = this_frame; + } + } + } + + (best_frame, best_tap) } } +impl RawConversion<{VAGADPCM::SIZE}> for VAGADPCM { + fn convert_to_raw(&self) -> [u8; VAGADPCM::SIZE] { + unsafe { + let data: [u8; VAGADPCM::SIZE] = std::mem::transmute_copy(&self as &VAGADPCM); + data + } + } +} + +#[derive(Clone)] +pub struct LPC { + first: i32, + second: i32 +} + +impl LPC { + const FILTERS: [LPC; 5] = [ + LPC{first: 0, second: 0}, + LPC{first: 60, second: 0}, + LPC{first: 115, second: -52}, + LPC{first: 98, second: -55}, + LPC{first: 122, second: -60} + ]; + + pub fn empty() -> LPC { + LPC{first: 0, second: 0} + } +} + +pub type ADPCMSampleForVag = [i16; VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM]; + pub struct MonoADPCMIterator>> { iter: I }