mod housekeeping;
mod load;
mod logging;
use super::shared::{ClapParamPayload, NamClapShared, PendingModel};
use crate::common::diagnostics::SystemSnapshot;
use crate::common::params::NamPluginParams;
use crate::common::spsc::{self, GcItem};
use crate::models::{NamModel, StaticModel};
use clack_plugin::prelude::*;
use rtrb::{Consumer, Producer};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
pub struct NamClapMainThread<'a> {
pub(crate) shared: &'a NamClapShared,
pub params: NamPluginParams,
pub host: HostMainThreadHandle<'a>,
pub sys: SystemSnapshot,
pub param_tx: Producer<ClapParamPayload>,
pub gc_rx: Consumer<GcItem>,
pub slimmable_tx: Producer<Option<Box<StaticModel>>>,
pub last_reported_latency: u32,
pub last_reported_cabsim_tail: u32,
#[cfg(feature = "clap-plugin")]
pub window_handle: Option<baseview::WindowHandle>,
#[cfg(feature = "clap-plugin")]
pub floating_thread_handle: Option<std::thread::JoinHandle<()>>,
#[cfg(feature = "clap-plugin")]
pub floating_close_signal: Option<Arc<AtomicBool>>,
#[cfg(feature = "clap-plugin")]
#[expect(dead_code, reason = "held for join on teardown, never read directly")]
pub(crate) dialog_handle: Option<std::thread::JoinHandle<()>>,
#[cfg(feature = "clap-plugin")]
#[expect(dead_code, reason = "held for Arc lifecycle, never read directly")]
pub(crate) dialog_state:
Option<Arc<crate::clap::gui::ui::zones::dialog_state::DialogSharedState>>,
#[cfg(feature = "clap-plugin")]
#[expect(dead_code, reason = "held for join on teardown, never read directly")]
pub(crate) ir_dialog_handle: Option<std::thread::JoinHandle<()>>,
#[cfg(feature = "clap-plugin")]
#[expect(dead_code, reason = "held for Arc lifecycle, never read directly")]
pub(crate) ir_dialog_state:
Option<Arc<crate::clap::gui::ui::zones::dialog_state::IrDialogSharedState>>,
}
impl<'a> NamClapMainThread<'a> {
pub fn flush_pending_model(&mut self) -> Result<(), PluginError> {
let pending = if let Ok(mut guard) = self.shared.cold.pending_model.lock() {
guard.take()
} else {
return Ok(());
};
let Some(p) = pending else { return Ok(()) };
let buffer_size = self.shared.cold.buffer_size.load(Ordering::Relaxed) as usize;
if buffer_size == 0 {
if let Ok(mut guard) = self.shared.cold.pending_model.lock() {
*guard = Some(p);
}
return Ok(());
}
let PendingModel {
model: mut model_l,
resampler: new_resampler,
input_mult_adj,
output_mult_adj,
} = p;
if let Some(ref mut model) = model_l
&& let Err(e) = model.set_max_buffer_size(buffer_size)
{
self.shared
.cold
.rt_status
.set_flag(spsc::RT_STATUS_MODEL_LOAD_FAILED);
return Err(PluginError::Message(Box::leak(
format!(
"Failed to resize deferred model buffers for host buffer size ({}): {}",
buffer_size, e
)
.into_boxed_str(),
)));
}
match self.param_tx.push(ClapParamPayload::LoadModel {
model_l,
new_resampler,
input_mult_adj,
output_mult_adj,
}) {
Ok(()) => Ok(()),
Err(_) => {
self.shared
.cold
.rt_status
.set_flag(spsc::RT_STATUS_MODEL_LOAD_FAILED);
Err(PluginError::Message(
"Failed to send deferred model: SPSC channel full",
))
}
}
}
pub(crate) fn snapshot_params(&mut self) {
self.params.input_gain_db = f32::from_bits(
self.shared
.ui_to_rt
.param_input_gain
.load(std::sync::atomic::Ordering::Relaxed),
);
self.params.output_gain_db = f32::from_bits(
self.shared
.ui_to_rt
.param_output_gain
.load(std::sync::atomic::Ordering::Relaxed),
);
self.params.gate_threshold_db = f32::from_bits(
self.shared
.ui_to_rt
.param_gate_thresh
.load(std::sync::atomic::Ordering::Relaxed),
);
self.params.bypass = super::super::extensions::params::bypass_u32_to_bool(
self.shared
.ui_to_rt
.param_bypass
.load(std::sync::atomic::Ordering::Relaxed),
);
self.params.adaptive_compute = crate::common::params::AdaptiveComputeMode::from_f32(
self.shared
.ui_to_rt
.param_adaptive_compute
.load(std::sync::atomic::Ordering::Relaxed) as f32,
);
self.params.slim_override = crate::dsp::adaptive::SlimOverride::from_f32(
self.shared
.ui_to_rt
.param_slim_override
.load(std::sync::atomic::Ordering::Relaxed) as f32,
);
self.params.oversample = crate::dsp::oversample::OversampleFactor::from_f32(
self.shared
.ui_to_rt
.param_oversample
.load(std::sync::atomic::Ordering::Relaxed) as f32,
);
self.params.activation_precision = crate::common::params::ActivationPrecision::from_f32(
self.shared
.ui_to_rt
.param_activation
.load(std::sync::atomic::Ordering::Relaxed) as f32,
);
#[cfg(any(feature = "standalone", feature = "clap-plugin", test))]
{
if let Ok(ir_guard) = self.shared.cold.ir_path.lock() {
self.params.ir_path = ir_guard.as_ref().map(std::path::PathBuf::from);
}
}
}
pub(crate) fn drain_gc_final(&mut self) {
use crate::common::spsc::drain_gc_channels;
let drained = drain_gc_channels(
&mut self.gc_rx,
&self.shared.cold.gc_overflow,
&self.shared.cold.rt_status,
);
if drained > 0 {
log::debug!(
"NAM-rs: GC drain final — {} item(s) liberados no destroy (R11)",
drained
);
}
let _ = drain_gc_channels(
&mut self.gc_rx,
&self.shared.cold.gc_overflow,
&self.shared.cold.rt_status,
);
}
}
impl<'a> Drop for NamClapMainThread<'a> {
fn drop(&mut self) {
log::info!("NAM-rs: Plugin instance destroying — draining GC.");
self.drain_gc_final();
}
}
impl<'a> PluginMainThread<'a, NamClapShared> for NamClapMainThread<'a> {
fn on_main_thread(&mut self) {
if !self.shared.cold.alive_fence.load(Ordering::Relaxed) {
self.drain_gc_final();
return;
}
self.housekeeping();
self.emit_pending_logs();
}
}
pub fn debug_assert_main_thread(host: &HostMainThreadHandle) {
if let Some(check) = host
.shared()
.get_extension::<clack_extensions::thread_check::HostThreadCheck>()
{
debug_assert!(
check.is_main_thread(&host.shared()).unwrap_or(true),
"CLAP method called from a thread other than the main thread"
);
}
}