use timestretch::{AudioBuffer, Channels, EdmPreset, StretchParams};
fn sine_mono(freq: f32, sample_rate: u32, num_frames: usize) -> AudioBuffer {
let data: Vec<f32> = (0..num_frames)
.map(|i| (2.0 * std::f32::consts::PI * freq * i as f32 / sample_rate as f32).sin())
.collect();
AudioBuffer::from_mono(data, sample_rate)
}
fn sine_stereo(freq: f32, sample_rate: u32, num_frames: usize) -> AudioBuffer {
let mut data = Vec::with_capacity(num_frames * 2);
for i in 0..num_frames {
let s = (2.0 * std::f32::consts::PI * freq * i as f32 / sample_rate as f32).sin();
data.push(s);
data.push(s);
}
AudioBuffer::from_stereo(data, sample_rate)
}
#[test]
fn test_slice_then_stretch() {
let full = sine_mono(440.0, 44100, 44100); let portion = full.slice(10000, 20000); assert_eq!(portion.num_frames(), 20000);
let params = StretchParams::new(1.5)
.with_sample_rate(44100)
.with_channels(1);
let output = timestretch::stretch_buffer(&portion, ¶ms).unwrap();
let ratio = output.num_frames() as f64 / portion.num_frames() as f64;
assert!(
(ratio - 1.5).abs() < 0.15,
"Expected ~1.5x, got {:.3}x",
ratio
);
assert!(output.peak() > 0.01, "Output should not be silent");
}
#[test]
fn test_stretch_then_slice() {
let input = sine_mono(220.0, 44100, 22050); let params = StretchParams::new(2.0)
.with_sample_rate(44100)
.with_channels(1)
.with_preset(EdmPreset::Halftime);
let stretched = timestretch::stretch_buffer(&input, ¶ms).unwrap();
let half_len = stretched.num_frames() / 2;
let first_half = stretched.slice(0, half_len);
let second_half = stretched.slice(half_len, half_len);
assert!(first_half.peak() > 0.01);
assert!(second_half.peak() > 0.01);
assert_eq!(
first_half.num_frames() + second_half.num_frames(),
half_len * 2
);
}
#[test]
fn test_concatenate_then_stretch() {
let low = sine_mono(110.0, 44100, 11025); let high = sine_mono(880.0, 44100, 11025); let combined = AudioBuffer::concatenate(&[&low, &high]);
assert_eq!(combined.num_frames(), 22050);
let params = StretchParams::new(1.2)
.with_sample_rate(44100)
.with_channels(1);
let output = timestretch::stretch_buffer(&combined, ¶ms).unwrap();
assert!(!output.is_empty());
assert!(output.peak() > 0.01);
}
#[test]
fn test_stretch_then_concatenate() {
let a = sine_mono(440.0, 44100, 22050);
let b = sine_mono(220.0, 44100, 22050);
let params = StretchParams::new(1.5)
.with_sample_rate(44100)
.with_channels(1);
let stretched_a = timestretch::stretch_buffer(&a, ¶ms).unwrap();
let stretched_b = timestretch::stretch_buffer(&b, ¶ms).unwrap();
let combined = AudioBuffer::concatenate(&[&stretched_a, &stretched_b]);
assert_eq!(
combined.num_frames(),
stretched_a.num_frames() + stretched_b.num_frames()
);
assert!(combined.peak() > 0.01);
}
#[test]
fn test_normalize_before_stretch() {
let quiet = AudioBuffer::from_mono(
(0..22050)
.map(|i| 0.1 * (2.0 * std::f32::consts::PI * 440.0 * i as f32 / 44100.0).sin())
.collect(),
44100,
);
assert!(quiet.peak() < 0.15);
let normalized = quiet.normalize(1.0);
assert!((normalized.peak() - 1.0).abs() < 1e-4);
let params = StretchParams::new(1.5)
.with_sample_rate(44100)
.with_channels(1);
let output = timestretch::stretch_buffer(&normalized, ¶ms).unwrap();
assert!(!output.is_empty());
assert!(
output.rms() > 0.1,
"Normalized input should produce loud output"
);
}
#[test]
fn test_stretch_then_normalize() {
let input = sine_mono(440.0, 44100, 22050);
let params = StretchParams::new(1.5)
.with_sample_rate(44100)
.with_channels(1);
let stretched = timestretch::stretch_buffer(&input, ¶ms).unwrap();
let normalized = stretched.normalize(0.5);
assert!((normalized.peak() - 0.5).abs() < 1e-4);
}
#[test]
fn test_fade_in_out_then_stretch() {
let input = sine_mono(440.0, 44100, 44100); let faded = input.fade_in(4410).fade_out(4410);
assert!(faded.data[0].abs() < 0.01);
let mid_rms = AudioBuffer::from_mono(faded.data[20000..24000].to_vec(), 44100).rms();
assert!(
mid_rms > 0.3,
"Middle region RMS should be significant: {}",
mid_rms
);
let params = StretchParams::new(1.0)
.with_sample_rate(44100)
.with_channels(1);
let output = timestretch::stretch_buffer(&faded, ¶ms).unwrap();
assert!(!output.is_empty());
}
#[test]
fn test_stretch_then_fade() {
let input = sine_mono(440.0, 44100, 22050);
let params = StretchParams::new(1.5)
.with_sample_rate(44100)
.with_channels(1);
let stretched = timestretch::stretch_buffer(&input, ¶ms).unwrap();
let fade_frames = stretched.num_frames() / 10;
let faded = stretched.fade_in(fade_frames).fade_out(fade_frames);
assert!(faded.data[0].abs() < 1e-6);
assert!(faded.data[faded.data.len() - 1].abs() < 0.1);
}
#[test]
fn test_trim_silence_after_stretch() {
let mut data = vec![0.0f32; 4410]; data.extend(
(0..22050).map(|i| (2.0 * std::f32::consts::PI * 440.0 * i as f32 / 44100.0).sin()),
);
data.extend(vec![0.0f32; 4410]); let input = AudioBuffer::from_mono(data, 44100);
let params = StretchParams::new(1.5)
.with_sample_rate(44100)
.with_channels(1);
let stretched = timestretch::stretch_buffer(&input, ¶ms).unwrap();
let trimmed = stretched.trim_silence(0.01);
assert!(
trimmed.num_frames() < stretched.num_frames(),
"Trimming should reduce frame count"
);
assert!(trimmed.peak() > 0.1);
}
#[test]
fn test_rms_preserved_after_identity_stretch() {
let input = sine_mono(440.0, 44100, 22050);
let input_rms = input.rms();
let params = StretchParams::new(1.0)
.with_sample_rate(44100)
.with_channels(1);
let output = timestretch::stretch_buffer(&input, ¶ms).unwrap();
let output_rms = output.rms();
let ratio = output_rms / input_rms;
assert!(
(ratio - 1.0).abs() < 0.3,
"RMS should be roughly preserved: ratio={}",
ratio
);
}
#[test]
fn test_peak_gain_roundtrip() {
let input = sine_mono(440.0, 44100, 22050);
let original_peak = input.peak();
let quieter = input.apply_gain(-6.0);
assert!(quieter.peak() < original_peak);
let restored = quieter.normalize(original_peak);
assert!((restored.peak() - original_peak).abs() < 1e-4);
}
#[test]
fn test_frames_iterator_with_stereo_stretch() {
let input = sine_stereo(440.0, 44100, 22050);
let params = StretchParams::new(1.5)
.with_sample_rate(44100)
.with_channels(2);
let output = timestretch::stretch_buffer(&input, ¶ms).unwrap();
let mut frame_count = 0;
for frame in &output {
assert_eq!(frame.len(), 2, "Each stereo frame should have 2 samples");
assert!(frame[0].is_finite());
assert!(frame[1].is_finite());
frame_count += 1;
}
assert_eq!(frame_count, output.num_frames());
}
#[test]
fn test_frames_iterator_peak_matches() {
let input = sine_mono(440.0, 44100, 22050);
let params = StretchParams::new(1.2)
.with_sample_rate(44100)
.with_channels(1);
let output = timestretch::stretch_buffer(&input, ¶ms).unwrap();
let manual_peak = output.frames().map(|f| f[0].abs()).fold(0.0f32, f32::max);
assert!((manual_peak - output.peak()).abs() < 1e-6);
}
#[test]
fn test_as_ref_interop() {
let input = sine_mono(440.0, 44100, 22050);
let slice: &[f32] = input.as_ref();
let params = StretchParams::new(1.0)
.with_sample_rate(44100)
.with_channels(1);
let output = timestretch::stretch(slice, ¶ms).unwrap();
assert!(!output.is_empty());
}
#[test]
fn test_partial_eq_after_clone_and_modify() {
let a = sine_mono(440.0, 44100, 22050);
let b = a.clone();
assert_eq!(a, b);
let c = a.apply_gain(6.0);
assert_ne!(a, c);
}
#[test]
fn test_dj_crossfade_workflow() {
let track_a = sine_mono(440.0, 44100, 44100); let track_b = sine_mono(330.0, 44100, 44100);
let params = StretchParams::new(126.0 / 128.0) .with_sample_rate(44100)
.with_channels(1)
.with_preset(EdmPreset::DjBeatmatch);
let a_stretched = timestretch::stretch_buffer(&track_a, ¶ms).unwrap();
let b_stretched = timestretch::stretch_buffer(&track_b, ¶ms).unwrap();
let crossfade_frames = a_stretched.num_frames().min(b_stretched.num_frames()) / 4;
let a_faded = a_stretched.fade_out(crossfade_frames);
let b_faded = b_stretched.fade_in(crossfade_frames);
assert!(a_faded.peak() > 0.01);
assert!(b_faded.peak() > 0.01);
}
#[test]
fn test_sample_chop_workflow() {
let full_sample = sine_mono(220.0, 44100, 88200);
let frames_per_chop = full_sample.num_frames() / 4;
let chops: Vec<AudioBuffer> = (0..4)
.map(|i| full_sample.slice(i * frames_per_chop, frames_per_chop))
.collect();
let params = StretchParams::new(2.0)
.with_sample_rate(44100)
.with_channels(1)
.with_preset(EdmPreset::Halftime);
let stretched_chops: Vec<AudioBuffer> = chops
.iter()
.map(|c| timestretch::stretch_buffer(c, ¶ms).unwrap())
.collect();
let normalized: Vec<AudioBuffer> = stretched_chops.iter().map(|c| c.normalize(0.8)).collect();
let faded: Vec<AudioBuffer> = normalized
.iter()
.map(|c| {
let fade = c.num_frames() / 20; c.fade_in(fade).fade_out(fade)
})
.collect();
let refs: Vec<&AudioBuffer> = faded.iter().collect();
let result = AudioBuffer::concatenate(&refs);
assert!(result.num_frames() > full_sample.num_frames()); assert!(
result.peak() > 0.1 && result.peak() <= 0.81,
"Peak {} should be in (0.1, 0.81]",
result.peak()
);
assert!(result.rms() > 0.01); }
#[test]
fn test_channels_from_count_in_params() {
let channels = Channels::from_count(2).unwrap();
assert_eq!(channels, Channels::Stereo);
let params = StretchParams::new(1.0)
.with_sample_rate(44100)
.with_channels(channels.count() as u32);
assert_eq!(params.channels, Channels::Stereo);
}