Clear: lift clips removed by id in Shuffle so crossfade partners don't overlap

This commit is contained in:
ydking0911
2026-10-30 16:13:42 +09:00
parent 9f5f312051
commit 5a1324b8bc
2 changed files with 31 additions and 20 deletions
+13 -20
View File
@@ -177,28 +177,21 @@ pub fn clear_range(s: &mut Session, track: TrackId, range: Range, shuffle: bool)
removed
}
/// Remove the clips `ids` from every track. In Shuffle the later clips on each track close the gaps they leave,
/// the latest gap first so the earlier ones stay where they were. Returns how many clips went.
/// Remove the clips `ids` from every track. In Shuffle the later clips on each track close the holes the movable
/// ones leave, as a Shuffle move lifts them (see [`lift_plan`]), so a crossfade partner keeps its material. Returns
/// how many clips went.
pub fn remove_clips(s: &mut Session, ids: &[ClipId], shuffle: bool) -> usize {
let mut gaps: Vec<(TrackId, Range)> = Vec::new();
for t in &mut s.tracks {
let track = t.id;
if let Some(pl) = t.playlist_mut() {
pl.clips.retain(|c| {
let gone = ids.contains(&c.id);
if gone {
gaps.push((track, c.range()));
}
!gone
});
let ids: HashSet<ClipId> = ids.iter().copied().collect();
let mut n = 0;
for track in s.tracks.iter().map(|t| t.id).collect::<Vec<_>>() {
if shuffle {
n += lift_all(s, track, &ids).len();
}
}
let n = gaps.len();
if shuffle {
gaps.sort_by_key(|(t, r)| (t.0, std::cmp::Reverse(r.start)));
for (track, r) in gaps {
let moved = ripple(s, track, r.end, -r.len());
follow_automation(s, track, r.end, moved);
// Locked clips don't lift; they go without closing up, as everything does outside Shuffle.
if let Some(pl) = s.track_mut(track).and_then(|t| t.playlist_mut()) {
let before = pl.clips.len();
pl.clips.retain(|c| !ids.contains(&c.id));
n += before - pl.clips.len();
}
}
n
+18
View File
@@ -175,6 +175,24 @@ fn remove_clips_closes_each_gap_in_shuffle_only() {
assert_eq!(split(true), vec![(1000, 1000, 2000), (2000, 1000, 4000)]);
}
#[test]
fn remove_clips_in_shuffle_keeps_crossfade_partners_apart() {
// `(start, end)` of each clip; the second one goes.
let remove_second = |spans: &[(Samples, Samples)]| {
let (mut s, t) = session_with_clip();
let ids: Vec<ClipId> = spans.iter().map(|_| s.new_clip_id()).collect();
if let Some(pl) = s.track_mut(t).and_then(|t| t.playlist_mut()) {
pl.clips = ids.iter().zip(spans).map(|(id, (a, b))| Clip::audio(*id, "c", SourceId(500), *a, *a, b - a)).collect();
}
assert_eq!(remove_clips(&mut s, &[ids[1]], true), 1);
clips(&s, t).into_iter().map(|(start, len, _)| (start, start + len)).collect::<Vec<_>>()
};
// Crossfaded with the clip before: the hole starts where that one ends.
assert_eq!(remove_second(&[(0, 1050), (950, 2000), (2000, 3000)]), vec![(0, 1050), (1050, 2050)]);
// Crossfaded with the clip after: the hole ends where that one starts.
assert_eq!(remove_second(&[(0, 1000), (1000, 2050), (1950, 3000), (4000, 5000)]), vec![(0, 1000), (1000, 2050), (3050, 4050)]);
}
#[test]
fn copy_paste_overwrites() {
let (mut s, t) = session_with_clip();