Support loops from cmd line
This commit is contained in:
parent
dbaee2379a
commit
05a3be7836
|
@ -1,13 +1,102 @@
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use std::io::Write;
|
use std::{io::Write, str::FromStr};
|
||||||
use tool_helper::{Error, Input};
|
use tool_helper::{Error, Input};
|
||||||
use types::{LPC, MonoADPCMIterator, VAGADPCM, VAGHeader};
|
use types::{LPC, MonoADPCMIterator, VAGADPCM, VAGHeader};
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub struct Arguments {
|
pub struct Arguments {
|
||||||
name: Option<String>
|
#[clap(long, help="Specify the file name to be embedded in the header", default_value = "File name without extensions up to 16 characters")]
|
||||||
|
name: Option<String>,
|
||||||
|
#[clap(short='l', help="Set a loop at the specified time", value_name = "<min>:<sec>.<msec>[-<min>:<sec>.<msec>]", value_parser = clap::value_parser!(Loop))]
|
||||||
|
r#loop: Option<Loop>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Loop {
|
||||||
|
start_time_sec: f64,
|
||||||
|
end_time_sec: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Loop {
|
||||||
|
const END_DELIMITER: char = '-';
|
||||||
|
const MIN_DELIMITER: char = ':';
|
||||||
|
|
||||||
|
fn parse(str: Option<&str>) -> Result<Option<f64>, String> {
|
||||||
|
fn print_sec_conversion_error(str: &str, error: &dyn core::fmt::Display) -> String {
|
||||||
|
format!("Converting specified seconds \"{}\" failed with: {}", str, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(str) = str {
|
||||||
|
let time = {
|
||||||
|
if let Some(min_delim_idx) = str.find(Self::MIN_DELIMITER) {
|
||||||
|
let (min, sec) = str.split_at(min_delim_idx);
|
||||||
|
let min = min.parse::<u64>().map_err(|e| format!("Converting specified minutes \"{}\" failed with: {}", min, e))?;
|
||||||
|
let sec = sec.trim_start_matches(Self::MIN_DELIMITER).parse::<f64>().map_err(|e| print_sec_conversion_error(sec, &e))?;
|
||||||
|
|
||||||
|
(min*60) as f64 + sec
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
str.parse::<f64>().map_err(|e| print_sec_conversion_error(str, &e))?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Some(time))
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Loop {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(arg: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut args = arg.split(Self::END_DELIMITER);
|
||||||
|
let start_time_sec = Self::parse(args.next()).map_err(|e| format!("{}", e))?.ok_or_else(|| format!("A start time is required for a loop"))?;
|
||||||
|
let end_time_sec = Self::parse(args.next()).map_err(|e| format!("{}", e))?;
|
||||||
|
|
||||||
|
Ok(Loop{start_time_sec, end_time_sec})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LoopInfo {
|
||||||
|
start_sample: usize,
|
||||||
|
end_sample: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoopInfo {
|
||||||
|
fn create(r#loop: &Loop, sample_frequency: u32, vagadpcm_count: usize) -> LoopInfo {
|
||||||
|
fn calculate_sample(time_sec: f64, vag_frequency: f64) -> usize {
|
||||||
|
(time_sec*vag_frequency) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
let sample_frequency = sample_frequency as f64 / VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM as f64;
|
||||||
|
let start_sample = calculate_sample(r#loop.start_time_sec, sample_frequency);
|
||||||
|
let end_sample = {
|
||||||
|
if let Some(end_time_sec) = r#loop.end_time_sec {
|
||||||
|
let end_sample = calculate_sample(end_time_sec, sample_frequency);
|
||||||
|
if end_sample > vagadpcm_count {vagadpcm_count} else {end_sample}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
vagadpcm_count
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
LoopInfo{start_sample, end_sample}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_loop_start_reached(&self, sample: usize) -> bool {
|
||||||
|
self.start_sample == sample
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_loop_end_reached(&self, sample: usize) -> bool {
|
||||||
|
self.end_sample == sample
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert(args: Arguments, output_file_path: &Option<std::path::PathBuf>, input: Input, output: &mut dyn Write) -> Result<(), Error> {
|
pub fn convert(args: Arguments, output_file_path: &Option<std::path::PathBuf>, input: Input, output: &mut dyn Write) -> Result<(), Error> {
|
||||||
|
@ -15,17 +104,32 @@ pub fn convert(args: Arguments, output_file_path: &Option<std::path::PathBuf>, i
|
||||||
let wav_header = wav_file.spec();
|
let wav_header = wav_file.spec();
|
||||||
|
|
||||||
validate(&wav_header)?;
|
validate(&wav_header)?;
|
||||||
|
|
||||||
let vagadpcm_samples = VAGHeader::expected_vagadpcm_samples(wav_file.len()) + 1;
|
let vagadpcm_samples = VAGHeader::expected_vagadpcm_samples(wav_file.len()) + 1;
|
||||||
|
let sample_info = args.r#loop.map(|v| LoopInfo::create(&v, wav_header.sample_rate, vagadpcm_samples as usize));
|
||||||
let mut lpc = LPC::empty();
|
let mut lpc = LPC::empty();
|
||||||
|
|
||||||
tool_helper::raw::write_raw(output, &VAGHeader::create(vagadpcm_samples, wav_header.sample_rate, &get_name_for_file_header(args.name, output_file_path))?)?;
|
tool_helper::raw::write_raw(output, &VAGHeader::create(vagadpcm_samples, wav_header.sample_rate, &get_name_for_file_header(args.name, output_file_path))?)?;
|
||||||
for adpcm_sample in MonoADPCMIterator::create(wav_file.samples::<i16>()) {
|
for (sample_id, adpcm_sample) in MonoADPCMIterator::create(wav_file.samples::<i16>()).enumerate() {
|
||||||
let (vagadpcm, new_lpc) = VAGADPCM::create(adpcm_sample?, lpc);
|
let (mut vagadpcm, new_lpc) = VAGADPCM::create(adpcm_sample?, lpc);
|
||||||
|
|
||||||
|
if let Some(sample_info) = &sample_info {
|
||||||
|
if sample_info.is_loop_start_reached(sample_id) {
|
||||||
|
vagadpcm = vagadpcm.set_loop_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if sample_info.is_loop_end_reached(sample_id + 1) {
|
||||||
|
vagadpcm = vagadpcm.set_loop_end_repeat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tool_helper::raw::write_raw(output, &vagadpcm)?;
|
tool_helper::raw::write_raw(output, &vagadpcm)?;
|
||||||
lpc = new_lpc;
|
lpc = new_lpc;
|
||||||
}
|
}
|
||||||
tool_helper::raw::write_raw(output, &VAGADPCM::end())?;
|
|
||||||
|
if sample_info.is_none() {
|
||||||
|
tool_helper::raw::write_raw(output, &VAGADPCM::end())?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,8 +72,8 @@ pub struct VAGADPCM {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VAGADPCM {
|
impl VAGADPCM {
|
||||||
const SIZE: usize = std::mem::size_of::<VAGADPCM>();
|
const SIZE: usize = std::mem::size_of::<VAGADPCM>();
|
||||||
const ADPCM_SAMPLES_PER_VAGADPCM:usize = 28;
|
pub(super) const ADPCM_SAMPLES_PER_VAGADPCM:usize = 28;
|
||||||
|
|
||||||
pub fn empty() -> VAGADPCM {
|
pub fn empty() -> VAGADPCM {
|
||||||
VAGADPCM{data: [0; 4]}
|
VAGADPCM{data: [0; 4]}
|
||||||
|
|
Loading…
Reference in New Issue