use std::sync::atomic::AtomicBool;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use crate::queues::{SpscQueue, TelemetryBlock};
use crate::time::ClockTick;
pub type IoResult<T> = Result<T, String>;
pub trait IoControl {
fn write_data(&self, data: &[u8]) -> usize;
}
pub trait IoDriver: Send + Sync {
fn set_callback(&self, cb: Box<dyn FnMut(&ClockTick)>);
fn run(&self, running: Arc<AtomicBool>) -> IoResult<()>;
fn stop(&self) -> IoResult<()>;
fn as_control(&self) -> Option<&dyn IoControl> {
None
}
}
pub trait IoCapture: Send + Sync {
fn read_input(&self, channel: usize, dst: &mut [f32]) -> usize;
fn num_input_channels(&self) -> usize;
}
pub trait IoPlayback: Send + Sync {
fn write_output(&self, channel: usize, src: &[f32]) -> usize;
fn num_output_channels(&self) -> usize;
}
pub struct NullBackend {
channels: usize,
}
impl NullBackend {
pub fn new(channels: usize) -> Self {
Self { channels }
}
}
impl IoCapture for NullBackend {
fn read_input(&self, _channel: usize, dst: &mut [f32]) -> usize {
dst.fill(0.0);
dst.len()
}
fn num_input_channels(&self) -> usize {
self.channels
}
}
impl IoPlayback for NullBackend {
fn write_output(&self, _channel: usize, _src: &[f32]) -> usize {
_src.len()
}
fn num_output_channels(&self) -> usize {
self.channels
}
}
pub struct SpmcPlayback<T: crate::math::Transcendental, const BUF: usize, const CAP: usize> {
queue: Arc<SpscQueue<TelemetryBlock<T, BUF>, CAP>>,
channels: usize,
sample_rate: f32,
sample_pos: AtomicU64,
}
impl<T: crate::math::Transcendental, const BUF: usize, const CAP: usize> SpmcPlayback<T, BUF, CAP> {
pub fn new(
queue: Arc<SpscQueue<TelemetryBlock<T, BUF>, CAP>>,
channels: usize,
sample_rate: f32,
) -> Self {
Self {
queue,
channels,
sample_rate,
sample_pos: AtomicU64::new(0),
}
}
pub fn queue(&self) -> &Arc<SpscQueue<TelemetryBlock<T, BUF>, CAP>> {
&self.queue
}
}
impl IoPlayback for SpmcPlayback<f32, 256, 64> {
fn write_output(&self, channel: usize, src: &[f32]) -> usize {
let n = src.len();
if n == 0 {
return 0;
}
let pos = self.sample_pos.fetch_add(n as u64, Ordering::Relaxed);
let mut block = TelemetryBlock::default();
let limit = n.min(256);
block.data[..limit].copy_from_slice(&src[..limit]);
block.channel = channel as u32;
block.sample_rate = self.sample_rate;
block.block_index = pos;
block.timestamp = pos;
block.compute_metrics();
let _ = self.queue.push(block);
limit
}
fn num_output_channels(&self) -> usize {
self.channels
}
}
pub trait IoBackend: IoDriver {}
impl<T: IoDriver> IoBackend for T {}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
struct TestBackend {
reg: AtomicU8,
}
impl IoDriver for TestBackend {
fn set_callback(&self, _cb: Box<dyn FnMut(&ClockTick)>) {}
fn run(&self, _: Arc<AtomicBool>) -> IoResult<()> {
Ok(())
}
fn stop(&self) -> IoResult<()> {
Ok(())
}
fn as_control(&self) -> Option<&dyn IoControl> {
Some(self)
}
}
impl IoControl for TestBackend {
fn write_data(&self, data: &[u8]) -> usize {
if let Some(&v) = data.first() {
self.reg.store(v, Ordering::Relaxed);
}
1
}
}
#[test]
fn test_iocontrol_write_data() {
let b = TestBackend {
reg: AtomicU8::new(0),
};
let ctrl = b.as_control().unwrap();
ctrl.write_data(&[42]);
assert_eq!(b.reg.load(Ordering::Relaxed), 42);
}
#[test]
fn test_iocontrol_default_returns_none() {
struct NoControl;
impl IoDriver for NoControl {
fn set_callback(&self, _cb: Box<dyn FnMut(&ClockTick)>) {}
fn run(&self, _: Arc<AtomicBool>) -> IoResult<()> {
Ok(())
}
fn stop(&self) -> IoResult<()> {
Ok(())
}
}
let b = NoControl;
assert!(b.as_control().is_none());
}
}