use std::io;
use std::path::Path;
use clap::Parser;
use creak::{Decoder, DecoderError};
use ndarray::prelude::*;
use ndarray_npy::WriteNpyExt;
use rayon::prelude::*;
use pyin::{Framing, PYINExecutor, PadMode};
#[derive(Parser)]
#[clap(author, version, about)]
struct Cli {
input: String,
output: String,
fmin: f64,
fmax: f64,
#[clap(short, long, default_value_t = 80f64)]
frame_ms: f64,
#[clap(long)]
win_ms: Option<f64>,
#[clap(long)]
hop_ms: Option<f64>,
#[clap(long)]
resolution: Option<f64>,
#[clap(short, long)]
verbose: bool,
}
fn decode_audio_file<P: AsRef<Path>>(path: P) -> Result<(Array2<f32>, u32), DecoderError> {
let decoder = Decoder::open(path)?;
let info = decoder.info();
let sr = info.sample_rate();
let channels = info.channels();
let mut vec: Vec<f32> = Vec::with_capacity(channels);
for sample in decoder.into_samples()? {
vec.push(sample?);
}
if vec.len() < channels {
(vec.len()..channels).into_iter().for_each(|_| vec.push(0.));
}
let shape = (channels, vec.len() / channels);
vec.truncate(shape.0 * shape.1); let wav = Array2::from_shape_vec(shape.strides((1, shape.0)), vec).unwrap();
Ok((wav, sr))
}
fn main() {
let cli = Cli::parse();
let (wav, sr) = if &cli.input == "-" {
unimplemented!()
} else {
decode_audio_file(&cli.input)
.expect(&format!("Failed to decode input audio \"{}\"!", &cli.input))
};
let output_writer = io::BufWriter::new(if &cli.output == "-" {
Box::new(std::io::stdout()) as Box<dyn io::Write>
} else {
Box::new(
std::fs::File::create(&cli.output)
.unwrap_or_else(|_| panic!("Could not create output file \"{}\"!", &cli.output)),
) as Box<dyn io::Write>
});
let wav = wav.mapv(|x| x as f64);
let ms_to_samples = |ms: f64| (sr as f64 * ms / 1000.).round() as usize;
let frame_length = ms_to_samples(cli.frame_ms);
let win_length = cli.win_ms.map(ms_to_samples);
let hop_length = cli.hop_ms.map(ms_to_samples);
let mut pyin_exec = PYINExecutor::new(
cli.fmin,
cli.fmax,
sr,
frame_length,
win_length,
hop_length,
cli.resolution,
);
let results: Vec<_> = if wav.shape()[0] > 1 {
wav.axis_iter(Axis(0))
.par_bridge()
.map(|mono| {
pyin_exec.clone().pyin(
mono.into(),
f64::NAN,
Framing::Center(PadMode::Constant(0.)),
)
})
.collect()
} else {
vec![pyin_exec.pyin(
wav.remove_axis(Axis(0)).into(),
f64::NAN,
Framing::Center(PadMode::Constant(0.)),
)]
};
let pyin_result =
Array3::from_shape_fn(
(3, results.len(), results[0].0.len()),
|(i, j, k)| match i {
0 => results[j].0[k],
1 => results[j].1[k] as usize as f64,
2 => results[j].2[k],
_ => unreachable!(),
},
);
if cli.verbose && &cli.output != "-" {
println!("f0 = {}", pyin_result.index_axis(Axis(0), 0));
println!("voiced_flag = {}", pyin_result.index_axis(Axis(0), 1));
println!("voiced_prob = {}", pyin_result.index_axis(Axis(0), 2));
}
pyin_result
.write_npy(output_writer)
.expect("Failed to write pyin result to file!");
}