rill-io 0.5.0-beta.3

Audio I/O backends for Rill - CPAL, ALSA, PipeWire, JACK
Documentation
use std::fmt;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::time::Duration;

use crate::backend::{AudioBackend, BackendType};
use crate::config::AudioConfig;
use crate::error::IoResult;
use rill_core::io::IoBackend;

#[derive(Copy, Clone)]
struct CbSlot(usize);
unsafe impl Send for CbSlot {}
unsafe impl Sync for CbSlot {}

impl CbSlot {
    fn new() -> Self {
        Self(Box::into_raw(Box::new(None::<Box<dyn Fn()>>)) as usize)
    }
    unsafe fn set(&self, cb: Box<dyn Fn()>) {
        (*(self.0 as *mut Option<Box<dyn Fn()>>)) = Some(cb);
    }
    unsafe fn call(&self) {
        if let Some(ref cb) = *(self.0 as *mut Option<Box<dyn Fn()>>) {
            cb();
        }
    }
    unsafe fn drop_box(&self) {
        drop(Box::from_raw(self.0 as *mut Option<Box<dyn Fn()>>));
    }
}

/// A no-op audio backend that produces silence and discards output.
///
/// Useful for testing and offline processing.
pub struct NullBackend {
    config: AudioConfig,
    cb: CbSlot,
    is_running: bool,
    xruns: u32,
}

impl fmt::Debug for NullBackend {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("NullBackend")
            .field("config", &self.config)
            .field("is_running", &self.is_running)
            .field("xruns", &self.xruns)
            .finish()
    }
}

impl NullBackend {
    /// Create a new null backend with the given audio config.
    pub fn new(config: AudioConfig) -> Self {
        Self {
            config,
            cb: CbSlot::new(),
            is_running: false,
            xruns: 0,
        }
    }
}

impl AudioBackend for NullBackend {
    fn backend_type(&self) -> BackendType {
        BackendType::Null
    }
    fn config(&self) -> &AudioConfig {
        &self.config
    }
    fn config_mut(&mut self) -> &mut AudioConfig {
        &mut self.config
    }
    fn init(&mut self) -> IoResult<()> {
        Ok(())
    }
    fn start(&mut self) -> IoResult<()> {
        self.is_running = true;
        Ok(())
    }
    fn stop(&mut self) -> IoResult<()> {
        self.is_running = false;
        Ok(())
    }
    fn read(&mut self, buffer: &mut [f32]) -> IoResult<usize> {
        buffer.fill(0.0);
        Ok(buffer.len())
    }
    fn write(&mut self, buffer: &[f32]) -> IoResult<usize> {
        Ok(buffer.len())
    }
    fn xruns(&self) -> u32 {
        self.xruns
    }
    fn latency(&self) -> Duration {
        Duration::from_micros(
            (1_000_000.0 * self.config.buffer_size as f64 / self.config.sample_rate as f64) as u64,
        )
    }
    fn list_input_devices(&self) -> Vec<String> {
        vec!["Null Input".into()]
    }
    fn list_output_devices(&self) -> Vec<String> {
        vec!["Null Output".into()]
    }
}

impl IoBackend<f32> for NullBackend {
    fn set_process_callback(&self, cb: Box<dyn Fn()>) {
        unsafe {
            self.cb.set(cb);
        }
    }
    fn read(&self, channels: &mut [&mut [f32]]) -> usize {
        let n = channels.first().map(|c| c.len()).unwrap_or(0);
        for ch in channels.iter_mut() {
            ch[..n].fill(0.0);
        }
        n
    }
    fn write(&self, channels: &[&[f32]]) -> usize {
        channels.first().map(|c| c.len()).unwrap_or(0)
    }
    fn run(&self, _running: Arc<AtomicBool>) -> Result<(), String> {
        unsafe {
            self.cb.call();
        }
        Ok(())
    }
    fn stop(&self) -> Result<(), String> {
        Ok(())
    }
}

impl Drop for NullBackend {
    fn drop(&mut self) {
        unsafe {
            self.cb.drop_box();
        }
    }
}