Skip to content

Commit

Permalink
Introduce Cycle::with_crossfade
Browse files Browse the repository at this point in the history
  • Loading branch information
Ralith committed Jan 29, 2022
1 parent c03526d commit 758b2b3
Showing 1 changed file with 42 additions and 2 deletions.
44 changes: 42 additions & 2 deletions src/cycle.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
use alloc::sync::Arc;
use core::cell::Cell;

use crate::{frame, math::Float, Frame, Frames, Seek, Signal};
use crate::{frame, frame::lerp, math::Float, Frame, Frames, Seek, Signal};

/// Loops [`Frames`] end-to-end to construct a repeating signal
///
/// To avoid glitching at the loop point, the underlying `Frames` *must* be specially prepared to
/// provide a smooth transition.
#[derive(Clone)]
pub struct Cycle<T> {
/// Current playback time, in samples
cursor: Cell<f32>,
frames: Arc<Frames<T>>,
}

impl<T> Cycle<T> {
/// Construct cycle from `frames`, smoothing the loop point over `crossfade_size` seconds
///
/// Suitable for use with arbitrary audio,
pub fn with_crossfade(crossfade_size: f32, rate: u32, frames: &[T]) -> Self
where
T: Frame + Copy,
{
let mut frames = frames.iter().copied().collect::<alloc::vec::Vec<_>>();
let frames = apply_crossfade((crossfade_size * rate as f32) as usize, &mut frames);
Self::new(Frames::from_slice(rate, frames))
}

/// Construct cycle from `frames`
// TODO: Crossfade
///
/// `frames` *must* be specially constructed to loop seamlessly. For arbitrary audio, use
/// [`with_crossfade`](Self::with_crossfade) instead.
pub fn new(frames: Arc<Frames<T>>) -> Self {
Self {
cursor: Cell::new(0.0),
Expand Down Expand Up @@ -52,6 +70,18 @@ impl<T: Frame + Copy> Seek for Cycle<T> {
}
}

/// Prepare arbitrary frames for glitch-free use in `Cycle`
fn apply_crossfade<T: Frame + Copy>(size: usize, frames: &mut [T]) -> &mut [T] {
let end = frames.len() - size;
for i in 0..size {
let a = frames[end + i];
let b = frames[i];
let t = (i + 1) as f32 / (size + 1) as f32;
frames[i] = lerp(&a, &b, t);
}
&mut frames[..end]
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -74,4 +104,14 @@ mod tests {
s.sample(1.0, &mut buf[2..]);
assert_eq!(buf, [1.0, 2.0, 3.0, 1.0, 2.0]);
}

#[test]
fn crossfade() {
let mut frames = [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0];
let faded = apply_crossfade(4, &mut frames);
assert_eq!(faded.len(), 5);
for i in 0..5 {
assert!((faded[i] - (5 - (i + 1)) as f32 / 5.0).abs() < 1e-3);
}
}
}

0 comments on commit 758b2b3

Please sign in to comment.