rill-core 0.5.0-beta.5

Core foundation for the Rill ecosystem — traits, math, buffers, queues, time, macros
Documentation
use crate::buffer::{AtomicStats, Buffer, BufferStats};
use crate::math::Transcendental;
use core::marker::PhantomData;
use core::ops::{Index, IndexMut};

/// Delay line for signal effects — single-threaded circular buffer.
///
/// Provides a circular buffer for implementing delay, reverb,
/// and other time-based effects.
///
/// # Thread safety
///
/// `DelayLine` is **not** thread-safe. It is designed for single-threaded
/// use inside the signal graph. Use [`PipeBuffer`](super::PipeBuffer) or
/// [`RingBuffer`](super::RingBuffer) for cross-thread communication.
#[repr(align(64))]
pub struct DelayLine<T: Transcendental, const MAX_DELAY: usize> {
    buffer: [T; MAX_DELAY],
    write_pos: usize,
    delay_samples: usize,
    sample_rate: f32,
    stats: AtomicStats,
    _phantom: PhantomData<T>,
}

impl<T: Transcendental, const MAX_DELAY: usize> DelayLine<T, MAX_DELAY> {
    /// Create a new delay line with the given sample rate.
    ///
    /// # Panics
    /// Panics if `MAX_DELAY` is 0.
    pub fn new(sample_rate: f32) -> Self {
        assert!(MAX_DELAY > 0, "DelayLine must have MAX_DELAY > 0");
        Self {
            buffer: [T::ZERO; MAX_DELAY],
            write_pos: 0,
            delay_samples: 0,
            sample_rate,
            stats: AtomicStats::new(),
            _phantom: PhantomData,
        }
    }

    /// Set the delay time in seconds.
    pub fn set_delay(&mut self, delay_sec: f32) {
        let samples = (delay_sec * self.sample_rate) as usize;
        self.delay_samples = samples.min(MAX_DELAY - 1);
    }

    /// Set the delay time in samples.
    pub fn set_delay_samples(&mut self, samples: usize) {
        self.delay_samples = samples.min(MAX_DELAY - 1);
    }

    /// Current delay in samples.
    pub fn delay_samples(&self) -> usize {
        self.delay_samples
    }
    /// Maximum possible delay (const generic parameter).
    pub const fn max_delay(&self) -> usize {
        MAX_DELAY
    }
    /// The sample rate used for sec-to-sample conversion.
    pub fn sample_rate(&self) -> f32 {
        self.sample_rate
    }

    /// Write a sample and return the delayed sample.
    #[inline(always)]
    pub fn write(&mut self, input: T) -> T {
        self.buffer[self.write_pos] = input;
        let read_pos = if self.write_pos >= self.delay_samples {
            self.write_pos - self.delay_samples
        } else {
            MAX_DELAY + self.write_pos - self.delay_samples
        };
        let output = self.buffer[read_pos];
        self.write_pos = (self.write_pos + 1) % MAX_DELAY;
        self.stats.record_write();
        self.stats.record_read();
        self.stats.update_peak(MAX_DELAY);
        output
    }

    /// Read the delayed sample without writing.
    #[inline(always)]
    pub fn read(&self) -> T {
        let read_pos = if self.write_pos >= self.delay_samples {
            self.write_pos - self.delay_samples
        } else {
            MAX_DELAY + self.write_pos - self.delay_samples
        };
        self.buffer[read_pos]
    }

    /// Read sample at arbitrary delay (0 = most recent).
    #[inline(always)]
    pub fn read_delayed(&self, delay: usize) -> T {
        debug_assert!(
            delay < MAX_DELAY,
            "Delay {} out of range (max {})",
            delay,
            MAX_DELAY
        );
        let read_pos = if self.write_pos > delay {
            self.write_pos - 1 - delay
        } else {
            MAX_DELAY + self.write_pos - 1 - delay
        };
        self.buffer[read_pos]
    }

    /// Read with linear interpolation between samples.
    #[inline(always)]
    pub fn read_interpolated(&self, delay_frac: f32) -> T {
        let delay_int = delay_frac.floor() as usize;
        let frac = T::from_f32(delay_frac.fract());
        let s1 = self.read_delayed(delay_int);
        if delay_int == MAX_DELAY - 1 {
            s1
        } else {
            let s2 = self.read_delayed(delay_int + 1);
            s1 + (s2 - s1) * frac
        }
    }

    /// Clear the delay line (fill with zeros).
    pub fn clear(&mut self) {
        self.buffer.fill(T::ZERO);
        self.write_pos = 0;
        self.stats.reset();
    }

    /// Current write cursor position in the circular buffer.
    pub fn write_position(&self) -> usize {
        self.write_pos
    }
}

// ============================================================================
// Index implementations
// ============================================================================

impl<T: Transcendental, const MAX_DELAY: usize> Index<usize> for DelayLine<T, MAX_DELAY> {
    type Output = T;
    fn index(&self, index: usize) -> &Self::Output {
        &self.buffer[index]
    }
}

impl<T: Transcendental, const MAX_DELAY: usize> IndexMut<usize> for DelayLine<T, MAX_DELAY> {
    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
        &mut self.buffer[index]
    }
}

// ============================================================================
// Buffer trait implementation
// ============================================================================

impl<T: Transcendental, const MAX_DELAY: usize> Buffer<T> for DelayLine<T, MAX_DELAY> {
    fn capacity(&self) -> usize {
        MAX_DELAY
    }
    fn len(&self) -> usize {
        MAX_DELAY
    }
    fn is_empty(&self) -> bool {
        false
    }
    fn is_full(&self) -> bool {
        true
    }
    fn as_slice(&self) -> &[T] {
        &self.buffer
    }
    fn as_mut_slice(&mut self) -> &mut [T] {
        &mut self.buffer
    }
    fn fill(&mut self, value: T) {
        self.buffer.fill(value);
    }
    fn copy_from(&mut self, src: &[T]) {
        let len = src.len().min(MAX_DELAY);
        self.buffer[..len].copy_from_slice(&src[..len]);
    }
    fn clear(&mut self) {
        self.buffer.fill(T::ZERO);
        self.write_pos = 0;
        self.stats.reset();
    }
    fn stats(&self) -> BufferStats {
        let mut stats = self.stats.snapshot();
        stats.fill_level = 1.0;
        stats
    }
    fn reset_stats(&mut self) {
        self.stats.reset();
    }
}

// ============================================================================
// Tests
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_delay_line_basic() {
        let mut delay = DelayLine::<f32, 1024>::new(44100.0);
        delay.set_delay_samples(100);
        for i in 0..200 {
            let out = delay.write(i as f32);
            if i >= 100 {
                assert_eq!(out, (i - 100) as f32);
            }
        }
    }

    #[test]
    fn test_delay_line_read_delayed() {
        let mut delay = DelayLine::<f32, 1024>::new(44100.0);
        for i in 0..1024 {
            delay.write(i as f32);
        }
        assert_eq!(delay.read_delayed(0), 1023.0);
        assert_eq!(delay.read_delayed(100), 923.0);
    }

    #[test]
    fn test_delay_line_interpolation() {
        let mut delay = DelayLine::<f32, 1024>::new(44100.0);
        for i in 0..1024 {
            delay.write(i as f32);
        }
        let val = delay.read_interpolated(100.5);
        assert!((val - 922.5).abs() < 0.01);
    }
}