use std::ffi::c_void;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
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_low_power_enabled() -> bool;
fn eqtune_default_output_device_running() -> bool;
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)
}
pub fn low_power_enabled() -> bool {
unsafe { eqtune_low_power_enabled() }
}
pub fn default_output_device_running() -> bool {
unsafe { eqtune_default_output_device_running() }
}
#[derive(Default)]
struct AudioActivity {
silent_frames: AtomicU64,
}
struct AudioState {
processor: Processor,
settings: Arc<ArcSwap<EqSettings>>,
activity: Arc<AudioActivity>,
}
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(); let len = frames as usize * channels as usize;
let buf = unsafe { std::slice::from_raw_parts_mut(buffer, len) };
if crate::dsp::block_is_silent(buf) {
state
.activity
.silent_frames
.fetch_add(frames as u64, Ordering::Relaxed);
} else {
state.activity.silent_frames.store(0, Ordering::Relaxed);
}
state.processor.run(&settings, buf, channels as usize);
}
#[derive(Clone)]
pub struct EqHandle {
settings: Arc<ArcSwap<EqSettings>>,
activity: Arc<AudioActivity>,
}
impl EqHandle {
pub fn store(&self, settings: EqSettings) {
self.settings.store(Arc::new(settings));
}
pub fn silent_frames(&self) -> u64 {
self.activity.silent_frames.load(Ordering::Relaxed)
}
}
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 activity = Arc::new(AudioActivity::default());
let handle = EqHandle {
settings: shared.clone(),
activity: activity.clone(),
};
let mut state = Box::new(AudioState {
processor: Processor::new(channels),
settings: shared,
activity,
});
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_is_callable() {
let _ = default_output_device();
let _ = default_output_sample_rate();
let _ = low_power_enabled();
let _ = default_output_device_running();
}
}