mirror of
https://github.com/storytold/soundcraft.git
synced 2026-11-12 00:44:57 -06:00
Compensate a hosted instrument's latency (#1476)
* Compensate a hosted instrument's latency Delay compensation summed only insert latency, so a synth reporting latency played late against everything else in playback and bounces. run_inserts now seeds the strip latency with the instrument's. Fixes #1474. Added a mix test that fails without the change: a latent hosted instrument now contributes its latency to the mix. Signed-off-by: Vladimir Babin <vovababin@gmail.com> * test(mix): verify rendered instrument latency alignment --------- Signed-off-by: Vladimir Babin <vovababin@gmail.com> Co-authored-by: FredApps <FredApps@users.noreply.github.com>
This commit is contained in:
co-authored by
FredApps
parent
ad34dbf87e
commit
e86d09ab2a
@@ -334,7 +334,7 @@ fn render_instrument(s: &Session, t: &Track, strip: &mut Strip, blk: Block, tran
|
||||
fn run_inserts(t: &Track, strip: &mut Strip, blk: Block, transport: &Transport) {
|
||||
let Block { pos, frames, .. } = blk;
|
||||
let mut gr = 0.0f32;
|
||||
let mut latency = 0usize;
|
||||
let mut latency = strip.instrument.as_ref().filter(|i| !i.failed).map_or(0, |i| i.plugin.latency());
|
||||
for (i, ins) in t.mixer.inserts.iter().enumerate() {
|
||||
let (Some(ins), Some(Some(slot))) = (ins, strip.plugins.get_mut(i)) else { continue };
|
||||
if !ins.active || ins.bypass || slot.failed {
|
||||
|
||||
@@ -941,6 +941,84 @@ fn a_plugins_bogus_latency_is_capped() {
|
||||
assert_eq!((eng.track_latency, eng.latency), (MAX_LATENCY, 2 * MAX_LATENCY));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_hosted_instruments_latency_is_compensated() {
|
||||
// #1474: a synth that reports latency plays late. Its delay must count toward the mix
|
||||
// latency and align, the way an insert's does.
|
||||
struct LatentSynth {
|
||||
inner: Box<dyn Plugin>,
|
||||
n: usize,
|
||||
t: usize,
|
||||
pending: Option<usize>,
|
||||
}
|
||||
impl Plugin for LatentSynth {
|
||||
fn info(&self) -> &'static soundcraft_dsp::PluginInfo {
|
||||
self.inner.info()
|
||||
}
|
||||
fn prepare(&mut self, sr: f32, mb: usize, ch: usize) {
|
||||
self.inner.prepare(sr, mb, ch);
|
||||
}
|
||||
fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
fn set_param(&mut self, id: &str, v: f32) -> bool {
|
||||
self.inner.set_param(id, v)
|
||||
}
|
||||
fn param(&self, id: &str) -> Option<f32> {
|
||||
self.inner.param(id)
|
||||
}
|
||||
fn note_on(&mut self, offset: usize, _: u8, _: u8) {
|
||||
self.pending = Some(self.t + offset + self.n);
|
||||
}
|
||||
fn process(&mut self, io: &mut [Vec<f32>], frames: usize) {
|
||||
for c in io.iter_mut() {
|
||||
for (i, x) in c.iter_mut().take(frames).enumerate() {
|
||||
*x = if Some(self.t + i) == self.pending { 0.25 } else { 0.0 };
|
||||
}
|
||||
}
|
||||
self.t += frames;
|
||||
}
|
||||
fn latency(&self) -> usize {
|
||||
self.n
|
||||
}
|
||||
}
|
||||
use soundcraft_midi::{Note, Sequence};
|
||||
let n = 500;
|
||||
let mut s = Session::default();
|
||||
s.edit.delay_compensation = true;
|
||||
let audio = s.add_track(TrackKind::Audio, ChannelFormat::Stereo, None);
|
||||
let mut click = vec![0.0f32; 48_000];
|
||||
click[24_000] = 0.25;
|
||||
s.pool.insert(SourceId(1474), Arc::new(SourceAudio::new(AudioBuffer { sample_rate: 48_000, channels: vec![click.clone(), click] })));
|
||||
let cid = s.new_clip_id();
|
||||
s.track_mut(audio).unwrap().playlist_mut().unwrap().clips.push(Clip::audio(cid, "click", SourceId(1474), 0, 0, 48_000));
|
||||
let inst = s.add_track(TrackKind::Instrument, ChannelFormat::Stereo, None);
|
||||
let mut seq = Sequence::default();
|
||||
seq.notes.push(Note { pitch: 60, velocity: 100, release_velocity: 64, channel: 0, start: 960, length: 240 });
|
||||
let mcid = s.new_clip_id();
|
||||
s.track_mut(inst).unwrap().playlist_mut().unwrap().clips.push(Clip::midi(mcid, "note", 0, 48_000, seq));
|
||||
s.track_mut(inst).unwrap().instrument = Some(Insert::new("clap:test.latent"));
|
||||
let mut eng = MixEngine::new(48_000.0, 1024);
|
||||
eng.set_external_instances(true);
|
||||
eng.adopt(vec![PreparedInstance {
|
||||
track: inst,
|
||||
slot: INSTRUMENT_SLOT,
|
||||
id: "clap:test.latent".into(),
|
||||
ch: 2,
|
||||
plugin: Box::new(LatentSynth { inner: soundcraft_dsp::create("gain").unwrap(), n, t: 0, pending: None }),
|
||||
}]);
|
||||
let mut out = vec![vec![0.0; 1024]; 2];
|
||||
let mut rendered = Vec::new();
|
||||
for b in 0..48 {
|
||||
eng.render(&s, b * 1024, 1024, &mut out);
|
||||
rendered.extend_from_slice(&out[0]);
|
||||
}
|
||||
assert_eq!(eng.latency, n, "the hosted instrument's latency is part of the mix latency");
|
||||
let impulses: Vec<_> = rendered.iter().enumerate().filter(|(_, v)| v.abs() > 1e-6).map(|(i, _)| i).collect();
|
||||
assert_eq!(impulses, vec![24_000 + n], "both the audio click and instrument impulse align");
|
||||
assert!((rendered[24_000 + n] - 0.5).abs() < 1e-6, "the aligned impulses add, neither is missing");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_monitoring_replaces_clips() {
|
||||
let (mut s, t) = session_with_dc(0.5, 4800);
|
||||
|
||||
Reference in New Issue
Block a user