use std::sync::Arc;
use timestretch::PreAnalysisArtifact;
use timestretch::engine::{Engine, EngineConfig, EngineProfile};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Arm {
Tape,
Keylock,
}
pub fn render_keylock_with_artifact(
artifact: Arc<PreAnalysisArtifact>,
input: &[f32],
channels: usize,
sample_rate: u32,
callback_frames: usize,
rate_at: &dyn Fn(f64) -> f64,
) -> AbRender {
render_inner(
EngineProfile::Keylock,
Some(artifact),
input,
channels,
sample_rate,
callback_frames,
rate_at,
)
}
#[derive(Debug)]
pub struct AbRender {
pub output: Vec<f32>,
pub underrun_frames: u64,
}
pub fn render_with_rate_schedule(
arm: Arm,
input: &[f32],
channels: usize,
sample_rate: u32,
callback_frames: usize,
rate_at: &dyn Fn(f64) -> f64,
) -> AbRender {
match arm {
Arm::Tape => render(
EngineProfile::Tape,
input,
channels,
sample_rate,
callback_frames,
rate_at,
),
Arm::Keylock => render(
EngineProfile::Keylock,
input,
channels,
sample_rate,
callback_frames,
rate_at,
),
}
}
fn render(
profile: EngineProfile,
input: &[f32],
channels: usize,
sample_rate: u32,
callback_frames: usize,
rate_at: &dyn Fn(f64) -> f64,
) -> AbRender {
render_inner(
profile,
None,
input,
channels,
sample_rate,
callback_frames,
rate_at,
)
}
fn render_inner(
profile: EngineProfile,
artifact: Option<Arc<PreAnalysisArtifact>>,
input: &[f32],
channels: usize,
sample_rate: u32,
callback_frames: usize,
rate_at: &dyn Fn(f64) -> f64,
) -> AbRender {
let handles = Engine::build(EngineConfig {
sample_rate,
channels,
profile,
initial_tempo_rate: rate_at(0.0),
max_block_frames: callback_frames.clamp(64, 8192),
source_capacity_frames: (callback_frames * 16).max(32_768),
pre_analysis: artifact,
})
.expect("engine builds");
let (controller, mut processor, mut source) =
(handles.controller, handles.processor, handles.source);
source.set_track_position(0);
let mut feed_cursor = 0usize;
let mut finished = false;
let mut out = vec![0.0f32; callback_frames * channels];
let mut output: Vec<f32> = Vec::with_capacity(input.len() * 4 + 65_536);
let mut cb = 0usize;
loop {
let t = (cb * callback_frames) as f64 / sample_rate as f64;
controller.set_tempo_rate(rate_at(t));
while feed_cursor < input.len()
&& source.occupied_frames() < source.demand_hint(callback_frames, 4.0)
{
let end = (feed_cursor + 8192 * channels).min(input.len());
let accepted = source.push(&input[feed_cursor..end]);
feed_cursor += accepted * channels;
if accepted == 0 {
break;
}
}
if feed_cursor >= input.len() && !finished {
finished = source.finish();
}
let underruns_before = controller.underrun_frames();
processor.process(&mut out);
if controller.underrun_frames() > underruns_before {
let shortfall = controller.underrun_frames() - underruns_before;
let missing = shortfall as usize * channels;
output.extend_from_slice(&out[..out.len() - missing.min(out.len())]);
return AbRender {
output,
underrun_frames: underruns_before,
};
}
output.extend_from_slice(&out);
cb += 1;
if cb > 4 * input.len() / (callback_frames.max(1) * channels) + 1024 {
panic!("A/B render did not terminate");
}
}
}