use std::ffi::c_void;
use std::sync::Arc;
use arc_swap::ArcSwap;
use crate::dsp::{EqSettings, Processor};
type ProcessCb = extern "C" fn(ctx: *mut c_void, buffer: *mut f32, frames: u32, channels: u32);
#[repr(C)]
struct RawSession {
_private: [u8; 0],
}
unsafe extern "C" {
fn eqtune_default_output_device() -> u32;
fn eqtune_default_output_sample_rate() -> f64;
fn eqtune_tap_start(cb: ProcessCb, ctx: *mut c_void) -> *mut RawSession;
fn eqtune_tap_stop(session: *mut RawSession);
}
pub fn default_output_device() -> Option<u32> {
let id = unsafe { eqtune_default_output_device() };
(id != 0).then_some(id)
}
pub fn default_output_sample_rate() -> Option<f64> {
let rate = unsafe { eqtune_default_output_sample_rate() };
(rate > 0.0).then_some(rate)
}
struct AudioState {
processor: Processor,
settings: Arc<ArcSwap<EqSettings>>,
}
extern "C" fn process_trampoline(ctx: *mut c_void, buffer: *mut f32, frames: u32, channels: u32) {
if ctx.is_null() || buffer.is_null() || frames == 0 || channels == 0 {
return;
}
let state = unsafe { &mut *(ctx as *mut AudioState) };
let settings = state.settings.load_full(); let len = frames as usize * channels as usize;
let buf = unsafe { std::slice::from_raw_parts_mut(buffer, len) };
state.processor.run(&settings, buf, channels as usize);
}
#[derive(Clone)]
pub struct EqHandle(Arc<ArcSwap<EqSettings>>);
impl EqHandle {
pub fn store(&self, settings: EqSettings) {
self.0.store(Arc::new(settings));
}
}
pub struct TapSession {
raw: *mut RawSession,
_state: Box<AudioState>,
}
impl TapSession {
pub fn start(channels: usize, initial: EqSettings) -> Option<(Self, EqHandle)> {
let shared = Arc::new(ArcSwap::from_pointee(initial));
let handle = EqHandle(shared.clone());
let mut state = Box::new(AudioState { processor: Processor::new(channels), settings: shared });
let ctx = (&mut *state as *mut AudioState).cast::<c_void>();
let raw = unsafe { eqtune_tap_start(process_trampoline, ctx) };
if raw.is_null() {
None
} else {
Some((Self { raw, _state: state }, handle))
}
}
}
impl Drop for TapSession {
fn drop(&mut self) {
unsafe { eqtune_tap_stop(self.raw) };
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ffi_links_and_returns_a_device() {
assert!(default_output_device().is_some(), "expected a default output device");
}
}