use std::{
ffi::{CString, NulError},
os::raw::c_long,
ptr,
};
use crate::{
bindings::VBVMR_CBCOMMAND, interface::callback::data::RawCallbackData, CallbackCommand,
VoicemeeterRemote,
};
fn register_audio_callback<'cb, F>(
remote: &VoicemeeterRemote,
mode: &crate::AudioCallbackMode,
application: *mut std::os::raw::c_char,
callback: F,
) -> Result<*mut F, AudioCallbackRegisterError>
where
F: FnMut(CallbackCommand<'cb>, i32) -> c_long,
{
let data = Box::into_raw(Box::new(callback));
tracing::debug!("callback {:p}", data);
let res = unsafe {
remote.raw.VBVMR_AudioCallbackRegister(
mode.0,
Some(call_closure::<F>),
data as *mut _,
application,
)
};
tracing::debug!("registered application");
match res {
0 => Ok(data),
-1 => Err(AudioCallbackRegisterError::NoServer),
1 => Err(AudioCallbackRegisterError::AlreadyRegistered(unsafe {
CString::from_raw(application)
})),
s => Err(AudioCallbackRegisterError::Unexpected(s)),
}
}
unsafe extern "C" fn call_closure<'cb, F>(
user_data: *mut std::os::raw::c_void,
command: c_long,
buffer: *mut std::os::raw::c_void,
nnn: c_long,
) -> c_long
where
F: FnMut(CallbackCommand<'cb>, i32) -> c_long,
{
let callback_ptr = user_data as *mut F;
let callback = unsafe { &mut *callback_ptr };
let ptr = RawCallbackData::from_ptr(buffer);
callback(
unsafe {
CallbackCommand::new_unchecked(
crate::types::VoicemeeterApplication::PotatoX64Bits,
VBVMR_CBCOMMAND(command),
ptr,
)
},
nnn,
)
}
#[must_use = "This structure contains the raw pointer to the closure environment, if this is not returned you will leak memory"]
pub struct CallbackGuard<'a, F> {
guard: *mut F,
lt: std::marker::PhantomData<&'a ()>,
}
impl VoicemeeterRemote {
#[doc = include_str!("../../../examples/simple.rs")]
#[doc = include_str!("../../../examples/output.rs")]
#[tracing::instrument(skip(application_name, callback), fields(application_name, mode))]
pub fn audio_callback_register<'a, 'cb, 'g, F>(
&'a self,
mode: crate::AudioCallbackMode,
application_name: impl AsRef<str>,
callback: F,
) -> Result<CallbackGuard<'g, F>, AudioCallbackRegisterError>
where
F: FnMut(CallbackCommand<'cb>, i32) -> c_long + 'g,
{
let application_name = application_name.as_ref();
tracing::Span::current().record("application_name", application_name);
assert!(application_name.len() < 64);
let mut application = [b'\0'; 64];
application[0..application_name.len()].copy_from_slice(application_name.as_bytes());
let ptr = ptr::addr_of!(self.program);
tracing::info!("a: {ptr:p}");
let g = register_audio_callback(
self,
&mode,
ptr::addr_of_mut!(application) as *mut _,
callback,
)?;
Ok(CallbackGuard {
guard: g,
lt: Default::default(),
})
}
pub fn audio_callback_unregister<F>(
&self,
guard: CallbackGuard<'_, F>,
) -> Result<(), AudioCallbackUnregisterError> {
let res = unsafe { self.raw.VBVMR_AudioCallbackUnregister() };
match res {
0 => {
let _ = unsafe { Box::from_raw(guard.guard) };
Ok(())
}
-1 => Err(AudioCallbackUnregisterError::NoServer),
1 => Err(AudioCallbackUnregisterError::AlreadyUnregistered),
s => Err(AudioCallbackUnregisterError::Unexpected(s)),
}
}
pub fn audio_callback_unregister_leak<F>(&self) -> Result<(), AudioCallbackUnregisterError> {
let res = unsafe { self.raw.VBVMR_AudioCallbackUnregister() };
match res {
0 => Ok(()),
-1 => Err(AudioCallbackUnregisterError::NoServer),
1 => Err(AudioCallbackUnregisterError::AlreadyUnregistered),
s => Err(AudioCallbackUnregisterError::Unexpected(s)),
}
}
}
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum AudioCallbackRegisterError {
#[error("no server")]
NoServer,
#[error("an application `{}` is already registered", _0.to_string_lossy())]
AlreadyRegistered(CString),
#[error("could not make application name into a c-string")]
NulError(#[from] NulError),
#[error("unexpected error occurred: error code {0}")]
Unexpected(i32),
}
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum AudioCallbackUnregisterError {
#[error("no server")]
NoServer,
#[error("callback already unregistered")]
AlreadyUnregistered,
#[error("an unexpected error occurred: error code {0}")]
Unexpected(i32),
}