mirror of
https://github.com/storytold/soundcraft.git
synced 2026-10-24 02:26:46 -06:00
Merge pull request #672 from reoniazx/fix/failed-insert-latency
Fix stale aux and master latency after insert failures
This commit is contained in:
@@ -123,6 +123,7 @@ mutated and truncated movies. Field reports from the first public week are track
|
||||
## Progress log
|
||||
|
||||
- **2026-10-16:** Master fader volume automation uses each render block's actual last sample, matching track faders. Breakpoints in the next block no longer pull the preceding block's level toward them, and ramps reach their last-sample value. Regression tests cover nonzero starts, one-sample callbacks and several block sizes. No readiness percentage change.
|
||||
- **2026-10-16:** Aux, folder and master latency calculations exclude inserts already taken out for producing NaN or infinity, matching audio tracks. Subsequent blocks no longer compensate for an insert that is bypassed after failure. Regression tests check impulse timing, healthy insert latency and reset/retry at 64- and 512-sample blocks. No readiness percentage change.
|
||||
- **2026-10-14:** MCP `batch` rejects more than 1000 calls before executing any command, instead of silently running only the first 1000 (#460). No readiness percentage change.
|
||||
- **2026-10-13:** WAV: an empty data chunk followed by metadata (LIST, bext) reads as no audio; a recording that was never finalised (sizes left at 0) still gives what was written (#466)
|
||||
- **2026-10-13:** Sampler: a ping-pong loop keeps its phase and direction when a step is wider than the loop (a narrow loop with high transposition), instead of reflecting once and dropping the rest of the travel (#463)
|
||||
@@ -160,6 +161,7 @@ mutated and truncated movies. Field reports from the first public week are track
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-16 | trivial | Master volume automation block boundaries corrected; readiness estimates unchanged |
|
||||
| 2026-10-16 | trivial | Failed inserts no longer contribute aux, folder or master latency after containment; readiness estimates unchanged |
|
||||
| 2026-10-14 | trivial | Progress log: oversized MCP batches rejected before execution (#460) |
|
||||
| 2026-10-13 | trivial | Progress log: empty WAV data chunk followed by metadata (#466) |
|
||||
| 2026-10-13 | trivial | Progress log: sampler ping-pong phase for wide steps (#463) |
|
||||
|
||||
+69
-1
@@ -1649,7 +1649,7 @@ fn note_insert_latency(t: &Track, strip: &mut Strip, pos: Samples) {
|
||||
let mut latency = 0usize;
|
||||
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 {
|
||||
if !ins.active || ins.bypass || slot.failed {
|
||||
continue;
|
||||
}
|
||||
apply_params(slot, ins, t, i, pos);
|
||||
@@ -2797,6 +2797,9 @@ mod tests {
|
||||
fn process(&mut self, io: &mut [Vec<f32>], frames: usize) {
|
||||
io.iter_mut().for_each(|c| c.iter_mut().take(frames).for_each(|x| *x = f32::NAN));
|
||||
}
|
||||
fn latency(&self) -> usize {
|
||||
self.0.latency()
|
||||
}
|
||||
}
|
||||
|
||||
fn nan(track: TrackId, slot: usize, ch: usize) -> Vec<PreparedInstance> {
|
||||
@@ -2843,6 +2846,71 @@ mod tests {
|
||||
assert_eq!(render_block(&mut eng, &s, 1024), only_b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_inserts_stop_contributing_latency_until_reset() {
|
||||
for kind in [TrackKind::Audio, TrackKind::Aux, TrackKind::Folder, TrackKind::Master] {
|
||||
for block in [64, 512] {
|
||||
let mut s = Session::default();
|
||||
s.edit.delay_compensation = true;
|
||||
let source = s.add_track(TrackKind::Audio, ChannelFormat::Stereo, None);
|
||||
let mut impulse = vec![0.0; block * 6];
|
||||
for at in [block, block * 2, block * 4] {
|
||||
impulse[at] = 0.25;
|
||||
}
|
||||
s.pool
|
||||
.insert(SourceId(900), Arc::new(SourceAudio::new(AudioBuffer { sample_rate: 48_000, channels: vec![impulse.clone(), impulse] })));
|
||||
let cid = s.new_clip_id();
|
||||
s.track_mut(source).unwrap().playlist_mut().unwrap().clips.push(Clip::audio(
|
||||
cid,
|
||||
"impulses",
|
||||
SourceId(900),
|
||||
0,
|
||||
0,
|
||||
(block * 6) as i64,
|
||||
));
|
||||
let target = if kind == TrackKind::Audio { source } else { s.add_track(kind, ChannelFormat::Stereo, None) };
|
||||
if matches!(kind, TrackKind::Aux | TrackKind::Folder) {
|
||||
let bus = s.add_bus("Return", ChannelFormat::Stereo);
|
||||
s.track_mut(source).unwrap().mixer.output = Route::Bus(bus);
|
||||
s.track_mut(target).unwrap().mixer.input = Route::Bus(bus);
|
||||
}
|
||||
let mut failed = Insert::new("clap:test.nan");
|
||||
failed.params.insert("lookahead".into(), 5.0);
|
||||
let mut healthy = Insert::new("compressor");
|
||||
healthy.params.insert("lookahead".into(), 1.0);
|
||||
healthy.params.insert("ratio".into(), 1.0);
|
||||
let tr = s.track_mut(target).unwrap();
|
||||
tr.mixer.inserts[0] = Some(failed);
|
||||
tr.mixer.inserts[1] = Some(healthy);
|
||||
let mut plugin = soundcraft_dsp::create("compressor").unwrap();
|
||||
plugin.prepare(48_000.0, block, 2);
|
||||
let mut eng = MixEngine::new(48_000.0, block);
|
||||
eng.set_external_instances(true);
|
||||
eng.adopt(vec![PreparedInstance { track: target, slot: 0, id: "clap:test.nan".into(), ch: 2, plugin: Box::new(Nan(plugin)) }]);
|
||||
let mut out = vec![vec![0.0; block]; 2];
|
||||
eng.render(&s, 0, block, &mut out);
|
||||
assert!(out.iter().flatten().all(|x| *x == 0.0));
|
||||
assert_eq!(eng.meters[&target].plugin_failures.count, 1);
|
||||
|
||||
for b in [1, 2, 4] {
|
||||
if b == 4 {
|
||||
// Reset retries the failed plugin once; the next block must drop its
|
||||
// latency again, while keeping the healthy insert's 48-sample delay.
|
||||
eng.reset();
|
||||
eng.render(&s, (block * 3) as i64, block, &mut out);
|
||||
assert_eq!(eng.meters[&target].plugin_failures.count, 2);
|
||||
}
|
||||
eng.render(&s, (block * b) as i64, block, &mut out);
|
||||
assert_eq!(eng.latency, 48, "{kind:?}, block {block}, pass {b}: only the healthy insert delays audio");
|
||||
for channel in &out {
|
||||
assert_eq!(impulse_at(channel), vec![48], "{kind:?}, block {block}, pass {b}: no compensation for the failed insert");
|
||||
assert!((channel[48] - 0.25).abs() < 1e-5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_instance_replaces_a_matching_one_in_its_slot() {
|
||||
// The host removed and re-added the plugin, and both session updates reached the audio
|
||||
|
||||
@@ -81,10 +81,15 @@ When you close a gap, delete its entry (or shrink it) in the same PR, and bump t
|
||||
### G-04. Correctness bugs in "done" features
|
||||
|
||||
**Fixed (2026-10-16):** Master fader volume automation uses the first and last samples of each
|
||||
**Fixed (2026-10-16):** Aux, folder and master latency calculations now skip inserts already
|
||||
render block, matching track faders. A breakpoint at the next block no longer ramps the preceding
|
||||
block, and ramps reach their last-sample value. Regression tests cover nonzero render starts,
|
||||
one-sample callbacks and several block sizes. Interior automation breakpoints remain block-dependent.
|
||||
|
||||
disabled for producing NaN or infinity. Subsequent blocks compensate only for healthy inserts;
|
||||
regression tests verify rendered impulse timing, retained healthy latency and reset/retry at
|
||||
64- and 512-sample blocks, with audio tracks as the control.
|
||||
|
||||
Open on 2026-10-10 (several have PRs open):
|
||||
editing #3, #5, #140, #144; clip/session #86 (PR #123), #88 (PR #130), #91 (PR #124), #92 (PR #132);
|
||||
automation #104 (PR #134); mix/bounce #59 (PR #131), #103 (PR #133), #152; playback #101 (PR #129);
|
||||
@@ -205,6 +210,7 @@ missing. Out of scope unless the owner decides otherwise.
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-16 | trivial | G-04: master volume automation block boundaries fixed and regression-tested; estimates unchanged |
|
||||
| 2026-10-16 | trivial | G-04: failed aux/folder/master inserts excluded from latency compensation after containment; estimates unchanged |
|
||||
| 2026-10-12 | minor | G-01 shrunk: audio device, rate and buffer choice; opt-in ASIO |
|
||||
| 2026-10-10 | minor | Marked the alpha blockers (G-01, G-02, G-03; parts of G-05 and G-06) from the core-workflow gate |
|
||||
| 2026-10-10 | major | Created from the full re-measure against Pro Tools Ultimate 2026.4.1: 22 ranked gaps with evidence and estimates |
|
||||
|
||||
Reference in New Issue
Block a user