Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion crates/engine/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Value>;
/// `Err(reason)` = disabled.
Expand Down Expand Up @@ -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
Expand Down
45 changes: 3 additions & 42 deletions crates/engine/src/cmd/edit.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -125,15 +125,6 @@ pub fn specs() -> Vec<CommandSpec> {
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...",
Expand Down Expand Up @@ -821,38 +812,8 @@ fn copy_to_new_playlist(e: &mut Engine, p: &Value) -> Result<Value> {
Ok(json!({"tracks": tracks.len()}))
}

/// Strip Silence's tracks, range, threshold, minimum strip duration and clip start and end pads.
type StripSettings = (Vec<TrackId>, 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<StripSettings> {
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<crate::io::StripScan> {
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<Value> {
let found = strip_silence_scan(e, p)?.run()?;
let tracks: Vec<Value> =
found.iter().map(|(t, ranges)| json!({"track": t.0, "ranges": ranges.iter().map(|x| [x.start, x.end]).collect::<Vec<_>>()})).collect();
Ok(json!({"tracks": tracks}))
}

fn strip_silence(e: &mut Engine, p: &Value) -> Result<Value> {
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}`")))?,
Expand Down
49 changes: 49 additions & 0 deletions crates/engine/src/cmd/strip_silence.rs
Original file line number Diff line number Diff line change
@@ -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<CommandSpec> {
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<TrackId>, 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<StripSettings> {
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<crate::io::StripScan> {
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<Value> {
let found = strip_silence_scan(e, p)?.run()?;
let tracks: Vec<Value> =
found.iter().map(|(t, ranges)| json!({"track": t.0, "ranges": ranges.iter().map(|x| [x.start, x.end]).collect::<Vec<_>>()})).collect();
Ok(json!({"tracks": tracks}))
}
4 changes: 4 additions & 0 deletions crates/ui-egui/src/dialogs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,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;
Expand Down
5 changes: 1 addition & 4 deletions crates/ui-egui/src/dialogs/strip_silence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
15 changes: 15 additions & 0 deletions crates/ui-egui/tests/it/strip_silence_rectangles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Loading