Files
soundcraft/crates/engine/src/cmd/options.rs
T
Dylan M. TaylorandDylan Taylor fe4bb3fe0c style: replace flagged glob imports with explicit uses (SonarCloud rust:S2208); move release workflow's write permission to the job (S8233) (#988)
* security: move the release workflow's write permission to the job level (SonarCloud githubactions:S8233)

* style: replace wildcard imports with explicit uses (SonarCloud rust:S2208)

Rewrites every glob `use` Sonar flags into explicit imports, so what each
command module consumes is visible at the top of the file.

* release: the git-sources gate moves into craft-common-actions (PR #78)

The check is shared at release.yml@main now; keep only the reusable call.

---------

Co-authored-by: Dylan Taylor <dylanmtaylor@users.noreply.github.com>
2026-10-23 14:53:00 -04:00

130 lines
7.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Options menu toggles and Setup › Session settings.
use super::{CommandSpec, always, bad, bool_or, position_param, str_param};
use crate::{Engine, Result, cmd};
use serde_json::{Value, json};
use soundcraft_model::{BitDepthSetting, EditState};
use soundcraft_time::FrameRate;
macro_rules! toggle {
($id:literal, $label:literal, [$($m:literal),*], $sc:expr, $field:ident) => {
cmd!(noundo $id, $label, [$($m),*], $sc, "{value?: bool}", always, |e, p| {
let s = e.session_mut();
s.edit.$field = p.get("value").and_then(Value::as_bool).unwrap_or(!s.edit.$field);
Ok(json!({"value": s.edit.$field}))
})
};
}
pub fn specs() -> Vec<CommandSpec> {
vec![
toggle!("options.loop_record", "Loop Record", ["Options"], Some("Alt+L"), loop_record),
toggle!("options.quickpunch", "QuickPunch", ["Options"], Some("Cmd+Shift+P"), quickpunch),
toggle!("options.pre_post_roll", "Pre/Post-Roll", ["Options"], Some("Cmd+K"), pre_post_roll),
toggle!("options.loop_playback", "Loop Playback", ["Options"], Some("Cmd+Shift+L"), loop_playback),
toggle!("options.link_timeline_edit", "Link Timeline and Edit Selection", ["Options"], Some("Shift+/"), link_timeline_edit),
toggle!("options.link_track_edit", "Link Track and Edit Selection", ["Options"], None, link_track_edit),
toggle!("options.insertion_follows_playback", "Insertion Follows Playback", ["Options"], None, insertion_follows_playback),
toggle!("options.tab_to_transient", "Tab to Transient", ["Options"], Some("Cmd+Alt+Tab"), tab_to_transient),
toggle!("options.mirrored_midi", "Mirrored MIDI Editing", ["Options"], None, mirrored_midi),
toggle!("options.automation_follows_edit", "Automation Follows Edit", ["Options"], None, automation_follows_edit),
toggle!("options.markers_follow_edit", "Markers Follow Edit", ["Options"], None, markers_follow_edit),
toggle!("options.layered_editing", "Layered Editing", ["Options"], None, layered_editing),
toggle!("options.click", "Click", ["Options"], None, click),
toggle!("options.countoff", "Count Off", [], None, countoff),
toggle!("options.midi_merge", "MIDI Merge", [], None, midi_merge),
toggle!("options.pre_fader_metering", "Pre-Fader Metering", ["Options"], None, pre_fader_metering),
toggle!("options.delay_compensation", "Delay Compensation", ["Options"], None, delay_compensation),
cmd!(noundo "options.destructive_record", "Destructive Record", ["Options"], None, "{value?: bool}", always, |e, p| record_mode(e, p, "destructive")),
cmd!(noundo "options.trackpunch", "TrackPunch", ["Options"], Some("Cmd+Shift+T"), "{value?: bool}", always, |e, p| record_mode(e, p, "trackpunch")),
cmd!(noundo "options.scrolling", "Edit Window Scrolling", ["Options", "Edit Window Scrolling"], None, "{mode: none|after_playback|page|continuous|center}", always, |e, p| {
let m = str_param(p, "mode").filter(|m| ["none", "after_playback", "page", "continuous", "center"].contains(m)).ok_or_else(|| bad("options.scrolling", "unknown mode"))?.to_string();
e.session_mut().edit.scrolling = m.clone();
Ok(json!({"mode": m}))
}),
cmd!(noundo "options.solo_mode", "Solo Mode", ["Options", "Solo Mode"], None, "{mode: sip|afl|pfl, latch?: bool, xor?: bool}", always, |e, p| {
let m = str_param(p, "mode").unwrap_or("sip").to_string();
let mut v = m.clone();
if bool_or(p, "xor", false) { v.push_str("+xor") }
e.session_mut().edit.solo_mode = v.clone();
Ok(json!({"mode": v}))
}),
cmd!(noundo "options.keyboard_focus", "Keyboard Focus", [], None, "{focus?: commands|clips|groups|none} — toggles Commands Keyboard Focus when omitted", always, |e, p| {
let s = e.session_mut();
let f = match str_param(p, "focus") {
Some(f @ ("commands" | "clips" | "groups" | "none")) => f.to_string(),
Some(other) => return Err(bad("options.keyboard_focus", format!("unknown focus `{other}`"))),
None => if s.edit.keyboard_focus == "commands" { "none".into() } else { "commands".into() },
};
s.edit.keyboard_focus = f.clone();
Ok(json!({"focus": f}))
}),
cmd!(noundo "options.pre_roll", "Pre-Roll Amount", [], None, "{length: samples|{seconds}}", always, |e, p| {
let v = position_param(e, "options.pre_roll", p, "length")?.unwrap_or(0).max(0);
e.session_mut().edit.pre_roll = v;
Ok(json!({"pre_roll": v}))
}),
cmd!(noundo "options.post_roll", "Post-Roll Amount", [], None, "{length}", always, |e, p| {
let v = position_param(e, "options.post_roll", p, "length")?.unwrap_or(0).max(0);
e.session_mut().edit.post_roll = v;
Ok(json!({"post_roll": v}))
}),
cmd!(
"setup.session",
"Session",
["Setup"],
Some("Cmd+2"),
"{frame_rate?: '29.97 Drop'…, bit_depth?: 16|24|32f, timecode_start?: position, name?}",
always,
session_setup
),
cmd!(noundo "setup.preferences", "Preferences...", ["Setup"], None, "{}", always, |e, _| Ok(json!({"edit": e.session().edit.clone()}))),
cmd!(noundo "setup.reset_edit_state", "Reset Edit Settings", [], None, "{}", always, |e, _| {
let s = e.session_mut();
let keep = (s.edit.selection, s.edit.selected_tracks.clone());
s.edit = EditState::default();
s.edit.selection = keep.0;
s.edit.selected_tracks = keep.1;
Ok(json!({}))
}),
]
}
fn record_mode(e: &mut Engine, p: &Value, mode: &str) -> Result<Value> {
let s = e.session_mut();
let on = p.get("value").and_then(Value::as_bool).unwrap_or(s.edit.record_mode != mode);
s.edit.record_mode = if on { mode.to_string() } else { "normal".into() };
Ok(json!({"record_mode": s.edit.record_mode}))
}
fn session_setup(e: &mut Engine, p: &Value) -> Result<Value> {
let fr = match str_param(p, "frame_rate") {
Some(f) => {
Some(FrameRate::ALL.into_iter().find(|r| r.label().eq_ignore_ascii_case(f)).ok_or_else(|| bad("setup.session", "unknown frame rate"))?)
}
None => None,
};
let bd = match p.get("bit_depth").map(|v| v.to_string().trim_matches('"').to_ascii_lowercase()) {
Some(b) if b == "16" => Some(BitDepthSetting::Int16),
Some(b) if b == "24" => Some(BitDepthSetting::Int24),
Some(b) if b.starts_with("32") => Some(BitDepthSetting::Float32),
Some(_) => return Err(bad("setup.session", "bit_depth must be 16, 24 or 32f")),
None => None,
};
let tc = position_param(e, "setup.session", p, "timecode_start")?;
let s = e.session_mut();
if let Some(f) = fr {
s.frame_rate = f;
}
if let Some(b) = bd {
s.bit_depth = b;
}
if let Some(t) = tc {
s.timecode_start = t;
}
if let Some(n) = p.get("name").and_then(Value::as_str) {
s.name = n.to_string();
}
Ok(json!({"frame_rate": s.frame_rate.label(), "bit_depth": s.bit_depth, "sample_rate": s.sample_rate.hz()}))
}