Integrate all the progress into master #6
|
@ -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::<i16>()) {
|
||||
|
||||
let mut lpc = LPC::empty();
|
||||
for adpcm_sample in MonoADPCMIterator::create(wav_file.samples::<i16>()) {
|
||||
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> {
|
||||
|
|
|
@ -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::<VAGHeader>();
|
||||
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::<VAGADPCM>();
|
||||
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<I: std::iter::Iterator<Item=Result<i16, hound::Error>>> {
|
||||
iter: I
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue