use crate::fourcc::FourCharCode;
pub type Sample = f32;
#[derive(Copy, Clone, PartialEq, Debug, Default)]
pub struct Timestamp {
pub sample_time: f64,
pub host_time: u64,
}
impl Timestamp {
#[inline]
#[must_use]
pub const fn new(sample_time: f64, host_time: u64) -> Self {
Self {
sample_time,
host_time,
}
}
pub const ZERO: Self = Self {
sample_time: 0.0,
host_time: 0,
};
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[repr(transparent)]
pub struct IoOperation(pub FourCharCode);
impl IoOperation {
pub const THREAD: Self = Self(FourCharCode::new(*b"thrd"));
pub const CYCLE: Self = Self(FourCharCode::new(*b"cycl"));
pub const READ_INPUT: Self = Self(FourCharCode::new(*b"read"));
pub const CONVERT_INPUT: Self = Self(FourCharCode::new(*b"cinp"));
pub const PROCESS_INPUT: Self = Self(FourCharCode::new(*b"pcin"));
pub const PROCESS_OUTPUT: Self = Self(FourCharCode::new(*b"pcou"));
pub const MIX_OUTPUT: Self = Self(FourCharCode::new(*b"mixo"));
pub const PROCESS_MIX: Self = Self(FourCharCode::new(*b"pcmx"));
pub const CONVERT_MIX: Self = Self(FourCharCode::new(*b"cmix"));
pub const WRITE_MIX: Self = Self(FourCharCode::new(*b"wmix"));
#[inline]
#[must_use]
pub const fn code(self) -> FourCharCode {
self.0
}
#[inline]
#[must_use]
pub fn is_processing(self) -> bool {
matches!(
self,
Self::PROCESS_INPUT | Self::PROCESS_OUTPUT | Self::PROCESS_MIX
)
}
}
impl From<FourCharCode> for IoOperation {
#[inline]
fn from(code: FourCharCode) -> Self {
Self(code)
}
}
#[derive(Debug)]
pub struct IoBuffer<'a> {
pub timestamp: Timestamp,
pub operation: IoOperation,
pub input: &'a [Sample],
pub output: &'a mut [Sample],
}
impl<'a> IoBuffer<'a> {
#[inline]
#[must_use]
pub fn new(
timestamp: Timestamp,
operation: IoOperation,
input: &'a [Sample],
output: &'a mut [Sample],
) -> Self {
Self {
timestamp,
operation,
input,
output,
}
}
#[inline]
#[must_use]
pub fn input_len(&self) -> usize {
self.input.len()
}
#[inline]
#[must_use]
pub fn output_len(&self) -> usize {
self.output.len()
}
#[inline]
pub fn silence_output(&mut self) {
self.output.fill(0.0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn timestamp_zero_is_origin() {
assert_eq!(Timestamp::ZERO.sample_time, 0.0);
assert_eq!(Timestamp::ZERO.host_time, 0);
assert_eq!(Timestamp::ZERO, Timestamp::new(0.0, 0));
}
#[test]
fn timestamp_default_is_zero() {
assert_eq!(Timestamp::default(), Timestamp::ZERO);
}
#[test]
fn io_operation_constants_match_core_audio_codes() {
assert_eq!(IoOperation::READ_INPUT.code(), FourCharCode::new(*b"read"));
assert_eq!(
IoOperation::PROCESS_OUTPUT.code(),
FourCharCode::new(*b"pcou")
);
assert_eq!(IoOperation::WRITE_MIX.code(), FourCharCode::new(*b"wmix"));
}
#[test]
fn only_process_operations_are_processing() {
assert!(IoOperation::PROCESS_INPUT.is_processing());
assert!(IoOperation::PROCESS_OUTPUT.is_processing());
assert!(IoOperation::PROCESS_MIX.is_processing());
assert!(!IoOperation::READ_INPUT.is_processing());
assert!(!IoOperation::WRITE_MIX.is_processing());
assert!(!IoOperation::CYCLE.is_processing());
assert!(!IoOperation::THREAD.is_processing());
}
#[test]
fn io_buffer_reports_slice_lengths() {
let input = [0.1_f32, 0.2, 0.3, 0.4];
let mut output = [0.0_f32; 4];
let buf = IoBuffer::new(
Timestamp::ZERO,
IoOperation::PROCESS_OUTPUT,
&input,
&mut output,
);
assert_eq!(buf.input_len(), 4);
assert_eq!(buf.output_len(), 4);
}
#[test]
fn silence_output_zeroes_the_buffer() {
let input: [f32; 0] = [];
let mut output = [0.5_f32; 8];
let mut buf = IoBuffer::new(
Timestamp::ZERO,
IoOperation::PROCESS_OUTPUT,
&input,
&mut output,
);
buf.silence_output();
assert!(buf.output.iter().all(|&s| s == 0.0));
}
#[test]
fn output_only_device_sees_empty_input() {
let input: [f32; 0] = [];
let mut output = [0.0_f32; 4];
let buf = IoBuffer::new(
Timestamp::new(128.0, 999),
IoOperation::PROCESS_OUTPUT,
&input,
&mut output,
);
assert_eq!(buf.input_len(), 0);
assert_eq!(buf.output_len(), 4);
assert_eq!(buf.timestamp.sample_time, 128.0);
}
#[test]
fn io_operation_layout_is_transparent() {
use core::mem::size_of;
assert_eq!(size_of::<IoOperation>(), size_of::<u32>());
}
}