diff --git a/crates/engine/src/cmd.rs b/crates/engine/src/cmd.rs index b943c5c1..9b9d7954 100644 --- a/crates/engine/src/cmd.rs +++ b/crates/engine/src/cmd.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/edit.rs b/crates/engine/src/cmd/edit.rs index 06e6bec0..98797a26 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...", @@ -821,38 +812,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/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 fc18e173..aa794c0f 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -455,6 +455,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; diff --git a/crates/ui-egui/src/dialogs/strip_silence.rs b/crates/ui-egui/src/dialogs/strip_silence.rs index 2d5b98e9..52f3da5b 100644 --- a/crates/ui-egui/src/dialogs/strip_silence.rs +++ b/crates/ui-egui/src/dialogs/strip_silence.rs @@ -42,10 +42,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"); +}