diff --git a/crates/engine/src/clip_group_file.rs b/crates/engine/src/clip_group_file.rs index e20caf0c..4708a6ec 100644 --- a/crates/engine/src/clip_group_file.rs +++ b/crates/engine/src/clip_group_file.rs @@ -149,6 +149,7 @@ pub fn import(e: &mut Engine, path: &str, first: Option, at: Samples) - s.add_track(kind, fmt, Some("Clip Group")) } }; + let mut prepared = Vec::new(); for mut c in lane { if let ClipContent::Audio { source, offset } = &mut c.content { let Some(n) = map.get(&source.0) else { continue }; @@ -163,7 +164,16 @@ pub fn import(e: &mut Engine, path: &str, first: Option, at: Samples) - c.group = Some(group_id); c.sanitize(); placed.push(c.id); - crate::edit::place_clip(s, target, c); + prepared.push(c); + } + // Clear what was already on the track first, then add the whole lane at once, so clips of + // the group that overlap each other (crossfades) keep their overlap. + for c in &prepared { + crate::edit::clear_range(s, target, c.range(), false); + } + if let Some(pl) = s.track_mut(target).and_then(|t| t.playlist_mut()) { + pl.clips.extend(prepared); + pl.sort(); } } Ok(placed) @@ -229,6 +239,26 @@ mod tests { let _ = std::fs::remove_dir_all(&dir); } + #[test] + fn overlapping_clips_of_a_group_keep_their_overlap_on_import() { + let dir = std::env::temp_dir().join(format!("sc-grp-overlap-{}", std::process::id())); + let _ = std::fs::create_dir_all(&dir); + let p = dir.join("overlap.scgrp"); + let note = json!({"pitch": 60, "velocity": 100, "release_velocity": 64, "channel": 0, "start": 0, "length": 100}); + let content = json!({"Midi": {"sequence": {"notes": [note], "ctrls": []}}}); + let a = json!({"id": 1, "name": "a", "start": 0, "length": 1000, "content": content}); + let b = json!({"id": 2, "name": "b", "start": 600, "length": 1000, "content": content}); + let file = json!({"format": "soundcraft.clipgroup", "version": 1, "sample_rate": 48000, "length": 1600, "lanes": [[a, b]], "audio": {}}); + std::fs::write(&p, file.to_string()).unwrap(); + let mut e = crate::demo::demo_engine(); + let r = e.execute("file.import_clip_groups", &json!({"path": p.to_string_lossy()})).unwrap(); + let ids: Vec<_> = r["clips"].as_array().unwrap().iter().filter_map(serde_json::Value::as_u64).collect(); + assert_eq!(ids.len(), 2); + let spans: Vec<_> = ids.iter().map(|id| e.session().find_clip(soundcraft_model::ClipId(*id)).map(|(_, c)| (c.start, c.length))).collect(); + assert_eq!(spans, vec![Some((0, 1000)), Some((600, 1000))]); + let _ = std::fs::remove_dir_all(&dir); + } + #[test] fn a_file_named_by_many_ids_becomes_one_source() { let dir = std::env::temp_dir().join(format!("sc-grp-shared-{}", std::process::id()));