Support loops from cmd line
This commit is contained in:
parent
dbaee2379a
commit
05a3be7836
|
@ -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> {
|
||||
|
@ -17,15 +106,30 @@ pub fn convert(args: Arguments, output_file_path: &Option<std::path::PathBuf>, i
|
|||
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(())
|
||||
}
|
||||
|
||||
|
|
|
@ -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]}
|
||||
|
|
Loading…
Reference in New Issue