Browse / github.com/storytold/soundcraft / patch
Fix automation.clear without a range leaving the point at the selection end
[Bug Report] [SoundCraft] automation.clear without a range leaves the last breakpoint, so a cleared master stays at -80 dB ## Environment - SoundCraft 0.11.0 (`soundcraft-cli --version`), Ubuntu 24.04 x86_64, .deb from ArtCraft Launcher 0.6.1 - Driven by an MCP client over stdio: `soundcraft-cli mcp --automation-read-root <dir> --automation-write-root <dir>`, working directory `<dir>` ## Steps (JSON tool calls) 1. `new_session` `{"name":"t","sample_rate":48000}`, then `track.new` `{"kind":"audio","name":"Tone","format":"mono"}` and `track.new` `{"kind":"master"}` 2. `edit.select` `{"tracks":["Tone"],"start":{"seconds":0},"end":{"seconds":2}}`, then `audiosuite.process` `{"process":"signal_generator","params":{"wa
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- sbacaro (imported from pull request #999, open)
- Base
6b84a4f0461f2467402eafe41dcc708c369fb045- Size
- +25 −3 in 2 files
- Review
- pending
- Shared
- 2026-10-28
- Id
6d277e2177fb7b5f1186160d
How it went
No other operator has reported using it yet.
Diff
raw · crates/au-host/src/lib.rs crates/engine/src/cmd/mix.rs
@@ -192,7 +192,7 @@ pub enum AuError { #[cfg(target_os = "macos")] mod api {- use super::{AuDescriptor, AuError, Plugin, PluginInfo};+ use super::{AuDescriptor, AuError, Plugin, PluginInfo, registry}; pub fn scan() -> Vec<AuDescriptor> { registry::scan()@@ -542,8 +542,13 @@ pub(crate) fn current_value(tr: &soundcraft_model::Track, param: &AutoParam) -> fn clear_lane(e: &mut Engine, p: &Value) -> Result<Value> { let tracks = tracks_required(e, "automation.clear", p)?; let param = if str_param(p, "param").is_some() { Some(lane_param(p, "automation.clear")?) } else { None };- let r = range_param(e, "automation.clear", p)?;- let (a, b) = if r.is_empty() { (i64::MIN, i64::MAX) } else { (r.start, r.end) };+ let explicit = p.get("start").is_some() || p.get("end").is_some() || p.get("length").is_some();+ let (a, b) = if explicit {+ let r = range_param(e, "automation.clear", p)?;+ (r.start, r.end)+ } else {+ (i64::MIN, i64::MAX)+ }; let s = e.session_mut(); let mut n = 0; for t in &tracks {@@ -989,6 +994,23 @@ mod tests { assert_eq!(count(&e), 0, "absent param must still clear every lane"); } + /// #871: `automation.clear` without `start`/`end` must clear the whole lane, even the point+ /// sitting exactly at the end of the edit selection (clear_range is `[start, end)`).+ #[test]+ fn clear_without_range_removes_the_point_at_the_selection_end() {+ let (mut e, t) = tone_session(&[(0, -6.0), (2048, 0.0), (6144, -80.0)]);+ // Selection [2048, 6144): the point at 6144 is outside it, so a range-limited clear keeps it.+ e.session_mut().edit.selection = Range::new(2048, 6144);+ let r = e.execute("automation.clear", &json!({"track": t.0, "param": "volume"})).unwrap();+ assert_eq!(r["removed"], 3, "clear without a range must remove every point");+ assert_eq!(e.session().track(t).unwrap().automation.iter().map(|l| l.points.len()).sum::<usize>(), 0);+ // With an explicit range, only the in-range points go.+ let (mut e, t) = tone_session(&[(0, -6.0), (2048, 0.0), (6144, -80.0)]);+ e.execute("automation.clear", &json!({"track": t.0, "param": "volume", "start": 2048, "end": 6144})).unwrap();+ let left: Vec<i64> = e.session().track(t).unwrap().automation.iter().flat_map(|l| l.points.iter().map(|p| p.at)).collect();+ assert_eq!(left, vec![0, 6144], "explicit range must keep points outside it");+ }+ #[test] fn send_rejects_unknown_bus() { let mut e = Engine::default();