Basic conversion working
This commit is contained in:
parent
e229115c19
commit
ec8c1bb316
|
@ -2,20 +2,31 @@ pub mod types;
|
||||||
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use tool_helper::{Error, Input};
|
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 mut wav_file = hound::WavReader::new(input)?;
|
||||||
let wav_header = wav_file.spec();
|
let wav_header = wav_file.spec();
|
||||||
|
|
||||||
validate(&wav_header)?;
|
validate(&wav_header)?;
|
||||||
|
let mut samples = Vec::new();
|
||||||
let mut sample_count = 0;
|
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;
|
sample_count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Parsed {} vag samples", sample_count);
|
// TODO: Restructure
|
||||||
Err(Error::not_implemented("my vag convert"))
|
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> {
|
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 {
|
pub struct VAGADPCM {
|
||||||
data: [u32; 4]
|
data: [u32; 4]
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VAGADPCM {
|
impl VAGADPCM {
|
||||||
|
const SIZE: usize = std::mem::size_of::<VAGADPCM>();
|
||||||
const ADPCM_SAMPLES_PER_VAGADPCM:usize = 28;
|
const ADPCM_SAMPLES_PER_VAGADPCM:usize = 28;
|
||||||
|
|
||||||
pub fn create() -> VAGADPCM {
|
pub fn empty() -> VAGADPCM {
|
||||||
VAGADPCM{data: [0; 4]}
|
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>>> {
|
pub struct MonoADPCMIterator<I: std::iter::Iterator<Item=Result<i16, hound::Error>>> {
|
||||||
iter: I
|
iter: I
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue