extern crate anyhow;
use anyhow::{anyhow, Context, Result};
use std::env;
use std::fs::File;
use std::path::Path;
use tdpsola::{AlternatingHann, Speed, TdpsolaAnalysis, TdpsolaSynthesis};
use wav::BitDepth;
struct CliArguments {
input_filename: String,
output_filename: String,
speed: f32,
source_frequency: f32,
target_frequency: f32,
}
const USAGE: &'static str = "Usage:
tdpsola_demo INPUT_FILENAME OUTPUT_FILENAME SPEED SOURCE_FREQUENCY TARGET_FREQUENCY
The output file will be overwritten without warning.
Only mono .wav files are supported.
The output will always be 16 bits, regardless of the input.";
impl CliArguments {
fn parse() -> Result<Self> {
let args: Vec<String> = env::args().collect();
if args.len() < 4 {
Err(anyhow!("Missing command line argument."))
} else {
let input_filename = args[1].clone();
let output_filename = args[2].clone();
let speed = args[3].parse()?;
let source_frequency: f32 = args[4].parse()?;
let target_frequency = args[5].parse()?;
if source_frequency <= 0.0 {
return Err(anyhow!("Source frequency must be positive."));
}
let minimum_source_frequency = 1e-6;
if source_frequency.abs() < minimum_source_frequency {
return Err(anyhow!(
"Source frequencies lower than {} Hz are not supported.",
minimum_source_frequency
));
}
if target_frequency <= 0.0 {
return Err(anyhow!("Target frequency must be positive."));
}
let minimum_target_frequency = 1e-6;
if target_frequency < minimum_target_frequency {
return Err(anyhow!(
"Target frequencies lower than {} Hz are not supported.",
minimum_target_frequency
));
}
let minimum_speed = 1e-6;
if speed < minimum_speed {
return Err(anyhow!(
"Speeds slower than {} are not supported.",
minimum_speed
));
}
Ok(CliArguments {
input_filename,
output_filename,
speed,
source_frequency,
target_frequency,
})
}
}
}
fn extract_data(bit_depth: BitDepth) -> Vec<f32> {
match bit_depth {
BitDepth::Eight(v) => v.iter().map(|x| *x as f32).collect(),
BitDepth::Sixteen(v) => v.iter().map(|x| *x as f32).collect(),
BitDepth::TwentyFour(v) => v.iter().map(|x| *x as f32).collect(),
BitDepth::Empty => Vec::new(),
}
}
fn repackage_data(input: &[f32]) -> BitDepth {
if input.is_empty() {
return BitDepth::Empty;
}
BitDepth::Sixteen(input.iter().map(|x| (*x) as i16).collect())
}
fn main() -> Result<()> {
let arguments = CliArguments::parse().context(USAGE)?;
let mut input_file = File::open(Path::new(&arguments.input_filename)).context(format!(
"Failed to open input file '{}'.",
arguments.input_filename
))?;
let (input_header, input_data) = wav::read(&mut input_file).context(format!(
"Failed to read input file {}",
arguments.input_filename
))?;
if input_header.channel_count != 1 {
return Err(anyhow!(format!(
"Input file {} is not mono. Only mono files are supported.",
arguments.input_filename
)));
}
let mut out_file = File::create(Path::new(&arguments.output_filename)).context(format!(
"Failed to open output file '{}'.",
arguments.output_filename
))?;
let sampling_frequency = input_header.sampling_rate;
let source_wavelength = (sampling_frequency as f32) / arguments.source_frequency;
let target_wavelength = (sampling_frequency as f32) / arguments.target_frequency;
let input = extract_data(input_data);
let mut alternating_hann = AlternatingHann::new(source_wavelength);
let mut analysis = TdpsolaAnalysis::new(&alternating_hann);
let padding_length = source_wavelength as usize + 1;
for _ in 0..padding_length {
analysis.push_sample(0.0, &mut alternating_hann);
}
println!("Analysing...");
for sample in input.iter() {
analysis.push_sample(*sample, &mut alternating_hann);
}
let speed = Speed::from_f32(arguments.speed);
let mut synthesis = TdpsolaSynthesis::new(speed, target_wavelength);
let mut output = Vec::new();
println!("Synthesizing...");
for output_sample in synthesis.iter(&analysis).skip(padding_length) {
output.push(output_sample);
}
let output_data = repackage_data(&output);
wav::write(input_header, output_data, &mut out_file).context(format!(
"Unable to write to output file '{}'.",
arguments.output_filename
))?;
println!("Done.");
Ok(())
}