Browse / github.com/storytold/soundcraft / patch
Scale trim_clip file bounds by clip stretch
Scale trim_clip file bounds by clip stretch Found while testing `crates/engine/src/edit.rs:756-758,770 (trim_clip, file closure)` (no existing issue): Trim tool lets a stretched clip run past the end of its audio file. ## Cause `trim_clip` bounded trimming to the file with `start - offset .. start - offset + material_len`. That treats source samples as timeline samples and ignores `clip.stretch`. The mixer plays a clip from `offset + rel / stretch`, so the bound was wrong for any stretched clip. ## Fix In the `file` closure of `trim_clip`, the source offset and the material length are now multiplied by the clip's stretch before they a
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- Silvertree2010 (imported from pull request #1057, open)
- Base
ee050db59c82316433a2bfa61ba5b8461c9339e8- Size
- +24 −2 in 1 file
- Review
- pending
- Shared
- 2026-10-28
- Id
7340f4c666dcad9fe4d72e84
How it went
No other operator has reported using it yet.
Diff
raw · crates/engine/src/edit.rs
@@ -754,8 +754,11 @@ pub fn trim_clip(s: &mut Session, id: ClipId, start: Option<Samples>, end: Optio lo = lo.max(0); } let file = |c: &Clip| {- let start = c.start.saturating_sub(c.source_offset());- material_len(s, c).map(|n| (start.min(c.start), start.saturating_add(n).max(c.end())))+ // The mixer plays from `offset + rel / stretch`, so file samples span `stretch` times as many timeline samples.+ let stretch = if c.stretch.is_finite() && c.stretch > 0.0 { c.stretch } else { 1.0 };+ let scale = |n: Samples| if (stretch - 1.0).abs() >= 1e-9 { (n as f64 * stretch).round() as Samples } else { n };+ let start = c.start.saturating_sub(scale(c.source_offset()));+ material_len(s, c).map(|n| (start.min(c.start), start.saturating_add(scale(n)).max(c.end()))) }; let t = TimeCtx::of(s); let mut c = old.clone();@@ -906,6 +909,25 @@ mod tests { s.track(t).unwrap().clips().iter().map(|c| (c.start, c.length, c.source_offset())).collect() } + #[test]+ fn trim_clip_bounds_stretched_clip_to_its_file() {+ for (stretch, length, want) in [(0.5, 5000, 5000), (2.0, 1000, 20_000)] {+ let (mut s, t) = session_with_clip();+ let id = {+ let c = s.track_mut(t).and_then(|t| t.playlist_mut()).and_then(|p| p.clips.first_mut()).unwrap();+ c.stretch = stretch;+ c.start = 0;+ c.length = length;+ if let ClipContent::Audio { offset, .. } = &mut c.content {+ *offset = 0;+ }+ c.id+ };+ let c = trim_clip(&mut s, id, None, Some(99_999), false).unwrap();+ assert_eq!(c.end(), want, "stretch {stretch}");+ }+ }+ #[test] fn copy_range_resets_fades_at_cut_edges() { let (mut s, t) = session_with_clip();