audio-lab 0.1.0

Toolkit for audio research and testing
Documentation
use ndarray::Array2;

use crate::signal::{SignalError, TimeSignal};

#[derive(Debug, thiserror::Error)]
pub enum ImpulseError {
    #[error("delay ({delay}) must be less than num_time_steps ({num_time_steps})")]
    DelayOutOfRange { delay: usize, num_time_steps: usize },
    #[error(transparent)]
    Signal(#[from] SignalError),
}

#[derive(Debug, Clone, Copy)]
pub struct ImpulseConfig {
    pub delay: usize,
    pub amplitude: f64,
    pub sample_rate: f64,
    pub num_channels: usize,
}

impl Default for ImpulseConfig {
    fn default() -> Self {
        Self {
            delay: 0,
            amplitude: 1.0,
            sample_rate: 48_000.0,
            num_channels: 1,
        }
    }
}

pub fn generate_impulse(
    num_time_steps: usize,
    config: ImpulseConfig,
) -> Result<TimeSignal, ImpulseError> {
    let ImpulseConfig {
        delay,
        amplitude,
        sample_rate,
        num_channels,
    } = config;

    if delay >= num_time_steps {
        return Err(ImpulseError::DelayOutOfRange {
            delay,
            num_time_steps,
        });
    }

    let mut data = Array2::zeros((num_channels, num_time_steps));
    for ch in 0..num_channels {
        data[[ch, delay]] = amplitude;
    }

    let mut signal = TimeSignal::new(data, sample_rate)?;
    let comment = format!("Impulse signal (delay = {delay} samples, amplitude = {amplitude})");
    signal.set_comment(Some(&comment));
    Ok(signal)
}

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

    #[test]
    fn generates_impulse_at_zero() {
        let signal = generate_impulse(8, ImpulseConfig::default()).unwrap();
        assert_eq!(signal.channel(0)[0], 1.0);
        assert_eq!(signal.channel(0)[1], 0.0);
    }

    #[test]
    fn generates_impulse_with_delay() {
        let signal = generate_impulse(
            8,
            ImpulseConfig {
                delay: 3,
                amplitude: 0.5,
                num_channels: 2,
                ..Default::default()
            },
        )
        .unwrap();
        assert_eq!(signal.channel(0)[3], 0.5);
        assert_eq!(signal.channel(1)[3], 0.5);
        assert_eq!(signal.channel(0)[0], 0.0);
    }

    #[test]
    fn validates_delay() {
        assert!(matches!(
            generate_impulse(
                8,
                ImpulseConfig {
                    delay: 8,
                    ..Default::default()
                }
            ),
            Err(ImpulseError::DelayOutOfRange { .. })
        ));
    }
}