mirror of
https://github.com/storytold/soundcraft.git
synced 2026-11-07 02:39:57 -06:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a43c6218f |
@@ -125,6 +125,43 @@ pub fn separate_at_if(s: &mut Session, track: TrackId, at: Samples, auto_name: i
|
||||
count
|
||||
}
|
||||
|
||||
/// Split every clip on a track's active playlist at each of `points`, in one pass over the
|
||||
/// playlist: [`separate_at_if`] once per point with `|_| false` (no piece takes an Auto Rename
|
||||
/// name here), without rebuilding the playlist per point. Returns the number of splits.
|
||||
pub fn separate_at_points(s: &mut Session, track: TrackId, points: &[Samples]) -> usize {
|
||||
let mut pts = points.to_vec();
|
||||
pts.sort_unstable();
|
||||
pts.dedup();
|
||||
let tc_tempo = s.tempo.clone();
|
||||
let t = TimeCtx { tempo: &tc_tempo, sr: s.sample_rate };
|
||||
// The points strictly inside a clip.
|
||||
let within = |c: &Clip| {
|
||||
let (lo, hi) = (pts.partition_point(|p| *p <= c.start), pts.partition_point(|p| *p < c.end()));
|
||||
lo..hi.max(lo)
|
||||
};
|
||||
let n_split: usize = s.track(track).map_or(0, |tr| tr.clips().iter().map(|c| within(c).len()).sum());
|
||||
let mut ids: Vec<ClipId> = (0..n_split).map(|_| s.new_clip_id()).collect();
|
||||
let Some(pl) = s.track_mut(track).and_then(|t| t.playlist_mut()) else { return 0 };
|
||||
let mut out = Vec::with_capacity(pl.clips.len() + n_split);
|
||||
for c in pl.clips.drain(..) {
|
||||
let mut rest = c;
|
||||
for p in &pts[within(&rest)] {
|
||||
let Some(id) = ids.pop() else { break };
|
||||
match split_clip(&rest, *p, id, t) {
|
||||
Some((l, r)) => {
|
||||
out.push(l);
|
||||
rest = r;
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
out.push(rest);
|
||||
}
|
||||
pl.clips = out;
|
||||
pl.sort();
|
||||
n_split
|
||||
}
|
||||
|
||||
/// Gives `c` the next name of its Auto Rename scheme, when it has one, and counts the scheme up.
|
||||
fn take_auto_name(schemes: &mut std::collections::BTreeMap<u64, soundcraft_model::AutoName>, c: &mut Clip) {
|
||||
if let Some(scheme) = c.auto_name.and_then(|k| schemes.get_mut(&k)) {
|
||||
|
||||
@@ -117,9 +117,7 @@ pub fn strip_silence(
|
||||
}
|
||||
cut_points.push(r.end);
|
||||
let old: std::collections::HashSet<_> = s.track(t).map(|x| x.clips().iter().map(|c| c.id).collect()).unwrap_or_default();
|
||||
for p in &cut_points {
|
||||
crate::edit::separate_at_if(s, t, *p, |_| false);
|
||||
}
|
||||
crate::edit::separate_at_points(s, t, &cut_points);
|
||||
let kept = strip_pieces(s, t, r, &keep, mode);
|
||||
crate::edit::auto_name_new_clips(s, t, &old, |c| !(named && c.is_audio() && c.start >= r.start && c.end() <= r.end));
|
||||
Ok(kept)
|
||||
@@ -144,10 +142,24 @@ fn strip_pieces(s: &mut Session, t: TrackId, r: Range, keep: &[(Samples, Samples
|
||||
let audio: Vec<Range> = keep.iter().map(|(a, b)| Range::new(*a, *b)).collect();
|
||||
let remove = if mode == StripMode::Extract { &audio } else { &gaps };
|
||||
let inside = |c: &Clip, g: &Range| c.start >= g.start && c.end() <= g.end;
|
||||
// The range of `rs` holding `c`: by binary search when `rs` is ascending and disjoint (the
|
||||
// usual case, checked once by the caller), by scan otherwise.
|
||||
let holder = |rs: &[Range], sorted: bool, c: &Clip| {
|
||||
if sorted {
|
||||
let i = rs.partition_point(|g| g.end < c.end());
|
||||
rs.get(i).filter(|g| inside(c, g)).map(|_| i)
|
||||
} else {
|
||||
rs.iter().position(|g| inside(c, g))
|
||||
}
|
||||
};
|
||||
let ordered = |rs: &[Range]| rs.windows(2).all(|w| w[0].end <= w[1].start);
|
||||
let Some(pl) = s.track_mut(t).and_then(|x| x.playlist_mut()) else { return if mode == StripMode::Extract { 0 } else { keep.len() } };
|
||||
pl.clips.retain(|c| !remove.iter().any(|g| inside(c, g)));
|
||||
let sorted = ordered(remove);
|
||||
pl.clips.retain(|c| holder(remove, sorted, c).is_none());
|
||||
if mode == StripMode::Extract {
|
||||
return gaps.iter().filter(|g| pl.clips.iter().any(|c| inside(c, g))).count();
|
||||
let sorted = ordered(&gaps);
|
||||
let held: std::collections::HashSet<usize> = pl.clips.iter().filter_map(|c| holder(&gaps, sorted, c)).collect();
|
||||
return held.len();
|
||||
}
|
||||
keep.len()
|
||||
}
|
||||
@@ -294,6 +306,26 @@ mod strip_tests {
|
||||
(r, e.session().track(t).unwrap().clips().iter().map(|c| (c.start, c.end())).collect())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cutting_at_many_points_in_one_pass_equals_cutting_at_each() {
|
||||
let spans = |e: &Engine, t: TrackId| {
|
||||
let mut v: Vec<_> = e.session().track(t).unwrap().clips().iter().map(|c| (c.start, c.end())).collect();
|
||||
v.sort_unstable();
|
||||
v
|
||||
};
|
||||
let points = [30_000, 10, 5_000, 30_000, 120_000, 0, 20_000, 1_000_000];
|
||||
let (mut one, t) = tones(&[0, 20_000, 100_000], 40_000);
|
||||
let (mut each, _) = tones(&[0, 20_000, 100_000], 40_000);
|
||||
crate::edit::separate_at_points(one.session_mut(), t, &points);
|
||||
for p in points {
|
||||
crate::edit::separate_at_if(each.session_mut(), t, p, |_| false);
|
||||
}
|
||||
assert_eq!(spans(&one, t), spans(&each, t));
|
||||
assert!(spans(&one, t).len() > 3);
|
||||
let ids: std::collections::HashSet<_> = one.session().track(t).unwrap().clips().iter().map(|c| c.id).collect();
|
||||
assert_eq!(ids.len(), spans(&one, t).len(), "new pieces have distinct ids");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strip_keeps_the_audio_and_removes_the_silence() {
|
||||
let (r, clips) = strip_mode("strip");
|
||||
|
||||
Reference in New Issue
Block a user