Support loops from cmd line

This commit is contained in:
jaby 2024-11-03 20:57:57 +00:00
parent dbaee2379a
commit 05a3be7836
2 changed files with 112 additions and 8 deletions

View File

@ -1,13 +1,102 @@
pub mod types;
use clap::Args;
use std::io::Write;
use std::{io::Write, str::FromStr};
use tool_helper::{Error, Input};
use types::{LPC, MonoADPCMIterator, VAGADPCM, VAGHeader};
#[derive(Args)]
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> {
@ -15,17 +104,32 @@ pub fn convert(args: Arguments, output_file_path: &Option<std::path::PathBuf>, i
let wav_header = wav_file.spec();
validate(&wav_header)?;
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();
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>()) {
let (vagadpcm, new_lpc) = VAGADPCM::create(adpcm_sample?, lpc);
for (sample_id, adpcm_sample) in MonoADPCMIterator::create(wav_file.samples::<i16>()).enumerate() {
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)?;
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(())
}

View File

@ -72,8 +72,8 @@ pub struct VAGADPCM {
}
impl VAGADPCM {
const SIZE: usize = std::mem::size_of::<VAGADPCM>();
const ADPCM_SAMPLES_PER_VAGADPCM:usize = 28;
const SIZE: usize = std::mem::size_of::<VAGADPCM>();
pub(super) const ADPCM_SAMPLES_PER_VAGADPCM:usize = 28;
pub fn empty() -> VAGADPCM {
VAGADPCM{data: [0; 4]}