use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::time::Instant;
use audioadapter::stats::AdapterStats;
use audioadapter::{Adapter, AdapterMut};
use clap::{Parser, ValueEnum};
use rubato::{Fft, FixedSync, Resampler, WindowFunction};
use waveadapter::{SampleFormat, WavReader, WavSpec, WavWriter};
#[derive(Parser)]
#[command(version)]
struct Options {
input: String,
output: String,
output_rate: usize,
#[arg(short, long, value_enum, ignore_case = true)]
format: Option<Format>,
#[arg(short, long, default_value_t = 0.0, allow_negative_numbers = true)]
gain: f64,
#[arg(short, long, default_value_t = 1024)]
chunk: usize,
#[arg(short, long, value_enum, ignore_case = true, default_value_t = Window::BlackmanHarris2)]
window: Window,
}
#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
enum Format {
#[value(name = "U8")]
U8,
#[value(name = "I16")]
I16,
#[value(name = "I24_3")]
I24_3,
#[value(name = "I24_4")]
I24_4,
#[value(name = "I32")]
I32,
#[value(name = "F32")]
F32,
#[value(name = "F64")]
F64,
}
impl From<Format> for SampleFormat {
fn from(format: Format) -> Self {
match format {
Format::U8 => SampleFormat::U8,
Format::I16 => SampleFormat::I16,
Format::I24_3 => SampleFormat::I24_3,
Format::I24_4 => SampleFormat::I24_4,
Format::I32 => SampleFormat::I32,
Format::F32 => SampleFormat::F32,
Format::F64 => SampleFormat::F64,
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
enum Window {
#[value(name = "Blackman")]
Blackman,
#[value(name = "Blackman2")]
Blackman2,
#[value(name = "BlackmanHarris")]
BlackmanHarris,
#[value(name = "BlackmanHarris2")]
BlackmanHarris2,
#[value(name = "Hann")]
Hann,
#[value(name = "Hann2")]
Hann2,
}
impl From<Window> for WindowFunction {
fn from(window: Window) -> Self {
match window {
Window::Blackman => WindowFunction::Blackman,
Window::Blackman2 => WindowFunction::Blackman2,
Window::BlackmanHarris => WindowFunction::BlackmanHarris,
Window::BlackmanHarris2 => WindowFunction::BlackmanHarris2,
Window::Hann => WindowFunction::Hann,
Window::Hann2 => WindowFunction::Hann2,
}
}
}
fn run(opts: Options) -> Result<(), Box<dyn std::error::Error>> {
let mut reader = WavReader::new(BufReader::new(File::open(&opts.input)?))?;
let channels = reader.channels();
let rate_in = reader.sample_rate();
let format_in = reader.sample_format().ok_or_else(|| {
format!(
"the input file cannot be decoded, format code 0x{:04X} with {} bits per sample",
reader.params().format_code,
reader.params().bits_per_sample
)
})?;
println!(
"Input: {}, {} ch, {} Hz, {:?}, {} frames",
opts.input,
channels,
rate_in,
format_in,
reader.frames()
);
let input = reader.read_all_to_float::<f64>()?;
let format_out = opts.format.map(SampleFormat::from).unwrap_or(format_in);
let window = WindowFunction::from(opts.window);
let mut resampler = Fft::<f64>::new_custom(
rate_in,
opts.output_rate,
opts.chunk,
1,
channels,
window,
FixedSync::Both,
)?;
println!(
"Config: chunks of {} -> {} frames, {:?} window, cutoff {:.0} Hz",
resampler.fft_size_in(),
resampler.fft_size_out(),
window,
resampler.cutoff() as f64 * rate_in as f64 / 2.0
);
let start = Instant::now();
let mut output = resampler.process_all(&input, input.frames(), None)?;
println!(
"Resampled {} frames to {} frames in {:?}",
input.frames(),
output.frames(),
start.elapsed()
);
if opts.gain != 0.0 {
let scale = 10.0f64.powf(opts.gain / 20.0);
for chan in 0..output.channels() {
for frame in 0..output.frames() {
let value = output.read_sample(chan, frame).unwrap() * scale;
output.write_sample(chan, frame, &value);
}
}
}
let peak =
(0..output.channels()).fold(0.0f64, |peak, chan| peak.max(output.channel_peak(chan)));
println!(
"Peak level after gain: {:.2} dBFS",
20.0 * peak.max(1e-12).log10()
);
let spec = WavSpec::new(channels, opts.output_rate, format_out);
let mut writer = WavWriter::new(BufWriter::new(File::create(&opts.output)?), spec)?;
let clipped = writer.write_float_buffer(&output)?;
writer.finalize()?;
println!(
"Output: {}, {} ch, {} Hz, {:?}, {} frames, {} clipped samples",
opts.output,
channels,
opts.output_rate,
format_out,
output.frames(),
clipped
);
Ok(())
}
fn main() {
if let Err(err) = run(Options::parse()) {
eprintln!("Error: {err}");
std::process::exit(1);
}
}