mirror of
https://github.com/storytold/soundcraft.git
synced 2026-10-24 02:26:46 -06:00
Merge pull request #667 from reoniazx/fix/master-fader-automation-boundaries
Fix master fader automation leaking across render-block boundaries
This commit is contained in:
+3
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
**Stage: pre-alpha** · next: alpha, ~5% and ~110–180 h away (the alpha gate's three failing workflows)
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (MCP batch limit validation) · **Target:** Avid Pro Tools Ultimate 2026.4.1
|
||||
> **Last reviewed:** 2026-10-16 · **Last updated:** 2026-10-16 · **Change:** trivial (master fader automation block boundaries) · **Target:** Avid Pro Tools Ultimate 2026.4.1
|
||||
|
||||
SoundCraft is a clean-room, pure-Rust digital audio workstation that aims at full parity with
|
||||
Avid Pro Tools, and then beyond it: faster, open, scriptable and agent-controllable. This page is
|
||||
@@ -122,6 +122,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-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:** `mix.instrument` takes the built-in instrument's parameters in `params` (validated, clamped) and refuses any other field, so a synth setting written beside `plugin` is an error instead of being accepted and ignored (#265)
|
||||
- **2026-10-13:** A bounce of a bus that an aux returns onto lines up with the timeline: the aux insert's lookahead is compensated like the main mix's (it left the bus late and cut the end off), and tracks routed to that bus wait for it too (#246)
|
||||
@@ -156,6 +157,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-14 | trivial | Progress log: oversized MCP batches rejected before execution (#460) |
|
||||
| 2026-10-13 | trivial | Progress log: mix.instrument params, unknown fields refused (#265) |
|
||||
| 2026-10-13 | trivial | Progress log: bus bounce compensates aux returns (#246) |
|
||||
|
||||
+50
-2
@@ -768,12 +768,14 @@ impl MixEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Match track faders: the next block's first sample must not ramp this one.
|
||||
let last = pos.saturating_add(frames.saturating_sub(1) as i64);
|
||||
let v0 = volume_db_at(t, pos);
|
||||
let v1 = volume_db_at(t, pos + frames as i64);
|
||||
let v1 = volume_db_at(t, last);
|
||||
for (ch, src) in strip.buf.iter().enumerate().take(self.main.len()) {
|
||||
if let Some(m) = self.main.get_mut(ch) {
|
||||
for i in 0..frames {
|
||||
let g = lerp_gain(v0, v1, i, frames);
|
||||
let g = lerp_gain(v0, v1, i, frames.saturating_sub(1));
|
||||
if let (Some(d), Some(x)) = (m.get_mut(i), src.get(i)) {
|
||||
*d = x * g;
|
||||
}
|
||||
@@ -2482,6 +2484,52 @@ mod tests {
|
||||
assert!(out[0][30_000].abs() < 1e-4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn master_volume_breakpoint_does_not_ramp_the_previous_block() {
|
||||
for block in [1, 2, 37, 512, 1024] {
|
||||
let start = 37;
|
||||
let boundary = start + block as i64;
|
||||
let end = boundary + block as i64;
|
||||
let (mut s, t) = session_with_dc(0.5, end as usize);
|
||||
s.track_mut(t).unwrap().mixer.pan = vec![-1.0];
|
||||
let master = s.add_track(TrackKind::Master, ChannelFormat::Stereo, None);
|
||||
let lane = s.track_mut(master).unwrap().lane_mut(&AutoParam::Volume);
|
||||
lane.set_point(start, 0.0);
|
||||
lane.set_point(boundary - 1, 0.0);
|
||||
lane.set_point(boundary, -60.0);
|
||||
lane.set_point(end - 1, -60.0);
|
||||
|
||||
let mut mixer = MixEngine::new(48_000.0, block);
|
||||
let mut out = vec![vec![0.0; block]; 2];
|
||||
for (pos, expected) in [(start, 0.5), (boundary, 0.0005)] {
|
||||
mixer.render(&s, pos, block, &mut out);
|
||||
for (i, &sample) in out[0].iter().enumerate() {
|
||||
assert!((sample - expected).abs() < 1e-6, "block {block}, position {pos}, sample {i}: {sample} vs {expected}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn master_volume_ramp_reaches_the_last_sample_of_the_block() {
|
||||
for block in [2, 37, 512, 1024] {
|
||||
let start = 37;
|
||||
let end = start + block as i64;
|
||||
let (mut s, t) = session_with_dc(0.5, end as usize);
|
||||
s.track_mut(t).unwrap().mixer.pan = vec![-1.0];
|
||||
let master = s.add_track(TrackKind::Master, ChannelFormat::Stereo, None);
|
||||
let lane = s.track_mut(master).unwrap().lane_mut(&AutoParam::Volume);
|
||||
lane.set_point(start, 0.0);
|
||||
lane.set_point(end - 1, -12.0);
|
||||
|
||||
let out = render_range(&s, Range::new(start, end), block);
|
||||
assert_eq!(out[0].len(), block);
|
||||
assert!((out[0][0] - 0.5).abs() < 1e-6);
|
||||
let expected = 0.5 * db_to_gain(-12.0);
|
||||
assert!((out[0][block - 1] - expected).abs() < 1e-6, "block {block}: {} vs {expected}", out[0][block - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inserts_process_and_survive_unknown_plugins() {
|
||||
let (mut s, t) = session_with_dc(0.5, 4800);
|
||||
|
||||
+7
-1
@@ -1,6 +1,6 @@
|
||||
# Where SoundCraft falls short of Pro Tools
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (G-01: audio device settings and opt-in ASIO) · **Target:** Avid Pro Tools Ultimate 2026.4.1
|
||||
> **Last reviewed:** 2026-10-16 · **Last updated:** 2026-10-16 · **Change:** trivial (G-04: master fader automation block boundaries) · **Target:** Avid Pro Tools Ultimate 2026.4.1
|
||||
|
||||
Every known shortfall, one entry each, ranked by how much it stops a Pro Tools user from doing
|
||||
real work in SoundCraft. This is the work list: pick from the top. Rows marked **alpha blocker**
|
||||
@@ -80,6 +80,11 @@ 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
|
||||
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.
|
||||
|
||||
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);
|
||||
@@ -199,6 +204,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-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