Merge pull request #301 from marcoarnulfo/fix/seek-flushes-state

Locate clears plugin and delay-compensation state
This commit is contained in:
Brandon Thomas authored and GitHub committed 2026-10-13 04:29:11 -04:00
commit 75e019dde3
2 files changed
+71

No files matched your search

+27
View File
@@ -251,6 +251,9 @@ impl DelayLine {
}
self.idx = (start + frames) % self.len.max(1);
}
fn clear(&mut self) {
self.bufs.iter_mut().for_each(|b| b.fill(0.0));
}
}
/// Clip Effects: an EQ and a compressor per clip, configured from `edit.values`
@@ -481,6 +484,9 @@ impl MixEngine {
i.plugin.reset();
}
s.held.clear();
s.align.clear();
s.align_pre.clear();
s.out_extra.clear();
}
self.meters.clear();
self.main_meter = StripMeter::default();
@@ -2363,6 +2369,27 @@ mod tests {
assert_ne!(argmax(&out[0]), argmax(&out[1]));
}
/// A reset (stop, locate) must not leave the previous position's audio in the alignment delay.
#[test]
fn reset_clears_delay_compensation_history() {
let (mut s, t) = session_with_dc(0.5, 4800);
s.edit.click = false;
s.track_mut(t).unwrap().mixer.pan = vec![1.0];
let latent = s.add_track(TrackKind::Audio, ChannelFormat::Mono, None);
let mut comp = Insert::new("compressor");
comp.params.insert("lookahead".into(), 5.0);
s.track_mut(latent).unwrap().mixer.inserts[0] = Some(comp);
let mut eng = MixEngine::new(48_000.0, 512);
let mut out = vec![vec![0.0f32; 512]; 2];
for b in 0..9 {
eng.render(&s, b * 512, 512, &mut out);
}
assert!(eng.latency > 0 && out[1][500] > 0.3, "the fixture plays through the alignment delay");
eng.reset();
eng.render(&s, 1_000_000, 512, &mut out);
assert!(out.iter().flatten().all(|x| *x == 0.0), "old aligned audio after a reset");
}
fn impulse_at(ch: &[f32]) -> Vec<usize> {
ch.iter().enumerate().filter(|(_, v)| v.abs() > 1e-4).map(|(i, _)| i).collect()
}
+44
View File
@@ -176,6 +176,7 @@ impl AudioState {
self.pos = at;
self.pending_len = 0;
self.read = 0.0;
self.mix.reset();
}
}
}
@@ -953,6 +954,49 @@ mod tests {
assert!(shared.playing.load(Ordering::Relaxed));
}
#[test]
fn locate_while_playing_drops_the_old_position_audio() {
use soundcraft_model::{ChannelFormat, Clip, Insert, SourceAudio, SourceId, TrackKind};
for (plugin, params) in [("compressor", &[("lookahead", 5.0), ("ratio", 1.0)][..]), ("room_reverb", &[][..])] {
let mut s = soundcraft_model::Session::default();
s.edit.click = false;
let t = s.add_track(TrackKind::Audio, ChannelFormat::Mono, None);
s.pool.insert(
SourceId(5),
Arc::new(SourceAudio::new(soundcraft_audio_io::AudioBuffer { sample_rate: 48_000, channels: vec![vec![0.5; 4800]] })),
);
let id = s.new_clip_id();
let tr = s.track_mut(t).unwrap();
tr.playlist_mut().unwrap().clips.push(Clip::audio(id, "x", SourceId(5), 0, 0, 4800));
let mut ins = Insert::new(plugin);
for (k, v) in params {
ins.params.insert((*k).into(), *v);
}
tr.mixer.inserts[0] = Some(ins);
let shared = Arc::new(Shared {
position: AtomicI64::new(0),
playing: AtomicBool::new(false),
speed: AtomicU32::new(1000),
meters: Mutex::new(MeterSnapshot::default()),
});
let (tx, rx) = channel();
let mut st = AudioState::new(rx, Arc::clone(&shared), Arc::new(s), 48_000.0, channel().0);
tx.send(Cmd::Play { from: 0, end: None, looped: None }).unwrap();
let mut out = vec![0.0f32; 2 * 512];
let mut before = 0.0f32;
for _ in 0..9 {
st.fill(&mut out, 2);
before = out.iter().fold(before, |m, x| m.max(x.abs()));
}
assert!(before > 0.1, "{plugin}: the clip plays before the locate");
tx.send(Cmd::Locate(1_000_000)).unwrap();
for _ in 0..8 {
st.fill(&mut out, 2);
assert!(out.iter().all(|x| x.abs() < 1e-6), "{plugin}: audio from before the locate");
}
}
}
#[test]
fn armed_tracks_are_heard_and_metered_after_playback_stops() {
use soundcraft_model::{ChannelFormat, TrackKind};