use std::sync::Arc;
use std::sync::atomic::AtomicU64;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum BackendError {
#[error("no output devices found")]
NoDevices,
#[error("device not found: {0}")]
DeviceNotFound(String),
#[error("unsupported sample rate: {0}")]
UnsupportedSampleRate(f64),
#[error("platform error: {0}")]
Platform(String),
#[error("stream creation failed: {0}")]
StreamCreation(String),
}
#[derive(Debug, Clone)]
pub struct DeviceInfo {
pub name: String,
pub sample_rates: Vec<f64>,
pub platform_id: u64,
}
pub trait AudioBackend: Send + Sync {
fn list_devices(&self) -> Result<Vec<DeviceInfo>, BackendError>;
fn default_device(&self) -> Result<DeviceInfo, BackendError>;
fn supported_sample_rates(&self, device: &DeviceInfo) -> Result<Vec<f64>, BackendError>;
fn get_device_sample_rate(&self, device: &DeviceInfo) -> Result<f64, BackendError>;
fn set_device_sample_rate(&self, device: &DeviceInfo, rate: f64) -> Result<(), BackendError>;
fn create_engine(
&self,
device: &DeviceInfo,
sample_rate: f64,
channels: u32,
consumer: rtrb::Consumer<f32>,
samples_played: Arc<AtomicU64>,
) -> Result<Box<dyn AudioEngineHandle>, BackendError>;
}
pub trait AudioEngineHandle: Send {
fn start(&self) -> Result<(), BackendError>;
fn stop(&self) -> Result<(), BackendError>;
fn is_running(&self) -> bool;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn device_info_construction() {
let info = DeviceInfo {
name: "Test DAC".into(),
sample_rates: vec![44100.0, 48000.0, 96000.0],
platform_id: 42,
};
assert_eq!(info.name, "Test DAC");
assert_eq!(info.sample_rates.len(), 3);
assert_eq!(info.platform_id, 42);
}
#[test]
fn backend_error_formatting() {
let err = BackendError::NoDevices;
assert_eq!(err.to_string(), "no output devices found");
let err = BackendError::DeviceNotFound("Missing".into());
assert!(err.to_string().contains("Missing"));
let err = BackendError::UnsupportedSampleRate(192000.0);
assert!(err.to_string().contains("192000"));
}
#[test]
fn platform_backend_constructs() {
let _backend = super::super::platform_backend();
}
#[test]
fn platform_backend_lists_devices() {
let backend = super::super::platform_backend();
let result = backend.list_devices();
assert!(result.is_ok());
}
#[test]
fn platform_backend_has_default_device() {
let backend = super::super::platform_backend();
let _ = backend.default_device();
}
#[test]
fn engine_create_with_ring_buffer() {
let backend = super::super::platform_backend();
let device = match backend.default_device() {
Ok(d) => d,
Err(_) => return, };
let (producer, consumer) = rtrb::RingBuffer::new(4096);
let samples_played = Arc::new(AtomicU64::new(0));
let rate = device.sample_rates.first().copied().unwrap_or(44100.0);
let engine = backend.create_engine(&device, rate, 2, consumer, samples_played);
if let Ok(engine) = engine {
assert!(!engine.is_running());
drop(engine);
}
drop(producer); }
}