From 8a1c82733cc71c38e41d7fc21cc49d5f113c18bd Mon Sep 17 00:00:00 2001 From: Mawen Salignat-Moandal Date: Fri, 9 Oct 2026 21:03:36 +0200 Subject: [PATCH 1/2] Honor file.import_audio new_tracks instead of silently ignoring it. Explicit track still wins; new_tracks:false uses the selected audio track or errors clearly. Fixes #60. --- crates/engine/src/cmd/file.rs | 86 +++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/crates/engine/src/cmd/file.rs b/crates/engine/src/cmd/file.rs index 86bb4b5..6299c24 100644 --- a/crates/engine/src/cmd/file.rs +++ b/crates/engine/src/cmd/file.rs @@ -3,7 +3,7 @@ use super::*; use crate::cmd; use serde_json::json; -use soundcraft_model::Session; +use soundcraft_model::{Session, TrackKind}; use soundcraft_time::SampleRate; pub fn specs() -> Vec { @@ -52,7 +52,7 @@ pub fn specs() -> Vec { "Audio...", ["File", "Import"], Some("Cmd+Shift+I"), - "{path | paths: [..], track?: target track, at?: position, new_tracks?: true}", + "{path | paths: [..], track?: target track, at?: position, new_tracks?: true (default; false uses `track` or the selected audio track)}", always, import_audio ), @@ -208,7 +208,7 @@ fn import_audio(e: &mut Engine, p: &Value) -> Result { return Err(bad("file.import_audio", "`path` or `paths` required")); } let at = position_param(e, "file.import_audio", p, "at")?.unwrap_or(0).max(0); - let target = track_param(e, "file.import_audio", p, "track")?; + let target = import_audio_target(e, p)?; let mut out = Vec::new(); for path in &paths { let bytes = std::fs::read(path).map_err(|err| EngineError::Io(format!("{path}: {err}")))?; @@ -219,6 +219,21 @@ fn import_audio(e: &mut Engine, p: &Value) -> Result { Ok(json!({"imported": out})) } +/// Destination for `file.import_audio`: an explicit `track` wins; otherwise `new_tracks` +/// (default true) creates a new track per file, and `new_tracks: false` uses the selected +/// audio track or errors when none is selected. +fn import_audio_target(e: &Engine, p: &Value) -> Result> { + if let Some(t) = track_param(e, "file.import_audio", p, "track")? { + return Ok(Some(t)); + } + if bool_or(p, "new_tracks", true) { + return Ok(None); + } + let s = e.session(); + let selected = s.edit.selected_tracks.iter().find_map(|id| s.track(*id).filter(|t| t.kind == TrackKind::Audio).map(|t| t.id)); + selected.map(Some).ok_or_else(|| bad("file.import_audio", "`new_tracks: false` needs `track` or a selected audio track")) +} + /// `fold_down: "stereo"` bounces an ITU stereo fold-down of a surround mix. fn fold_down_param(p: &Value, id: &str) -> Result { match str_param(p, "fold_down").map(str::trim) { @@ -270,3 +285,68 @@ fn write_score(e: &mut Engine, p: &Value, id: &str, svg: bool) -> Result let measures = parts.iter().map(|p| p.measures.len()).max().unwrap_or(0); Ok(json!({"path": path, "parts": parts.len(), "measures": measures})) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::Engine; + use soundcraft_audio_io::{AudioBuffer, EncodeOptions}; + use soundcraft_model::ChannelFormat; + + fn tone_wav(dir: &std::path::Path, name: &str) -> String { + let tone: Vec = (0..4_800).map(|i| (i as f32 * 0.1).sin() * 0.2).collect(); + let buf = AudioBuffer { sample_rate: 48_000, channels: vec![tone] }; + let bytes = soundcraft_audio_io::encode(&buf, &EncodeOptions::default()).unwrap(); + let path = dir.join(name); + std::fs::write(&path, bytes).unwrap(); + path.to_string_lossy().into_owned() + } + + #[test] + fn import_audio_honours_new_tracks() { + let dir = std::env::temp_dir().join(format!("sc-import-new-tracks-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).unwrap(); + let wav = tone_wav(&dir, "tone.wav"); + + // Default / new_tracks:true: create a new track even when one is selected. + let mut e = Engine::default(); + e.execute("track.new", &json!({"count": 1, "name": "Target", "format": "mono"})).unwrap(); + let target = e.session().tracks[0].id; + e.session_mut().edit.selected_tracks = vec![target]; + e.execute("file.import_audio", &json!({"path": wav, "new_tracks": true})).unwrap(); + assert_eq!(e.session().tracks.len(), 2); + assert_eq!(e.session().track(target).unwrap().clips().len(), 0); + assert_eq!(e.session().tracks[1].clips().len(), 1); + + // new_tracks:false with a selected audio track: import onto it. + let mut e = Engine::default(); + e.execute("track.new", &json!({"count": 1, "name": "Target", "format": "mono"})).unwrap(); + let target = e.session().tracks[0].id; + e.session_mut().edit.selected_tracks = vec![target]; + e.execute("file.import_audio", &json!({"path": wav, "new_tracks": false})).unwrap(); + assert_eq!(e.session().tracks.len(), 1); + assert_eq!(e.session().track(target).unwrap().clips().len(), 1); + + // new_tracks:false with no selection and no track: clear error. + let mut e = Engine::default(); + let err = e.execute("file.import_audio", &json!({"path": wav, "new_tracks": false})).unwrap_err(); + assert!(err.to_string().contains("new_tracks: false"), "{err}"); + + // Explicit track wins over new_tracks:true. + let mut e = Engine::default(); + e.execute("track.new", &json!({"count": 1, "name": "Target", "format": "mono"})).unwrap(); + e.execute("file.import_audio", &json!({"path": wav, "track": "Target", "new_tracks": true})).unwrap(); + assert_eq!(e.session().tracks.len(), 1); + assert_eq!(e.session().tracks[0].clips().len(), 1); + + // Omitted new_tracks still creates a track on a blank session. + let mut e = Engine::default(); + e.execute("file.import_audio", &json!({"path": wav})).unwrap(); + assert_eq!(e.session().tracks.len(), 1); + assert_eq!(e.session().tracks[0].kind, TrackKind::Audio); + assert_eq!(e.session().tracks[0].format, ChannelFormat::Mono); + + let _ = std::fs::remove_dir_all(dir); + } +} From ff4d8910fa3f46d7d228ee49d6c68fe22d9b3582 Mon Sep 17 00:00:00 2001 From: Mawen Salignat-Moandal Date: Fri, 9 Oct 2026 21:22:24 +0200 Subject: [PATCH 2/2] Credit Mawen Salignat-Moandal in the About contributors list. --- contributors/people.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contributors/people.toml b/contributors/people.toml index 89c6db2..fad3a62 100644 --- a/contributors/people.toml +++ b/contributors/people.toml @@ -21,3 +21,7 @@ real_name = "Brandon Thomas" display_name = "Brandon Thomas" sync_github_name = false + +[people.salignatmoandal] +real_name = "Mawen Salignat-Moandal" +sync_github_name = true