From ee55acbc93e76a424f54658e84e828a7965d527c Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 25 Oct 2026 14:13:49 -0700 Subject: [PATCH] Strip Silence: closing the window while Mix shows stops the rectangle scan Only the Edit window dropped the Strip Silence rectangles and their scan when Strip Silence closed. With the Mix window showing, a scan for old values ran to the end and strip_scan_running() stayed true. dialogs::show, which runs every frame, now drops them whenever neither Strip Silence nor its Rename Selected Clips dialog is open. edit.strip_silence_preview, strip_silence_scan and strip_settings move from cmd/edit.rs to a new cmd/strip_silence.rs with its own specs(), as AGENTS.md asks for new commands. edit.strip_silence reads its settings through the same strip_settings. --- crates/engine/src/cmd/edit.rs | 45 ++--------------- crates/engine/src/cmd/mod.rs | 4 +- crates/engine/src/cmd/strip_silence.rs | 49 +++++++++++++++++++ crates/ui-egui/src/dialogs.rs | 9 ++-- .../tests/it/strip_silence_rectangles.rs | 15 ++++++ 5 files changed, 75 insertions(+), 47 deletions(-) create mode 100644 crates/engine/src/cmd/strip_silence.rs diff --git a/crates/engine/src/cmd/edit.rs b/crates/engine/src/cmd/edit.rs index c11d3cbb..ab791760 100644 --- a/crates/engine/src/cmd/edit.rs +++ b/crates/engine/src/cmd/edit.rs @@ -1,7 +1,7 @@ //! Edit menu commands. use super::{ - CommandSpec, always, bad, bool_or, can_redo, can_undo, clip_id, clip_ids_param, f32_or, f64_or, has_clipboard, has_range, has_selection, - has_tracks, i64_or, position_param, range_param, str_param, track_param, tracks_param, tracks_required, + CommandSpec, always, bad, bool_or, can_redo, can_undo, clip_id, clip_ids_param, f32_or, has_clipboard, has_range, has_selection, has_tracks, + i64_or, position_param, range_param, str_param, track_param, tracks_param, tracks_required, }; use soundcraft_time::Samples; @@ -125,15 +125,6 @@ pub fn specs() -> Vec { has_range, strip_silence ), - cmd!( - query "edit.strip_silence_preview", - "Strip Silence Rectangles", - [], - None, - "{threshold_db?: -48, min_length_ms?: 50, pad_before_ms?: 5, pad_after_ms?: 20, tracks?, start?, end?} (the ranges edit.strip_silence finds with the same settings, padded, on each track: what Strip keeps and the Strip Silence window draws as rectangles; changes nothing)", - has_range, - strip_silence_preview - ), cmd!( "edit.fades_create", "Create...", @@ -812,38 +803,8 @@ fn copy_to_new_playlist(e: &mut Engine, p: &Value) -> Result { Ok(json!({"tracks": tracks.len()})) } -/// Strip Silence's tracks, range, threshold, minimum strip duration and clip start and end pads. -type StripSettings = (Vec, soundcraft_time::Range, f32, Samples, (Samples, Samples)); - -/// The [`StripSettings`] in Strip Silence's params, with their defaults and limits. `cmd` names the -/// command in errors. -fn strip_settings(e: &Engine, cmd: &str, p: &Value) -> Result { - let tracks = tracks_required(e, cmd, p)?; - let r = range_param(e, cmd, p)?; - let thr = f32_or(p, "threshold_db", -48.0).clamp(-96.0, 0.0); - let sr = e.session().sample_rate; - let ms = |k: &str, d: f64| sr.samples(f64_or(p, k, d).clamp(0.0, 10_000.0) / 1000.0); - Ok((tracks, r, thr, ms("min_length_ms", 50.0), (ms("pad_before_ms", 5.0), ms("pad_after_ms", 20.0)))) -} - -/// The scan `edit.strip_silence_preview` runs with params `p`, on the session as it is now. The Edit -/// window runs it while the Strip Silence window or its Rename Selected Clips dialog is open: on a -/// worker thread outside the web build, and on the frame thread in the web build or when no thread -/// can be started. -pub fn strip_silence_scan(e: &Engine, p: &Value) -> Result { - let (tracks, range, threshold_db, min_len, pad) = strip_settings(e, "edit.strip_silence_preview", p)?; - Ok(crate::io::StripScan { session: e.session_arc(), tracks, range, threshold_db, min_len, pad, stop: Default::default() }) -} - -fn strip_silence_preview(e: &mut Engine, p: &Value) -> Result { - let found = strip_silence_scan(e, p)?.run()?; - let tracks: Vec = - found.iter().map(|(t, ranges)| json!({"track": t.0, "ranges": ranges.iter().map(|x| [x.start, x.end]).collect::>()})).collect(); - Ok(json!({"tracks": tracks})) -} - fn strip_silence(e: &mut Engine, p: &Value) -> Result { - let (tracks, r, thr, min_len, (pre, post)) = strip_settings(e, "edit.strip_silence", p)?; + let (tracks, r, thr, min_len, (pre, post)) = super::strip_silence::strip_settings(e, "edit.strip_silence", p)?; let mode = match p.get("mode") { None => crate::io::StripMode::Strip, Some(Value::String(m)) => crate::io::StripMode::from_id(m).ok_or_else(|| bad("edit.strip_silence", format!("unknown mode `{m}`")))?, diff --git a/crates/engine/src/cmd/mod.rs b/crates/engine/src/cmd/mod.rs index b943c5c1..9b9d7954 100644 --- a/crates/engine/src/cmd/mod.rs +++ b/crates/engine/src/cmd/mod.rs @@ -28,6 +28,7 @@ mod plugin_state; mod query; pub(crate) mod sampler; mod setup_more; +mod strip_silence; mod surround; mod tab; mod track; @@ -63,7 +64,7 @@ mod panic_probe { pub use file::bounce_job; pub(crate) use edit::remember_selection; -pub use edit::strip_silence_scan; +pub use strip_silence::strip_silence_scan; pub type Run = fn(&mut Engine, &Value) -> Result; /// `Err(reason)` = disabled. @@ -160,6 +161,7 @@ pub fn command_specs() -> &'static [CommandSpec] { v.extend(sampler::specs()); v.extend(video::specs()); v.extend(zoom_toggle::specs()); + v.extend(strip_silence::specs()); #[cfg(test)] v.extend(panic_probe::specs()); v diff --git a/crates/engine/src/cmd/strip_silence.rs b/crates/engine/src/cmd/strip_silence.rs new file mode 100644 index 00000000..4fbc51c1 --- /dev/null +++ b/crates/engine/src/cmd/strip_silence.rs @@ -0,0 +1,49 @@ +//! Strip Silence Rectangles: the ranges Strip Silence keeps, which the Strip Silence window draws. +use super::{CommandSpec, f32_or, f64_or, has_range, range_param, tracks_required}; +use soundcraft_model::TrackId; +use soundcraft_time::Samples; + +use crate::{Engine, Result, cmd}; +use serde_json::{Value, json}; + +pub fn specs() -> Vec { + vec![cmd!( + query "edit.strip_silence_preview", + "Strip Silence Rectangles", + [], + None, + "{threshold_db?: -48, min_length_ms?: 50, pad_before_ms?: 5, pad_after_ms?: 20, tracks?, start?, end?} (the ranges edit.strip_silence finds with the same settings, padded, on each track: what Strip keeps and the Strip Silence window draws as rectangles; changes nothing)", + has_range, + strip_silence_preview + )] +} + +/// Strip Silence's tracks, range, threshold, minimum strip duration and clip start and end pads. +pub(super) type StripSettings = (Vec, soundcraft_time::Range, f32, Samples, (Samples, Samples)); + +/// The [`StripSettings`] in Strip Silence's params, with their defaults and limits. `cmd` names the +/// command in errors. +pub(super) fn strip_settings(e: &Engine, cmd: &str, p: &Value) -> Result { + let tracks = tracks_required(e, cmd, p)?; + let r = range_param(e, cmd, p)?; + let thr = f32_or(p, "threshold_db", -48.0).clamp(-96.0, 0.0); + let sr = e.session().sample_rate; + let ms = |k: &str, d: f64| sr.samples(f64_or(p, k, d).clamp(0.0, 10_000.0) / 1000.0); + Ok((tracks, r, thr, ms("min_length_ms", 50.0), (ms("pad_before_ms", 5.0), ms("pad_after_ms", 20.0)))) +} + +/// The scan `edit.strip_silence_preview` runs with params `p`, on the session as it is now. The Edit +/// window runs it while the Strip Silence window or its Rename Selected Clips dialog is open: on a +/// worker thread outside the web build, and on the frame thread in the web build or when no thread +/// can be started. +pub fn strip_silence_scan(e: &Engine, p: &Value) -> Result { + let (tracks, range, threshold_db, min_len, pad) = strip_settings(e, "edit.strip_silence_preview", p)?; + Ok(crate::io::StripScan { session: e.session_arc(), tracks, range, threshold_db, min_len, pad, stop: Default::default() }) +} + +fn strip_silence_preview(e: &mut Engine, p: &Value) -> Result { + let found = strip_silence_scan(e, p)?.run()?; + let tracks: Vec = + found.iter().map(|(t, ranges)| json!({"track": t.0, "ranges": ranges.iter().map(|x| [x.start, x.end]).collect::>()})).collect(); + Ok(json!({"tracks": tracks})) +} diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index a2f11adb..1c01e730 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -406,6 +406,10 @@ pub fn show(app: &mut SoundApp, ctx: &egui::Context) { audiosuite_window(app, ctx); crate::bounce::show(app, ctx); crate::import::show(app, ctx); + // Whichever window shows, closing Strip Silence drops its rectangles and stops their scan. + if !matches!(app.dialogs.open, Some(Dialog::StripSilence { .. } | Dialog::StripSilenceRename { .. })) { + app.dialogs.strip_rectangles = StripRectangles::default(); + } let Some(mut d) = app.dialogs.open.take() else { return }; let mut keep = true; let mut action: Option<(String, Value)> = None; @@ -914,10 +918,7 @@ pub fn strip_silence_rectangles(app: &mut SoundApp, ctx: &egui::Context) -> Stri let values = match &app.dialogs.open { Some(Dialog::StripSilence { threshold, min_ms, pre_ms, post_ms }) => [*threshold, *min_ms, *pre_ms, *post_ms], Some(Dialog::StripSilenceRename { strip, .. }) => *strip, - _ => { - app.dialogs.strip_rectangles = StripRectangles::default(); - return Arc::default(); - } + _ => return Arc::default(), }; let key: StripKey = (app.engine.revision, values.map(f64::to_bits)); let r = &mut app.dialogs.strip_rectangles; diff --git a/crates/ui-egui/tests/it/strip_silence_rectangles.rs b/crates/ui-egui/tests/it/strip_silence_rectangles.rs index 4ee12174..a76445ec 100644 --- a/crates/ui-egui/tests/it/strip_silence_rectangles.rs +++ b/crates/ui-egui/tests/it/strip_silence_rectangles.rs @@ -109,3 +109,18 @@ fn the_rectangles_stay_while_rename_is_open_and_go_when_the_window_closes() { assert_eq!(rectangles(&h), 0); assert_eq!(clips(&h, track), before); } + +#[test] +fn closing_the_window_while_the_mix_window_shows_stops_the_scan() { + let (mut h, _) = strip_silence_dialog(); + if let Some(Dialog::StripSilence { threshold, .. }) = &mut h.state_mut().dialogs.open { + *threshold = -30.0; + } + h.run_steps(1); + assert!(h.state().dialogs.strip_scan_running(), "a scan for the new threshold started"); + h.state_mut().run("window.mix", json!({})).unwrap(); + h.run_steps(2); + h.state_mut().dialogs.open = None; + h.run_steps(2); + assert!(!h.state().dialogs.strip_scan_running(), "the scan still runs after the window closed"); +}