mix: clamp negative fractional source position in stretched-clip rendering

This commit is contained in:
midasmatterhorn
2026-10-14 18:01:42 +03:00
parent 3a28a65769
commit 76696826cd
+25 -1
View File
@@ -1442,7 +1442,7 @@ fn render_clip(s: &Session, clip: &Clip, pos: Samples, frames: usize, buf: &mut
let x0 = f.floor();
let fr = (f - x0) as f32;
let a = if x0 >= 0.0 { src.get(x0 as usize).copied().unwrap_or(0.0) } else { 0.0 };
let b = if x0 + 1.0 >= 0.0 { src.get((x0 as usize).saturating_add(1)).copied().unwrap_or(0.0) } else { 0.0 };
let b = if x0 + 1.0 >= 0.0 { src.get((x0 + 1.0) as usize).copied().unwrap_or(0.0) } else { 0.0 };
a + (b - a) * fr
};
let t = (k - i) as f32 / n;
@@ -2369,6 +2369,30 @@ mod tests {
assert!(out[0][100] > 0.49 && out[1][100].abs() < 1e-4);
}
#[test]
fn a_stretched_clip_before_its_source_start_reads_from_the_first_sample() {
// A clip that extends before its source (a negative offset) plays silence there. When it
// is stretched, the read position passes through [-1, 0), which must interpolate from
// that silence towards sample 0 of the file -- not towards sample 1.
let mut s = Session::default();
let t = s.add_track(TrackKind::Audio, ChannelFormat::Mono, None);
let buf = AudioBuffer { sample_rate: 48_000, channels: vec![vec![1.0, 0.5, 0.25, 0.125]] };
s.pool.insert(SourceId(901), Arc::new(SourceAudio::new(buf)));
let id = s.new_clip_id();
let mut c = Clip::audio(id, "late", SourceId(901), -1, 0, 4);
c.stretch = 2.0;
if let Some(pl) = s.track_mut(t).and_then(|t| t.playlist_mut()) {
pl.clips.push(c);
}
let out = render_clips(&s, t, Range::new(0, 4));
// rel 0 -> source -1.0: silence; rel 1 -> source -0.5: halfway from silence to sample 0;
// rel 2 -> source 0.0: sample 0; rel 3 -> source 0.5: halfway to sample 1.
let want = [0.0, 0.5, 1.0, 0.75];
for (i, w) in want.iter().enumerate() {
assert!((out[0][i] - w).abs() < 1e-6, "frame {i}: {} vs {w}", out[0][i]);
}
}
#[test]
fn fade_curvature_reaches_the_rendered_audio() {
for fade_in in [true, false] {