use std::f32::consts::PI;
use std::path::PathBuf;
use std::time::Instant;
use neural_amp_modeler_rs::dsp::cabsim::adapter::CabSimAdapter;
use neural_amp_modeler_rs::dsp::cabsim::conv::ConvEngine;
use neural_amp_modeler_rs::dsp::cabsim::loader::CabSimIr;
const SAMPLE_RATE: u32 = 48000;
const PARTITION_SIZE: usize = 256;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("============================================================");
println!(" NeuralAmpModeler-rs — Cabinet IR Simulation (CabSim) ");
println!("============================================================");
let (ir_samples, ir_source_label) = match std::env::args().nth(1) {
Some(arg_path) => {
let path = PathBuf::from(arg_path);
if !path.exists() {
eprintln!(
"\nError: Specified IR file \"{}\" does not exist.",
path.display()
);
std::process::exit(1);
}
println!("\n[1/3] Loading IR WAV File");
println!(" File Path : {}", path.display());
let ir = CabSimIr::load(&path, SAMPLE_RATE, true)?;
println!(" Original Rate : {} Hz", ir.original_rate);
println!(" Loaded Rate : {} Hz", ir.sample_rate);
println!(" Samples Count : {}", ir.samples.len());
println!(" Normalized : {}", ir.normalized);
(
ir.samples.clone(),
format!(
"WAV: {}",
path.file_name().unwrap_or_default().to_string_lossy()
),
)
}
None => {
println!("\n[Notice] No WAV IR path supplied as CLI argument.");
println!("Generating synthetic 12-inch guitar cabinet impulse response...");
let syn_ir = generate_synthetic_cab_ir(SAMPLE_RATE, 2048);
(
syn_ir,
"Synthetic 12\" Guitar Cab IR (2048 samples)".to_string(),
)
}
};
println!("\n[2/3] Building UPOLS Convolution Engine");
let conv_engine = ConvEngine::new(&ir_samples, PARTITION_SIZE)
.map_err(|e| format!("Failed to build ConvEngine: {:?}", e))?;
let partition_size = conv_engine.partition_size();
let num_partitions = conv_engine.num_partitions();
let fft_size = conv_engine.fft_size();
let latency_samples = conv_engine.latency_samples();
let latency_ms = (latency_samples as f32 / SAMPLE_RATE as f32) * 1000.0;
println!(" IR Source Label : {}", ir_source_label);
println!(" IR Total Samples : {}", ir_samples.len());
println!(" Partition Size : {} samples", partition_size);
println!(" Num Partitions : {}", num_partitions);
println!(" FFT Block Size : {} bins", fft_size);
println!(
" Algorithmic Latency : {} samples ({:.2} ms)",
latency_samples, latency_ms
);
let mut adapter = CabSimAdapter::new(Box::new(conv_engine))
.map_err(|e| format!("Failed to create CabSimAdapter: {:?}", e))?;
let duration_secs = 2.0;
let total_samples = (SAMPLE_RATE as f32 * duration_secs) as usize;
let input_audio = generate_guitar_di_signal(total_samples, SAMPLE_RATE);
let mut output_audio = vec![0.0f32; total_samples];
println!("\n[3/3] Processing Audio through CabSim Adapter");
println!(
" Audio Duration : {:.2} seconds ({} samples)",
duration_secs, total_samples
);
let sub_block_sizes = [64, 128, 96, 256, 192, 128];
let mut block_idx = 0;
let mut offset = 0;
let start_time = Instant::now();
while offset < total_samples {
let block_size = sub_block_sizes[block_idx % sub_block_sizes.len()];
let len = block_size.min(total_samples - offset);
let in_slice = &input_audio[offset..offset + len];
let out_slice = &mut output_audio[offset..offset + len];
adapter.process_variable(in_slice, out_slice, None);
offset += len;
block_idx += 1;
}
let elapsed = start_time.elapsed();
let throughput = (total_samples as f64 / elapsed.as_secs_f64()) / 1000.0;
let in_peak = compute_peak(&input_audio);
let out_peak = compute_peak(&output_audio);
let in_rms = compute_rms(&input_audio);
let out_rms = compute_rms(&output_audio);
println!("\n[Performance & Statistics]");
println!(" Execution Time : {:.2?}", elapsed);
println!(" Sub-Blocks Ran : {} variable-length calls", block_idx);
println!(" Throughput : {:.2} kSamples/sec", throughput);
println!("\n[Audio Energy Breakdown]");
println!(
" Input Peak : {:.4} ({:.2} dBFS)",
in_peak,
20.0 * in_peak.max(1e-6).log10()
);
println!(
" Output Peak : {:.4} ({:.2} dBFS)",
out_peak,
20.0 * out_peak.max(1e-6).log10()
);
println!(
" Input RMS : {:.4} ({:.2} dBFS)",
in_rms,
20.0 * in_rms.max(1e-6).log10()
);
println!(
" Output RMS : {:.4} ({:.2} dBFS)",
out_rms,
20.0 * out_rms.max(1e-6).log10()
);
println!("\n[Status] Cabinet IR convolution completed successfully.");
Ok(())
}
fn generate_synthetic_cab_ir(sample_rate: u32, length: usize) -> Vec<f32> {
let mut ir = Vec::with_capacity(length);
let dt = 1.0 / sample_rate as f32;
for i in 0..length {
let t = i as f32 * dt;
let resonance = (2.0 * PI * 90.0 * t).sin();
let body = (2.0 * PI * 220.0 * t).sin() * 0.5;
let decay = (-t * 80.0).exp(); let sample = (resonance + body) * decay;
ir.push(sample);
}
let sum_abs: f32 = ir.iter().map(|s| s.abs()).sum();
if sum_abs > 0.0 {
for s in &mut ir {
*s /= sum_abs;
}
}
ir
}
fn generate_guitar_di_signal(samples: usize, sample_rate: u32) -> Vec<f32> {
let mut buf = Vec::with_capacity(samples);
let dt = 1.0 / sample_rate as f32;
for i in 0..samples {
let t = i as f32 * dt;
let sig = 0.5 * (2.0 * PI * 110.0 * t).sin()
+ 0.3 * (2.0 * PI * 220.0 * t).sin()
+ 0.15 * (2.0 * PI * 330.0 * t).sin();
buf.push(sig);
}
buf
}
fn compute_peak(buf: &[f32]) -> f32 {
buf.iter().map(|s| s.abs()).fold(0.0f32, f32::max)
}
fn compute_rms(buf: &[f32]) -> f32 {
if buf.is_empty() {
return 0.0;
}
let sum_sq: f32 = buf.iter().map(|s| s * s).sum();
(sum_sq / buf.len() as f32).sqrt()
}