use super::sample::PcmSample;
use super::wsola::{Stop, Wsola};
use crate::error::{Error, Result};
pub const MIN_TEMPO: f64 = 0.5;
pub const MAX_TEMPO: f64 = 2.0;
const MIN_RATE: u32 = 48;
const MAX_RATE: u32 = 768_000;
pub fn atempo(samples: &[f32], rate: u32, tempo: f64) -> Result<Vec<f32>> {
atempo_interleaved(samples, 1, rate, tempo)
}
pub fn atempo_pcm16(samples: &[i16], rate: u32, tempo: f64) -> Result<Vec<i16>> {
atempo_interleaved(samples, 1, rate, tempo)
}
pub fn atempo_interleaved<S: PcmSample>(
samples: &[S],
channels: u16,
rate: u32,
tempo: f64,
) -> Result<Vec<S>> {
validate(samples.len(), channels, rate, tempo)?;
let ch = usize::from(channels);
let frames = samples.len() / ch;
let mut engine = Wsola::new(samples, ch, rate, tempo);
let packet = packet_frames(rate);
let mut buffer: Option<OutputBuffer> = None;
let mut offered = 0usize;
while offered < frames {
let chunk = packet.min(frames - offered);
offered += chunk;
engine.offer(offered);
let capacity = (0.5 + chunk as f64 / tempo) as usize;
while engine.hungry() {
let buf = buffer.get_or_insert(OutputBuffer::new(capacity));
let before = engine.written();
let stop = engine.run(&mut buf.left)?;
buf.filled += (engine.written() - before) as usize;
if buf.left == 0 {
buffer = None;
}
if stop == Stop::NeedInput {
break;
}
}
}
let drain_capacity = 3 * engine.segment_len();
let pass_limit = 4 * (frames / engine.segment_len()) + 64;
for _ in 0..pass_limit {
let buf = buffer.get_or_insert(OutputBuffer::new(drain_capacity));
let before = engine.written();
let done = engine.drain(&mut buf.left)?;
buf.filled += (engine.written() - before) as usize;
if buf.filled > 0 {
buffer = None;
}
if done {
return Ok(engine.into_output());
}
}
Err(Error::DataError {
reason: format!(
"atempo end-of-stream pass did not finish after {pass_limit} passes \
({frames} frames, {channels} channels, {rate} Hz, tempo {tempo}); \
report this input as a bug"
),
})
}
fn packet_frames(rate: u32) -> usize {
let ceiling = ((rate / 10) as usize).max(1);
let mut frames = 1usize;
while frames * 2 <= ceiling {
frames *= 2;
}
frames
}
fn validate(len: usize, channels: u16, rate: u32, tempo: f64) -> Result<()> {
if !(MIN_TEMPO..=MAX_TEMPO).contains(&tempo) {
return Err(Error::InvalidArgument {
arg: "tempo",
reason: format!(
"tempo {tempo} is outside [{MIN_TEMPO}, {MAX_TEMPO}]; \
for a larger change, apply atempo more than once"
),
});
}
if !(MIN_RATE..=MAX_RATE).contains(&rate) {
return Err(Error::InvalidArgument {
arg: "rate",
reason: format!("sample rate {rate} Hz is outside [{MIN_RATE}, {MAX_RATE}] Hz"),
});
}
if channels == 0 {
return Err(Error::InvalidArgument {
arg: "channels",
reason: "channel count is 0; pass at least 1".into(),
});
}
if !len.is_multiple_of(usize::from(channels)) {
return Err(Error::InvalidArgument {
arg: "samples",
reason: format!(
"{len} samples do not split into whole frames of {channels} channels; \
trim the partial frame"
),
});
}
Ok(())
}
struct OutputBuffer {
left: usize,
filled: usize,
}
impl OutputBuffer {
fn new(capacity: usize) -> Self {
Self {
left: capacity,
filled: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::stretch::wsola::segment_frames;
#[test]
fn packet_is_largest_power_of_two_under_a_tenth_second() {
assert_eq!(packet_frames(48_000), 4096);
assert_eq!(packet_frames(44_100), 4096);
assert_eq!(packet_frames(24_000), 2048);
assert_eq!(packet_frames(16_000), 1024);
assert_eq!(packet_frames(11_025), 1024);
assert_eq!(packet_frames(96_000), 8192);
}
#[test]
fn rejects_out_of_range_arguments() {
let x = vec![0.0f32; 4800];
for bad in [0.49, 2.01, f64::NAN, f64::INFINITY] {
assert!(atempo(&x, 48_000, bad).is_err(), "tempo {bad}");
}
assert!(atempo(&x, 0, 1.0).is_err());
assert!(atempo_interleaved(&x[..3], 2, 48_000, 1.0).is_err());
assert!(atempo_interleaved(&x, 0, 48_000, 1.0).is_err());
}
#[test]
fn empty_and_short_inputs_yield_nothing() -> Result<()> {
assert!(atempo(&[], 48_000, 1.5)?.is_empty());
assert!(atempo(&vec![0.1f32; 1000], 48_000, 1.5)?.is_empty());
Ok(())
}
#[test]
fn length_scales_with_tempo() -> Result<()> {
let rate = 48_000u32;
let x: Vec<f32> = (0..rate as usize)
.map(|n| (n as f32 * 0.0577).sin() * 0.5)
.collect();
for tempo in [0.5, 0.8, 1.0, 1.25, 1.5, 2.0] {
let got = atempo(&x, rate, tempo)?.len() as f64;
let want = x.len() as f64 / tempo;
assert!(
(got - want).abs() < 2.0 * segment_frames(rate) as f64,
"tempo {tempo}: {got} frames, about {want} expected"
);
}
Ok(())
}
#[test]
fn stereo_keeps_channels_apart() -> Result<()> {
let x: Vec<i16> = (0..24_000).flat_map(|n| [(n % 200) as i16, 0]).collect();
let y = atempo_interleaved(&x, 2, 48_000, 1.25)?;
assert_eq!(y.len() % 2, 0);
assert!(y.chunks(2).all(|f| f[1] == 0));
Ok(())
}
}