use super::bank::Plan;
use crate::error::{Error, Result};
pub fn ffmpeg_resampled_len(in_len: usize, from: u32, to: u32) -> Result<usize> {
match plan_for(from, to)? {
Some(plan) => output_len(in_len, &plan),
None => Ok(in_len),
}
}
pub(super) fn plan_for(from: u32, to: u32) -> Result<Option<Plan>> {
if from == 0 || to == 0 {
return Err(Error::InvalidArgument {
arg: if from == 0 { "from" } else { "to" },
reason: format!("sample rate is 0 Hz in {from} Hz to {to} Hz; pass both rates in Hz"),
});
}
if from == to {
return Ok(None);
}
Plan::new(from, to).map(Some)
}
pub(super) fn output_len(n: usize, plan: &Plan) -> Result<usize> {
let n = n as u128;
let h = (plan.taps / 2) as u128;
let taps = plan.taps as u128;
let (up, down) = (u128::from(plan.up), u128::from(plan.down));
let (streamed, span) = if n > taps {
let streamed = ((n - h) * up).div_ceil(down);
let index = streamed * down / up;
(streamed, n + h - 1 - index)
} else {
(0, n)
};
let pad = span.div_ceil(2);
if n + pad <= taps {
return Ok(0);
}
let end = n + pad - h;
let total = streamed.max((end * up).div_ceil(down));
usize::try_from(total).map_err(|_| Error::InvalidArgument {
arg: "samples",
reason: format!(
"{n} samples converted by {up}/{down} give {total} samples, more than a usize \
holds; split the input"
),
})
}
#[cfg(test)]
mod tests {
use super::*;
fn len(n: usize, from: u32, to: u32) -> Result<usize> {
ffmpeg_resampled_len(n, from, to)
}
#[test]
fn upsampling_rounds_up() -> Result<()> {
assert_eq!(len(1000, 24_000, 48_000)?, 2000);
assert_eq!(len(1000, 22_050, 48_000)?, 2177);
assert_eq!(len(1013, 44_100, 48_000)?, 1103);
assert_eq!(len(4097, 16_000, 48_000)?, 12_291);
Ok(())
}
#[test]
fn downsampling_can_end_one_short() -> Result<()> {
assert_eq!(len(1000, 48_000, 16_000)?, 333);
assert_eq!(len(1001, 48_000, 16_000)?, 334);
assert_eq!(len(5002, 48_000, 44_100)?, 4595);
assert_eq!(len(5003, 48_000, 44_100)?, 4597);
Ok(())
}
#[test]
fn short_inputs_flush_only_or_vanish() -> Result<()> {
assert_eq!(len(21, 24_000, 48_000)?, 0);
assert_eq!(len(22, 24_000, 48_000)?, 34);
assert_eq!(len(32, 24_000, 48_000)?, 64);
assert_eq!(len(33, 24_000, 48_000)?, 66);
assert_eq!(len(66, 48_000, 16_000)?, 0);
assert_eq!(len(67, 48_000, 16_000)?, 17);
assert_eq!(len(0, 44_100, 48_000)?, 0);
Ok(())
}
#[test]
fn equal_rates_pass_through() -> Result<()> {
assert_eq!(len(7, 48_000, 48_000)?, 7);
Ok(())
}
#[test]
fn a_zero_rate_is_refused() {
assert!(matches!(
len(10, 0, 48_000),
Err(Error::InvalidArgument { arg: "from", .. })
));
assert!(matches!(
len(10, 48_000, 0),
Err(Error::InvalidArgument { arg: "to", .. })
));
}
}