diff --git a/src/cycle.rs b/src/cycle.rs index 7802731..b2a9abf 100644 --- a/src/cycle.rs +++ b/src/cycle.rs @@ -1,11 +1,14 @@ use std::{cell::Cell, sync::Arc}; -use crate::{frame, Frame, Frames, Signal}; +use crate::{frame, Controlled, Frame, Frames, Signal, Swap}; -/// Loops [`Frames`] end-to-end to construct a repeating signal +/// Loops [`Frames`] to construct a repeating signal +/// +/// [`CycleControl::set_sample_range`] can be used to adjust the range of frames being repeated. pub struct Cycle { /// Current playback time, in samples cursor: Cell, + range: Swap<(usize, Option)>, frames: Arc>, } @@ -15,17 +18,21 @@ impl Cycle { pub fn new(frames: Arc>) -> Self { Self { cursor: Cell::new(0.0), + range: Swap::new((0, Some(frames.len()))), frames, } } /// Interpolate a frame for position `sample` - fn interpolate(&self, sample: f32) -> T + fn interpolate(&self, start: usize, end: Option, sample: f32) -> T where T: Frame, { let a = sample as usize; - let b = (a + 1) % self.frames.len(); + let b = match end { + None => a + 1, + Some(end) => start + ((a + 1).saturating_sub(start) % (end - start)), + }; frame::lerp(&self.frames[a], &self.frames[b], sample.fract()) } } @@ -34,13 +41,44 @@ impl Signal for Cycle { type Frame = T; fn sample(&self, interval: f32, out: &mut [T]) { + self.range.refresh(); + let (start, end) = unsafe { *self.range.received() }; let ds = interval * self.frames.rate() as f32; for x in out { - *x = self.interpolate(self.cursor.get()); + *x = self.interpolate(start, end, self.cursor.get()); self.cursor .set((self.cursor.get() + ds) % self.frames.len() as f32); } } + + fn remaining(&self) -> f32 { + let (_, end) = unsafe { *self.range.received() }; + match end { + None => self.frames.len() as f32 / self.frames.rate() as f32 - self.cursor.get(), + Some(_) => f32::INFINITY, + } + } +} + +/// Thread-safe control for a [`Cycle`] +pub struct CycleControl<'a>(&'a Swap<(usize, Option)>); + +unsafe impl<'a, T: 'a> Controlled<'a> for Cycle { + type Control = CycleControl<'a>; + + unsafe fn make_control(signal: &'a Cycle) -> Self::Control { + CycleControl(&signal.range) + } +} + +impl<'a> CycleControl<'a> { + /// Adjust the range of time being cycled through, in seconds + /// + /// If the playback cursor is outside the range, it will be immediately moved into it. + pub fn set_sample_range(&mut self, start: usize, end: Option) { + unsafe { self.0.pending().write((start, end)) } + self.0.flush(); + } } #[cfg(test)] diff --git a/src/lib.rs b/src/lib.rs index aec101a..41bdfe4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,7 +51,7 @@ mod stop; mod stream; mod swap; -pub use cycle::Cycle; +pub use cycle::{Cycle, CycleControl}; pub use filter::*; pub use frame::Frame; pub use frames::*;