#![warn(missing_docs)]
use nam_rs::diagnostics::{SystemSnapshot, logger::NamLogger};
use nam_rs::dsp::cabsim::conv::ConvEngine;
use nam_rs::dsp::cabsim::loader::CabSimIr;
use nam_rs::math::activations::set_activation_precision;
use nam_rs::models::StaticModel;
use nam_rs::models::slimmable::clone_wavenet_for_slimmable_storage;
use nam_rs::standalone::{cli, colors::Colorize, pw_host, rt_setup};
use nam_rs::{loader, spsc, spsc::ParamPayload};
use std::sync::atomic::Ordering;
fn main() -> anyhow::Result<()> {
nam_rs::common::panic_hook::install_panic_hook("standalone");
let level_filter = std::env::var("RUST_LOG")
.or_else(|_| std::env::var("NAM_LOG_LEVEL"))
.unwrap_or_else(|_| "info".to_string())
.parse::<log::LevelFilter>()
.unwrap_or(log::LevelFilter::Info);
NamLogger::init_standalone(level_filter).expect("Failed to initialize NamLogger backend");
#[cfg(feature = "testing")]
if std::env::var("NAM_DISABLE_GATE").is_ok() {
nam_rs::dsp::pipeline::DISABLE_GATE.store(true, Ordering::Relaxed);
log::info!("⚡ Noise gate disabled via NAM_DISABLE_GATE environment variable.");
}
let args = cli::parse_args();
if args.diagnose || args.diagnose_full {
let bundle = nam_rs::diagnostics::DiagnosticBundle::capture().with_full(args.diagnose_full);
println!("{}", bundle.render());
std::process::exit(0);
}
let model_path = args.model_path;
let initial_in_gain = args.input_gain;
let initial_out_gain = args.output_gain;
let buffer_size = args.buffer_size;
let sys = SystemSnapshot::capture();
log::info!(
"🎸 {}",
format!(
"NAM-rs Standalone v{} [x86-64-v3] — Neural Amp Modeler",
sys.version
)
.bright_green()
.bold()
);
pipewire::init();
rt_setup::calibrate_tsc();
extern "C" fn sigint_handler(_sig: libc::c_int) {
if spsc::SHUTDOWN.load(Ordering::Acquire) {
unsafe { libc::_exit(1) };
}
spsc::SHUTDOWN.store(true, Ordering::Release); }
unsafe {
let mut sa: libc::sigaction = std::mem::zeroed();
sa.sa_sigaction = sigint_handler as *const () as libc::sighandler_t;
sa.sa_flags = libc::SA_RESTART;
libc::sigaction(libc::SIGINT, &sa, std::ptr::null_mut());
}
let channels = spsc::setup_spsc(spsc::SPSC_CAPACITY);
let mut producer = channels.param_producer;
let consumer = channels.param_consumer;
let gc_producer = channels.gc_producer;
let gc_consumer = channels.gc_consumer;
let gc_overflow = channels.gc_overflow;
let resampler_producer = channels.resampler_producer;
let resampler_consumer = channels.resampler_consumer;
let mut cabsim_producer = channels.cabsim_producer;
let cabsim_consumer = channels.cabsim_consumer;
let slimmable_producer = channels.slimmable_producer;
let slimmable_consumer = channels.slimmable_consumer;
let os_producer = channels.os_producer;
let os_consumer = channels.os_consumer;
let rt_status = channels.rt_status;
let mut ir_raw_samples = None;
if let Some(ref cab_path) = args.cab_path {
let target_rate = nam_rs::diagnostics::ACTIVE_SAMPLE_RATE
.load(Ordering::Relaxed)
.max(48000);
let partition_size = if buffer_size > 0 {
buffer_size as usize
} else {
256
};
match CabSimIr::load(cab_path, target_rate, true) {
Ok(cabsim) => {
let engine = ConvEngine::new(&cabsim.samples, partition_size)
.map_err(|e| anyhow::anyhow!("Cab-sim engine init: {e}"))?;
log::info!(
"{} Cab-sim IR loaded: {} ({} partitions, FFT={})",
"🎛️".cyan(),
cab_path.display(),
engine.num_partitions(),
engine.fft_size(),
);
ir_raw_samples = Some(cabsim.samples);
let _ = cabsim_producer.push(Some(Box::new(engine)));
}
Err(e) => {
log::warn!(
"{} Cab-sim IR load failed: {} — continuing without cab-sim",
"⚠️".yellow(),
e
);
}
}
}
let mut full_wavenet_model: Option<Box<StaticModel>> = None;
let mut model_architecture = String::new();
if let Some(ref path) = model_path {
log::info!("{} Loading model...", "📂".cyan());
match loader::load_and_build_model(path, &sys, true, loader::LoadOptions::default()) {
Ok(loaded) => {
if let Ok(mut name) = nam_rs::diagnostics::ACTIVE_MODEL_NAME.write() {
*name = path.to_string_lossy().into_owned();
}
nam_rs::diagnostics::ACTIVE_SAMPLE_RATE
.store(loaded.sample_rate, Ordering::Relaxed);
model_architecture = loaded.architecture.clone();
let model_info = loaded.model_info(path);
if let Ok(mut info_guard) = nam_rs::diagnostics::ACTIVE_MODEL_INFO.write() {
*info_guard = Some(model_info);
}
full_wavenet_model = loaded.model_l.as_ref().and_then(|m| {
if let StaticModel::WavenetDyn(w) = m.as_ref() {
clone_wavenet_for_slimmable_storage(w).ok()
} else {
None
}
});
let _ = producer.push(ParamPayload::LoadModel {
model_l: loaded.model_l,
model_r: loaded.model_r,
input_mult_adj: loaded.input_mult_adj,
output_mult_adj: loaded.output_mult_adj,
sample_rate: loaded.sample_rate,
});
}
Err(e) => cli::exit_with_error(format!("Model load failed: {}", e)),
}
}
if initial_in_gain != 0.0 {
let _ = producer.push(ParamPayload::InputGain(
nam_rs::math::dsp::gain_lut::get_gain_lut().db_to_linear(initial_in_gain),
));
}
if initial_out_gain != 0.0 {
let _ = producer.push(ParamPayload::OutputGain(
nam_rs::math::dsp::gain_lut::get_gain_lut().db_to_linear(initial_out_gain),
));
}
let _ = producer.push(ParamPayload::SlimOverride(args.slim_override));
let _ = producer.push(ParamPayload::SetOversample(args.oversample));
if let Some(activation) = args.activation {
set_activation_precision(activation);
log::info!(
"{} Activation precision explicitly set to {:?}",
"⚡".yellow(),
activation
);
if activation == nam_rs::math::activations::ActivationPrecision::Fast
&& model_architecture.eq_ignore_ascii_case("LSTM")
{
log::warn!(
"{} Fast activation (Padé) with LSTM architecture is NOT recommended — \
measured degradation is ~−13 dB ESR (clearly audible). \
Standard (exact-grade) activation is the universal default and costs only \
+10–15% CPU for LSTM models. See docs/audio_fidelity_map.md §2.",
"⚠️".yellow(),
);
}
}
rt_setup::configure_process_wide();
let res = pw_host::run_pipewire_host(
consumer,
gc_producer,
gc_overflow,
resampler_consumer,
resampler_producer,
cabsim_consumer,
cabsim_producer,
rt_status,
pw_host::PipewireHostConfig {
buffer_size,
sys,
ir_raw_samples,
full_wavenet_model,
slimmable_producer,
os_producer,
oversample: args.oversample,
},
gc_consumer,
slimmable_consumer,
os_consumer,
);
nam_rs::common::panic_hook::set_shutdown_in_progress();
unsafe {
pipewire::deinit();
}
res?;
Ok(())
}