NeuralAmpModeler-rs 3.0.2

An opinionated, high-performance Neural Amp Modeler (NAM) client and core implementation in Rust for Linux/PipeWire and CLAP plugins.
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 Fábio Henrique de Lima Silva (fhl.bsb@gmail.com) All rights reserved.

//! WaveNet A2 model — const-generic fast-path (`WaveNetA2<CH>`) and dynamic engine (`WaveNetA2Dyn`).
//!
//! ## Architecture
//!
//! Both variants share the same 23-layer dilated causal architecture:
//! 1. Input rechannel: `Conv1x1(1 → CH)` (bias, no activation)
//! 2. 23 layers: dilated conv → input-mixin → LeakyReLU → head_accum += out → layer1x1 → residual
//! 3. Head conv: `Conv1D(CH → 1, K=16, bias)` over head_accum ring → × head_scale
//!
//! Processing is chunked by `WAVENET_MAX_NUM_FRAMES` (64) with zero allocation on the hot-path.
//!
//! ## Cross-Validation and Golden Vectors
//!
//! A2 golden vectors are generated by the C++ render tool (`a2_fast.cpp` fast-path)
//! via `golden_gen_build.sh`, providing real cross-validation against the upstream
//! reference. See `docs/cpp_parity_map.md` §5 for the parity matrix.
//!
//! ## Accepted divergences
//!
//! - Rechannel weights are stored as u16 (f16/bf16) and dequantized at runtime.
//!   C++ uses native f32. The quantization error (< 1e-3 relative) is below the
//!   output SNR tolerance for A2 models.

use crate::dsp::mirror_buf::MirroredBuffer;
use crate::models::a2::params::{
    A2_DILATIONS, A2_HEAD_KERNEL_SIZE, A2_KERNEL_SIZES, A2_NUM_LAYERS,
};
#[cfg(test)]
use crate::models::wavenet::common::WAVENET_MAX_NUM_FRAMES;

/// Computes the receptive field size for the A2 architecture.
///
/// Sum of `(kernel_size - 1) * dilation` across all 23 layers,
/// plus `(head_kernel_size - 1)` for the head convolution lookback.
#[inline]
pub const fn a2_receptive_field() -> usize {
    let mut rf = 0usize;
    let mut i = 0;
    while i < A2_NUM_LAYERS {
        rf += (A2_KERNEL_SIZES[i] - 1) * A2_DILATIONS[i];
        i += 1;
    }
    rf + (A2_HEAD_KERNEL_SIZE - 1)
}

/// Common prewarm reset: zeroes all layer buffers, resets ring starts,
/// clears `layer_in` and `head_accum`, and positions `head_write_pos`
/// at the receptive field frontier — matching the C++ `DSP::Reset` preamble.
#[expect(
    clippy::too_many_arguments,
    reason = "A2 model constructor requiring many topology and buffer parameters for neural network initialization"
)]
#[cold]
pub(crate) fn a2_prewarm_common(
    num_layers: usize,
    receptive_field_size: usize,
    layer_buffers: &mut [MirroredBuffer<f32>],
    layer_ring_sizes: &[usize],
    layer_buffer_starts: &mut [usize],
    layer_in: &mut [f32],
    head_accum: &mut [f32],
    head_write_pos: &mut usize,
) {
    for buf in layer_buffers.iter_mut() {
        let len = buf.size();
        buf[..len].fill(0.0);
    }
    layer_buffer_starts[..num_layers].copy_from_slice(&layer_ring_sizes[..num_layers]);
    layer_in.fill(0.0);
    head_accum.fill(0.0);
    *head_write_pos = receptive_field_size;
}

#[path = "static/mod.rs"]
pub mod static_mod;
pub use static_mod::WaveNetA2;

pub mod cascade;
pub mod dynamic;
mod set_weights;

#[cfg(test)]
#[path = "../model_test.rs"]
mod tests;