use crate::host::HostSharedHandle;
use crate::internal_utils::UnsafeOptionCell;
use crate::plugin::{Plugin, PluginAudioProcessor, PluginBoxInner, PluginError, logging};
use crate::process::PluginAudioConfiguration;
use clap_sys::ext::log::*;
use clap_sys::plugin::clap_plugin;
use std::borrow::Cow;
use std::error::Error;
use std::ffi::{CStr, CString};
use std::fmt::{Display, Formatter};
use std::io::Write;
use std::panic::AssertUnwindSafe;
use std::pin::Pin;
use std::ptr::NonNull;
#[cfg(not(test))]
#[allow(unused)]
pub(crate) use std::panic::catch_unwind as handle_panic;
#[cfg(test)]
#[inline]
#[allow(unused)]
pub(crate) fn handle_panic<F: FnOnce() -> R, R>(f: F) -> std::thread::Result<R> {
Ok(f())
}
pub struct PluginWrapper<'a, P: Plugin> {
audio_processor: UnsafeOptionCell<P::AudioProcessor<'a>>,
main_thread: P::MainThread<'a>,
shared: Pin<Box<P::Shared<'a>>>,
host: HostSharedHandle<'a>,
}
impl<'a, P: Plugin> PluginWrapper<'a, P> {
pub(crate) unsafe fn new(
host: HostSharedHandle<'a>,
shared: Pin<Box<P::Shared<'a>>>,
main_thread: P::MainThread<'a>,
) -> Self {
Self {
host,
shared,
main_thread,
audio_processor: UnsafeOptionCell::new(),
}
}
pub(crate) unsafe fn activate(
&self,
audio_config: PluginAudioConfiguration,
) -> Result<(), PluginWrapperError> {
if self.is_active() {
return Err(PluginWrapperError::ActivatedPlugin);
}
let shared = &*(self.shared() as *const _);
let host = self.host;
let processor = P::AudioProcessor::activate(
host.as_audio_processor_unchecked(),
self.main_thread(),
shared,
audio_config,
)?;
self.audio_processor.put(processor);
Ok(())
}
pub(crate) unsafe fn deactivate(&self) -> Result<(), PluginWrapperError> {
match self.audio_processor.take() {
None => Err(PluginWrapperError::DeactivatedPlugin),
Some(audio_processor) => {
audio_processor.deactivate(self.main_thread());
Ok(())
}
}
}
#[inline]
pub fn is_active(&self) -> bool {
self.audio_processor.is_some()
}
#[inline]
pub fn shared(&self) -> &P::Shared<'a> {
&self.shared
}
#[inline]
pub unsafe fn main_thread(&self) -> &'_ P::MainThread<'a> {
&self.main_thread
}
#[inline]
pub unsafe fn audio_processor(
&self,
) -> Result<NonNull<P::AudioProcessor<'a>>, PluginWrapperError> {
self.audio_processor
.as_ptr()
.ok_or(PluginWrapperError::DeactivatedPlugin)
}
pub unsafe fn handle<T, F>(plugin: *const clap_plugin, handler: F) -> Option<T>
where
F: FnOnce(&PluginWrapper<'a, P>) -> Result<T, PluginWrapperError>,
{
match Self::from_raw(plugin).and_then(|p| Self::handle_panic(p, handler)) {
Ok(value) => Some(value),
Err(e) => {
logging::plugin_log::<P>(plugin, &e);
None
}
}
}
pub(crate) unsafe fn handle_plugin_data<T, F>(
plugin: *const clap_plugin,
handler: F,
) -> Option<T>
where
F: FnOnce(NonNull<PluginBoxInner<'a, P>>) -> Result<T, PluginWrapperError>,
{
match Self::plugin_data_from_raw(plugin).and_then(|p| Self::handle_panic(p, handler)) {
Ok(value) => Some(value),
Err(e) => {
logging::plugin_log::<P>(plugin, &e);
None
}
}
}
unsafe fn from_raw<'p>(raw: *const clap_plugin) -> Result<&'p Self, PluginWrapperError> {
raw.as_ref()
.ok_or(PluginWrapperError::NullPluginInstance)?
.plugin_data
.cast::<PluginBoxInner<'a, P>>()
.as_ref()
.ok_or(PluginWrapperError::AlreadyDestroyed)?
.wrapper()
}
unsafe fn plugin_data_from_raw(
raw: *const clap_plugin,
) -> Result<NonNull<PluginBoxInner<'a, P>>, PluginWrapperError> {
let data = raw
.as_ref()
.ok_or(PluginWrapperError::NullPluginInstance)?
.plugin_data
.cast::<PluginBoxInner<'a, P>>();
NonNull::new(data).ok_or(PluginWrapperError::AlreadyDestroyed)
}
#[inline]
fn handle_panic<Pa, T, F>(parameter: Pa, handler: F) -> Result<T, PluginWrapperError>
where
F: FnOnce(Pa) -> Result<T, PluginWrapperError>,
{
handle_panic(AssertUnwindSafe(|| handler(parameter)))
.map_err(|_| PluginWrapperError::Panic)?
}
}
unsafe impl<P: Plugin> Send for PluginWrapper<'_, P> {}
unsafe impl<P: Plugin> Sync for PluginWrapper<'_, P> {}
#[derive(Debug)]
#[non_exhaustive]
pub enum PluginWrapperError {
NullPluginInstance,
AlreadyDestroyed,
NulPtr(&'static str),
InvalidParameter(&'static str),
UninitializedPlugin,
PluginCalledDuringInitialization,
Destroying,
InitializationAlreadyFailed,
AlreadyInitialized,
ActivatedPlugin,
DeactivatedPlugin,
DeactivationRequiredForFunction(&'static str),
Panic,
Plugin(PluginError),
StringEncoding(std::str::Utf8Error),
InvalidCString(std::ffi::NulError),
ErrorFormatError(std::io::Error),
Error(clap_log_severity, Box<dyn Error>),
Message(clap_log_severity, &'static str),
}
impl PluginWrapperError {
pub fn severity(&self) -> clap_log_severity {
match self {
PluginWrapperError::Plugin(_) => CLAP_LOG_ERROR,
PluginWrapperError::Panic => CLAP_LOG_PLUGIN_MISBEHAVING,
PluginWrapperError::Error(s, _) => *s,
PluginWrapperError::Message(s, _) => *s,
_ => CLAP_LOG_HOST_MISBEHAVING,
}
}
pub fn os_error_code(&self) -> Option<i32> {
let e = match self {
PluginWrapperError::Error(_, e) => e,
_ => return None,
};
e.downcast_ref::<std::io::Error>()?.raw_os_error()
}
pub fn format_cstr(&self) -> Cow<'static, CStr> {
let mut buf = Vec::new();
match buf.write_fmt(format_args!("{}", self)) {
Ok(()) => {}
Err(e) => {
if matches!(self, PluginWrapperError::ErrorFormatError(_)) {
return c"Failed to format error".into();
}
return PluginWrapperError::ErrorFormatError(e).format_cstr();
}
};
if let Some(idx) = buf.iter().position(|&b| b == 0) {
buf.truncate(idx);
}
let Ok(str) = CString::new(buf) else {
return c"Error format failure: NulError".into();
};
str.into()
}
#[inline]
pub fn with_severity<E: 'static + Error>(
severity: clap_log_severity,
) -> impl Fn(E) -> PluginWrapperError {
move |e| PluginWrapperError::Error(severity, Box::new(e))
}
}
impl From<PluginError> for PluginWrapperError {
#[inline]
fn from(e: PluginError) -> Self {
PluginWrapperError::Plugin(e)
}
}
impl Display for PluginWrapperError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
use PluginWrapperError::*;
match self {
NullPluginInstance => {
f.write_str("Plugin method was called with null clap_plugin pointer")
}
AlreadyDestroyed => f.write_str(
"Plugin instance was already destroyed (clap_plugin.plugin_data pointer is null)",
),
PluginCalledDuringInitialization => {
f.write_str("Host tried to call plugin function during initialization")
}
InitializationAlreadyFailed => f.write_str("Plugin initialization has already failed"),
AlreadyInitialized => f.write_str("Plugin is already initialized"),
Destroying => f.write_str("Plugin is being destroyed"),
NulPtr(ptr_name) => {
write!(f, "Plugin method was called with null {ptr_name} pointer")
}
InvalidParameter(p) => {
write!(f, "Received invalid parameter '{p}'")
}
UninitializedPlugin => f.write_str("Plugin was not properly initialized before use"),
ActivatedPlugin => f.write_str("Plugin was already activated"),
DeactivatedPlugin => {
f.write_str("Plugin was not activated before calling a audio-thread method")
}
DeactivationRequiredForFunction(function) => write!(
f,
"Host attempted to call '{function}' while plugin was still active"
),
StringEncoding(e) => {
write!(
f,
"Encountered string containing invalid UTF-8 at position {}.",
e.valid_up_to()
)
}
InvalidCString(e) => {
write!(
f,
"Encountered string containing a NUL byte at position {}.",
e.nul_position()
)
}
ErrorFormatError(e) => {
write!(f, "Error while formatting plugin error: '{e}'")
}
Plugin(e) => Display::fmt(&e, f),
Error(_, e) => Display::fmt(e, f),
Message(_, msg) => f.write_str(msg),
Panic => f.write_str("Plugin panicked"),
}
}
}
impl Error for PluginWrapperError {}