From 9adcf2f5e5d0816d75349da0e22e249decebda2d Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sat, 29 Jan 2022 12:22:33 -0800 Subject: [PATCH 1/2] Introduce Cycle::with_crossfade --- src/cycle.rs | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/cycle.rs b/src/cycle.rs index 70c480d..915a1bd 100644 --- a/src/cycle.rs +++ b/src/cycle.rs @@ -1,9 +1,13 @@ 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 { /// Current playback time, in samples cursor: Cell, @@ -11,8 +15,23 @@ pub struct Cycle { } impl Cycle { + /// Construct cycle from `frames` played at `rate` Hz, 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::>(); + 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>) -> Self { Self { cursor: Cell::new(0.0), @@ -62,6 +81,18 @@ impl Seek for Cycle { } } +/// Prepare arbitrary frames for glitch-free use in `Cycle` +fn apply_crossfade(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::*; @@ -122,4 +153,14 @@ mod tests { s.sample(10.0, &mut buf[2..]); assert_eq!(buf, [1.0, 2.0, 3.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); + } + } } From 7f7e2b97dbf9ab05ef2843b39ff29e4e636cda72 Mon Sep 17 00:00:00 2001 From: Riccardo Zaglia Date: Sat, 17 Dec 2022 18:22:10 +0100 Subject: [PATCH 2/2] Add bare minimum for seamless streaming --- src/fader.rs | 28 +++++++++++++++++++++++++--- src/spsc.rs | 35 +++++++++++++++++++++++++++++------ src/stream.rs | 42 ++++++++++++++++++++++++++++++------------ 3 files changed, 84 insertions(+), 21 deletions(-) diff --git a/src/fader.rs b/src/fader.rs index 2c67f5c..e6105f6 100644 --- a/src/fader.rs +++ b/src/fader.rs @@ -1,7 +1,9 @@ use core::{ cell::{Cell, UnsafeCell}, mem, + time::Duration, }; +use std::time::Instant; use crate::{frame, math::Float, Controlled, Filter, Frame, Signal, Swap}; @@ -36,11 +38,17 @@ where fn sample(&self, interval: f32, mut out: &mut [T::Frame]) { let inner = unsafe { &mut *self.inner.get() }; - if self.progress.get() >= 1.0 { + if self.progress.get() >= 1.0 || self.progress.get() < 0.0 { // A fade must complete before a new one begins if self.next.refresh() { - self.progress.set(0.0); - } else { + let time_diff = unsafe { (*self.next.received()).as_mut().unwrap() } + .begin + .map(|i| i.saturating_duration_since(Instant::now()).as_secs_f32()) + .unwrap_or(0.0); + self.progress.set(-time_diff); + } + + if !self.next.refresh() || self.progress.get() < 0.0 { // Fast path inner.sample(interval, out); return; @@ -112,6 +120,19 @@ impl<'a, T> FaderControl<'a, T> { *self.0.pending() = Some(Command { fade_to: signal, duration, + begin: None, + }); + } + self.0.flush() + } + + /// Crossfade to `signal` over `duration`, after `seconds_from_now`. + pub fn deferred_fade_to(&mut self, signal: T, duration: f32, seconds_from_now: f32) { + unsafe { + *self.0.pending() = Some(Command { + fade_to: signal, + duration, + begin: Some(Instant::now() + Duration::from_secs_f32(seconds_from_now)), }); } self.0.flush() @@ -121,6 +142,7 @@ impl<'a, T> FaderControl<'a, T> { struct Command { fade_to: T, duration: f32, + begin: Option, } #[cfg(test)] diff --git a/src/spsc.rs b/src/spsc.rs index ff2c64b..56140ea 100644 --- a/src/spsc.rs +++ b/src/spsc.rs @@ -26,7 +26,7 @@ impl Sender { /// Append a prefix of `data` to the channel /// /// Returns the number of items sent. - pub fn send_from_slice(&mut self, data: &[T]) -> usize + pub fn send_from_slice(&mut self, data: &[T]) -> SendSliceResult where T: Copy, { @@ -63,7 +63,12 @@ impl Sender { .header .write .store((write + n) % size, Ordering::Release); - n + + if n < data.len() { + SendSliceResult::PushedElements(n) + } else { + SendSliceResult::RemainingSlots(free.0.len() + free.1.len() - n) + } } } @@ -97,6 +102,12 @@ impl Sender { } } +#[derive(PartialEq, Eq, Debug)] +pub enum SendSliceResult { + RemainingSlots(usize), + PushedElements(usize), +} + pub struct Receiver { shared: Arc>, len: usize, @@ -296,7 +307,10 @@ mod tests { #[test] fn send_excess() { let (mut send, mut recv) = channel::(4); - assert_eq!(send.send_from_slice(&[1, 2, 3, 4, 5]), 4); + assert_eq!( + send.send_from_slice(&[1, 2, 3, 4, 5]), + SendSliceResult::PushedElements(4) + ); recv.update(); assert_eq!(recv.len(), 4); assert_eq!(recv[0], 1); @@ -308,10 +322,19 @@ mod tests { #[test] fn fill_release_fill() { let (mut send, mut recv) = channel::(4); - assert_eq!(send.send_from_slice(&[1, 2, 3, 4]), 4); + assert_eq!( + send.send_from_slice(&[1, 2, 3, 4]), + SendSliceResult::RemainingSlots(0) + ); recv.update(); recv.release(2); - assert_eq!(send.send_from_slice(&[5, 6, 7]), 2); - assert_eq!(send.send_from_slice(&[7]), 0); + assert_eq!( + send.send_from_slice(&[5, 6, 7]), + SendSliceResult::PushedElements(2) + ); + assert_eq!( + send.send_from_slice(&[7]), + SendSliceResult::PushedElements(0) + ); } } diff --git a/src/stream.rs b/src/stream.rs index ce79eea..10df1bd 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -86,12 +86,8 @@ impl Signal for Stream { self.advance(interval * out.len() as f32); } - #[allow(clippy::float_cmp)] fn is_finished(&self) -> bool { - if !self.closed.get() { - return false; - } - self.t.get() == self.inner.borrow().len() as f32 + self.closed.get() } fn handle_dropped(&self) { @@ -116,15 +112,25 @@ where } impl<'a, T> StreamControl<'a, T> { - /// Add more samples. Returns the number of samples read. - pub fn write(&mut self, samples: &[T]) -> usize + /// Add more frames. Returns either the number of frames read if they couldn't all fit inside + /// the buffer, or the remaining number of frames that can be written in the buffer. + pub fn write(&mut self, samples: &[T]) -> StreamWriteResult where T: Copy, { - self.0.send.borrow_mut().send_from_slice(samples) + match self.0.send.borrow_mut().send_from_slice(samples) { + spsc::SendSliceResult::RemainingSlots(n) => StreamWriteResult::AvailableSpace(n), + spsc::SendSliceResult::PushedElements(n) => StreamWriteResult::FramesRead(n), + } } } +#[derive(PartialEq, Eq, Debug)] +pub enum StreamWriteResult { + FramesRead(usize), + AvailableSpace(usize), +} + #[cfg(test)] mod tests { use super::*; @@ -139,10 +145,19 @@ mod tests { #[test] fn smoke() { let s = Stream::::new(1, 3); - assert_eq!(StreamControl(&s).write(&[1.0, 2.0]), 2); - assert_eq!(StreamControl(&s).write(&[3.0, 4.0]), 1); + assert_eq!( + StreamControl(&s).write(&[1.0, 2.0]), + StreamWriteResult::AvailableSpace(1) + ); + assert_eq!( + StreamControl(&s).write(&[3.0, 4.0]), + StreamWriteResult::FramesRead(1) + ); assert_out(&s, &[1.0, 2.0, 3.0, 0.0, 0.0]); - assert_eq!(StreamControl(&s).write(&[5.0, 6.0, 7.0, 8.0]), 3); + assert_eq!( + StreamControl(&s).write(&[5.0, 6.0, 7.0, 8.0]), + StreamWriteResult::FramesRead(3) + ); assert_out(&s, &[5.0]); assert_out(&s, &[6.0, 7.0, 0.0, 0.0]); assert_out(&s, &[0.0, 0.0]); @@ -151,7 +166,10 @@ mod tests { #[test] fn cleanup() { let s = Stream::::new(1, 4); - assert_eq!(StreamControl(&s).write(&[1.0, 2.0]), 2); + assert_eq!( + StreamControl(&s).write(&[1.0, 2.0]), + StreamWriteResult::AvailableSpace(2) + ); assert!(!s.is_finished()); s.handle_dropped(); assert!(!s.is_finished());