From 05a3be7836096c9528ee55106c024f072151dfe7 Mon Sep 17 00:00:00 2001 From: jaby Date: Sun, 3 Nov 2024 20:57:57 +0000 Subject: [PATCH] Support loops from cmd line --- src/Tools/psxfileconv/src/audio/my_vag/mod.rs | 116 +++++++++++++++++- .../psxfileconv/src/audio/my_vag/types.rs | 4 +- 2 files changed, 112 insertions(+), 8 deletions(-) diff --git a/src/Tools/psxfileconv/src/audio/my_vag/mod.rs b/src/Tools/psxfileconv/src/audio/my_vag/mod.rs index c86063ed..af632358 100644 --- a/src/Tools/psxfileconv/src/audio/my_vag/mod.rs +++ b/src/Tools/psxfileconv/src/audio/my_vag/mod.rs @@ -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 + #[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, + #[clap(short='l', help="Set a loop at the specified time", value_name = ":.[-:.]", value_parser = clap::value_parser!(Loop))] + r#loop: Option +} + +#[derive(Clone)] +pub struct Loop { + start_time_sec: f64, + end_time_sec: Option, +} + +impl Loop { + const END_DELIMITER: char = '-'; + const MIN_DELIMITER: char = ':'; + + fn parse(str: Option<&str>) -> Result, 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::().map_err(|e| format!("Converting specified minutes \"{}\" failed with: {}", min, e))?; + let sec = sec.trim_start_matches(Self::MIN_DELIMITER).parse::().map_err(|e| print_sec_conversion_error(sec, &e))?; + + (min*60) as f64 + sec + } + + else { + str.parse::().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 { + 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, input: Input, output: &mut dyn Write) -> Result<(), Error> { @@ -15,17 +104,32 @@ pub fn convert(args: Arguments, output_file_path: &Option, 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::()) { - let (vagadpcm, new_lpc) = VAGADPCM::create(adpcm_sample?, lpc); + for (sample_id, adpcm_sample) in MonoADPCMIterator::create(wav_file.samples::()).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(()) } diff --git a/src/Tools/psxfileconv/src/audio/my_vag/types.rs b/src/Tools/psxfileconv/src/audio/my_vag/types.rs index ae086962..2cd84511 100644 --- a/src/Tools/psxfileconv/src/audio/my_vag/types.rs +++ b/src/Tools/psxfileconv/src/audio/my_vag/types.rs @@ -72,8 +72,8 @@ pub struct VAGADPCM { } impl VAGADPCM { - const SIZE: usize = std::mem::size_of::(); - const ADPCM_SAMPLES_PER_VAGADPCM:usize = 28; + const SIZE: usize = std::mem::size_of::(); + pub(super) const ADPCM_SAMPLES_PER_VAGADPCM:usize = 28; pub fn empty() -> VAGADPCM { VAGADPCM{data: [0; 4]}