pub mod shared;
pub use shared::{
ClapParamPayload, ColdShared, NamClapShared, NamClapSharedRef, NamModelMetadata, PendingModel,
RENDER_MODE_OFFLINE, RENDER_MODE_REALTIME, RtToUi, UiToRt,
};
mod main_thread;
pub use main_thread::{NamClapMainThread, debug_assert_main_thread};
use crate::clap::descriptor::nam_descriptor;
use crate::clap::processor::NamClapProcessor;
use crate::common::diagnostics::SystemSnapshot;
use crate::common::params::NamPluginParams;
use crate::common::spsc::{GcOverflowBuffer, RtStatusFlags};
use clack_plugin::prelude::*;
use rtrb::RingBuffer;
use std::ffi::CString;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, Mutex};
pub struct NamClapPlugin;
impl Plugin for NamClapPlugin {
type AudioProcessor<'a> = NamClapProcessor<'a>;
type Shared<'a> = NamClapShared;
type MainThread<'a> = NamClapMainThread<'a>;
fn declare_extensions(
builder: &mut PluginExtensions<Self>,
_shared: Option<&Self::Shared<'_>>,
) {
builder.register::<clack_extensions::audio_ports::PluginAudioPorts>();
builder.register::<clack_extensions::audio_ports_activation::PluginAudioPortsActivation>();
builder.register::<clack_extensions::params::PluginParams>();
builder.register::<clack_extensions::state::PluginState>();
builder.register::<crate::clap::extensions::latency::NamPluginLatency>();
builder.register::<crate::clap::extensions::track_info::NamPluginTrackInfo>();
builder.register::<crate::clap::extensions::remote_controls::NamPluginRemoteControls>();
builder.register::<crate::clap::extensions::param_indication::NamPluginParamIndication>();
builder.register::<clack_extensions::preset_discovery::PluginPresetLoad>();
builder.register::<crate::clap::extensions::render::NamPluginRender>();
builder.register::<crate::clap::extensions::state_context::NamPluginStateContext>();
builder.register::<crate::clap::extensions::tail::NamPluginTail>();
#[cfg(feature = "clap-plugin")]
builder.register::<crate::clap::extensions::gui::NamPluginGui>();
}
}
impl DefaultPluginFactory for NamClapPlugin {
fn get_descriptor() -> PluginDescriptor {
nam_descriptor()
}
fn new_shared(_host: HostSharedHandle<'_>) -> Result<Self::Shared<'_>, PluginError> {
static INIT_PANIC_HOOK: std::sync::OnceLock<()> = std::sync::OnceLock::new();
INIT_PANIC_HOOK.get_or_init(|| {
crate::common::panic_hook::install_panic_hook("clap");
});
let (param_tx, param_rx) = RingBuffer::new(8);
let (gc_tx, gc_rx) = RingBuffer::new(32); let (slimmable_tx, slimmable_rx) = RingBuffer::new(4);
let dialog_state = Some(Arc::new(
crate::clap::gui::ui::zones::dialog_state::DialogSharedState {
pending_model: Mutex::new(None),
loading: std::sync::atomic::AtomicBool::new(false),
},
));
let ir_dialog_state = Some(Arc::new(
crate::clap::gui::ui::zones::dialog_state::IrDialogSharedState {
pending_ir: Mutex::new(None),
ir_loading: std::sync::atomic::AtomicBool::new(false),
},
));
Ok(NamClapShared {
rt_to_ui: RtToUi {
ui_peak_l: AtomicU32::new(0.0f32.to_bits()),
ui_peak_r: AtomicU32::new(0.0f32.to_bits()),
ui_clipped: std::sync::atomic::AtomicBool::new(false),
current_latency: AtomicU32::new(0),
cabsim_tail_samples: AtomicU32::new(0),
active_channel_count: AtomicU32::new(1),
},
ui_to_rt: UiToRt {
param_input_gain: AtomicU32::new(0.0f32.to_bits()),
param_output_gain: AtomicU32::new(0.0f32.to_bits()),
param_gate_thresh: AtomicU32::new((-70.0f32).to_bits()),
param_bypass: AtomicU32::new(0),
param_adaptive_compute: AtomicU32::new(1), param_slim_override: AtomicU32::new(0), param_oversample: AtomicU32::new(0), param_activation: AtomicU32::new(1), gesture_flags: AtomicU32::new(0),
gui_param_generation: AtomicU32::new(0),
host_r_deactivated: std::sync::atomic::AtomicBool::new(false),
},
cold: ColdShared {
param_tx: Mutex::new(Some(param_tx)),
param_rx: Mutex::new(Some(param_rx)),
gc_tx: Mutex::new(Some(gc_tx)),
gc_rx: Mutex::new(Some(gc_rx)),
gc_overflow: Arc::new(GcOverflowBuffer::new(crate::common::spsc::SPSC_CAPACITY)),
rt_status: Arc::new(RtStatusFlags::new()),
model_sample_rate: AtomicU32::new(48000),
sample_rate: AtomicU32::new(0),
buffer_size: AtomicU32::new(0),
track_accent_color: AtomicU32::new(0),
param_indication: [
std::sync::atomic::AtomicU8::new(0),
std::sync::atomic::AtomicU8::new(0),
std::sync::atomic::AtomicU8::new(0),
std::sync::atomic::AtomicU8::new(0),
std::sync::atomic::AtomicU8::new(0),
std::sync::atomic::AtomicU8::new(0),
std::sync::atomic::AtomicU8::new(0),
std::sync::atomic::AtomicU8::new(0),
std::sync::atomic::AtomicU8::new(0),
],
param_indication_color: [
std::sync::atomic::AtomicU32::new(0),
std::sync::atomic::AtomicU32::new(0),
std::sync::atomic::AtomicU32::new(0),
std::sync::atomic::AtomicU32::new(0),
std::sync::atomic::AtomicU32::new(0),
std::sync::atomic::AtomicU32::new(0),
std::sync::atomic::AtomicU32::new(0),
std::sync::atomic::AtomicU32::new(0),
std::sync::atomic::AtomicU32::new(0),
],
model_load_counter: AtomicU32::new(0),
ui_model_name: Mutex::new(String::new()),
ui_model_metadata: Mutex::new(None),
ui_pending_model: Mutex::new(None),
ui_loading: std::sync::atomic::AtomicBool::new(false),
ui_load_error: std::sync::atomic::AtomicBool::new(false),
ui_load_error_msg: Mutex::new(String::new()),
ui_model_info: Mutex::new(None),
alive_fence: Arc::new(std::sync::atomic::AtomicBool::new(true)),
render_mode: AtomicU32::new(0),
gui_scale_factor: AtomicU32::new(0),
ir_path: Mutex::new(None),
ui_pending_ir: Mutex::new(None),
ui_ir_loading: std::sync::atomic::AtomicBool::new(false),
ui_ir_load_error: std::sync::atomic::AtomicBool::new(false),
ui_ir_load_error_msg: Mutex::new(String::new()),
ui_clear_ir: std::sync::atomic::AtomicBool::new(false),
ir_raw_samples: Mutex::new(None),
slimmable_tx: Mutex::new(Some(slimmable_tx)),
slimmable_rx: Mutex::new(Some(slimmable_rx)),
full_wavenet_model: Mutex::new(None),
pending_model: Mutex::new(None),
dialog_state: dialog_state.clone(),
ir_dialog_state: ir_dialog_state.clone(),
dialog_handle_sink: Mutex::new(None),
ir_dialog_handle_sink: Mutex::new(None),
host_log_sink: Mutex::new(None),
},
})
}
fn new_main_thread<'a>(
mut host: HostMainThreadHandle<'a>,
shared: &'a Self::Shared<'a>,
) -> Result<Self::MainThread<'a>, PluginError> {
if let Some(track_info_ext) =
host.get_extension::<clack_extensions::track_info::HostTrackInfo>()
{
let mut buffer = clack_extensions::track_info::TrackInfoBuffer::new();
if let Some(color) = track_info_ext
.get(&mut host, &mut buffer)
.and_then(|info| info.color())
{
let packed = crate::clap::extensions::track_info::pack_argb(
color.alpha,
color.red,
color.green,
color.blue,
);
shared
.cold
.track_accent_color
.store(packed, Ordering::Relaxed);
}
}
let param_tx = shared
.cold
.param_tx
.lock()
.unwrap_or_else(|e| e.into_inner())
.take()
.ok_or(PluginError::Message("param_tx producer already taken"))?;
let gc_rx = shared
.cold
.gc_rx
.lock()
.unwrap_or_else(|e| e.into_inner())
.take()
.ok_or(PluginError::Message("gc_rx consumer already taken"))?;
let slimmable_tx = shared
.cold
.slimmable_tx
.lock()
.unwrap_or_else(|e| e.into_inner())
.take()
.ok_or(PluginError::Message("slimmable_tx producer already taken"))?;
{
use crate::common::diagnostics::logger::{HostLogFn, NamLogger};
use clack_extensions::log::{HostLog, LogSeverity};
let _ = NamLogger::init_plugin(log::LevelFilter::Info);
if let Some(host_log) = host.get_extension::<HostLog>() {
let handle_copy = *host; let host_nn: std::ptr::NonNull<()> =
unsafe { std::mem::transmute_copy(&handle_copy) };
let host_addr = host_nn.as_ptr() as usize;
let sink: Arc<HostLogFn> = Arc::new(move |severity_str, msg| {
let severity = match severity_str {
"ERROR" => LogSeverity::Error,
"WARN" => LogSeverity::Warning,
"INFO" => LogSeverity::Info,
"DEBUG" => LogSeverity::Debug,
_ => LogSeverity::Info,
};
let cmsg = CString::new(msg).unwrap_or_default();
let ptr = host_addr as *mut ();
let nn = unsafe { std::ptr::NonNull::new_unchecked(ptr) };
let host_shared: HostSharedHandle<'static> =
unsafe { std::mem::transmute::<std::ptr::NonNull<()>, _>(nn) };
host_log.log(&host_shared, severity, &cmsg);
});
if let Some(nl) = NamLogger::global() {
nl.register_sink(&sink);
}
if let Ok(mut guard) = shared.cold.host_log_sink.lock() {
*guard = Some(sink);
}
}
}
#[cfg_attr(test, allow(unused_mut, clippy::allow_attributes))]
let main_thread = NamClapMainThread {
shared,
params: NamPluginParams::default(),
host,
sys: SystemSnapshot::capture(),
param_tx,
gc_rx,
slimmable_tx,
last_reported_latency: 0,
last_reported_cabsim_tail: 0,
#[cfg(feature = "clap-plugin")]
window_handle: None,
#[cfg(feature = "clap-plugin")]
floating_thread_handle: None,
#[cfg(feature = "clap-plugin")]
floating_close_signal: None,
#[cfg(feature = "clap-plugin")]
dialog_handle: None,
#[cfg(feature = "clap-plugin")]
dialog_state: shared.cold.dialog_state.clone(),
#[cfg(feature = "clap-plugin")]
ir_dialog_handle: None,
#[cfg(feature = "clap-plugin")]
ir_dialog_state: shared.cold.ir_dialog_state.clone(),
};
let host_name = main_thread
.host
.name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| String::from("Unknown"));
let negotiated_clap = main_thread.host.clap_version();
log::info!(
"NAM-rs plugin instance created in host \"{host_name}\" (negotiated CLAP API {negotiated_clap})",
);
Ok(main_thread)
}
}
#[cfg(test)]
pub(crate) use shared::make_test_shared;
#[cfg(test)]
#[path = "../plugin_test.rs"]
mod plugin_test;