Merge pull request #284 from marcoarnulfo/fix/elastic-properties-ratio

fix(clip): Elastic Properties stretches the clip by the requested ratio (#227)
This commit is contained in:
Brandon Thomas
2026-10-13 04:27:14 -04:00
committed by GitHub
+40 -3
View File
@@ -199,9 +199,7 @@ fn elastic(e: &mut Engine, p: &Value) -> Result<Value> {
continue;
}
let out_len = soundcraft_time::to_samples(base.length as f64 * ratio).max(1);
let mut warp = base.clone();
warp.stretch = ratio;
let Some(src) = super::more_util::render_stretched(s, "clip.elastic_properties", &warp, out_len)? else { continue };
let Some(src) = super::more_util::render_stretched(s, "clip.elastic_properties", &base, out_len)? else { continue };
if orig.is_none()
&& let soundcraft_model::ClipContent::Audio { source, offset } = base.content
{
@@ -378,6 +376,45 @@ mod tests {
assert_eq!(e.session().find_clip(ClipId(cid)).unwrap().1.length, len0);
}
/// A clip on an Elastic track playing `clip_len` samples of a source that is silent before `tone_at`.
fn elastic_clip(source_len: usize, tone_at: usize, clip_len: i64) -> (crate::Engine, TrackId, ClipId) {
use soundcraft_model::{ChannelFormat, Clip, Session, TrackKind};
let mut s = Session::default();
let t = s.add_track(TrackKind::Audio, ChannelFormat::Mono, Some("Elastic"));
let samples = (0..source_len).map(|i| if i >= tone_at { (i as f32 * 0.2).sin() * 0.4 } else { 0.0 }).collect();
let buf = soundcraft_audio_io::AudioBuffer { sample_rate: 48_000, channels: vec![samples] };
let src = crate::io::add_source(&mut s, "Tone", buf, None, soundcraft_audio_io::FileFormat::Wav);
let cid = s.new_clip_id();
crate::edit::place_clip(&mut s, t, Clip::audio(cid, "Clip", src, 0, 0, clip_len));
let mut e = crate::Engine::new(s);
e.execute("track.elastic", &json!({"track": t.0, "algorithm": "polyphonic"})).unwrap();
(e, t, cid)
}
fn rendered_peak(e: &crate::Engine, t: TrackId, cid: ClipId) -> f32 {
let range = e.session().find_clip(cid).unwrap().1.range();
soundcraft_mix::render_clips(e.session(), t, range).iter().flatten().fold(0.0, |m, x| m.max(x.abs()))
}
/// Elastic Properties stretches the material the clip plays, by the requested ratio (#227).
#[test]
fn elastic_properties_stretches_the_clip_material() {
let (mut e, t, cid) = elastic_clip(20_000, 10_000, 20_000);
e.execute("clip.elastic_properties", &json!({"clips": [cid.0], "ratio": 2.0})).unwrap();
assert_eq!(e.session().find_clip(cid).unwrap().1.length, 40_000);
assert!(rendered_peak(&e, t, cid) > 0.3, "the tone in the second half was lost");
let (mut e, t, cid) = elastic_clip(60_000, 20_000, 20_000);
e.execute("clip.elastic_properties", &json!({"clips": [cid.0], "ratio": 0.5})).unwrap();
assert_eq!(rendered_peak(&e, t, cid), 0.0, "audio from past the clip end leaked in");
for (ratio, len) in [(4.0, 80_000), (0.2, 4_000)] {
let (mut e, _, cid) = elastic_clip(20_000, 10_000, 20_000);
e.execute("clip.elastic_properties", &json!({"clips": [cid.0], "ratio": ratio})).unwrap();
assert_eq!(e.session().find_clip(cid).unwrap().1.length, len, "ratio {ratio}");
}
}
#[test]
fn remove_warp_keeps_the_audio_when_the_original_file_is_gone() {
let mut e = crate::demo::demo_engine();