Skip to main content

Crate nam_rs

Crate nam_rs 

Source
Expand description

§nam-rs

Pure-Rust, real-time-safe inference for Neural Amp Modeler (NAM) .nam models.

This crate loads a .nam model file and runs its neural network forward pass — a whole buffer at a time (WaveNet uses a cache-friendly block kernel), or one sample at a time — with no heap allocation on the audio (hot) path. It is a from-scratch Rust port of NAM’s inference, written against the reference implementations below and validated for parity against them (per-sample error within 1e-5, the same tolerance NeuralAudio uses).

Three model shapes are supported through the architecture-agnostic Model enum, which dispatches on the .nam’s declared architecture so you never branch on it yourself: WaveNet, LSTM, and SlimmableContainer (NAM “A2”). A SlimmableContainer is a thin multiplexer over a set of complete standalone submodels (each WaveNet or LSTM); a width dial selects the active one as a CPU/quality trade-off. Drive it via Model::as_slimmable_mutSlimmable::set_slim_size / Slimmable::select. Switching is real-time-safe (a single index write); each submodel keeps its own state, so switching mid-stream leaves a short warmup transient on the newly-selected submodel.

The A2 feature set is supported: FiLM, gating, bottleneck, grouped convs, the per-array head (incl. multi-tap conv heads), the optional post-stack head (activation → Conv1d chain run after the arrays, with head_scale scaling its input), and a condition_dsp (a nested model whose output replaces the conditioning fed to every array — including a multi-channel-output one, whose N rows become the N-wide conditioning fed to every array). The remaining restrictions — multi-channel input (in_channels != 1), a post-stack head with out_channels != 1, mixed gating modes within one array, and exotic activations — are rejected with Error::UnsupportedFeature (or Error::UnsupportedActivation) rather than silently mis-run.

Sample rate. A .nam is captured at a specific rate (NamModel::expected_sample_rate, 48 kHz when the file does not say). nam-rs does not resample — you must feed the model audio at that rate, or resample in your host first. A mismatched rate produces silently wrong output, because the model’s dilations and recurrence are defined in samples, not seconds.

Processing boundary. nam-rs runs only the model’s forward pass (plus its head_scale). The reference NAM plugin additionally applies a DC blocker (high-pass) and, optionally, loudness normalization on the output — those are the host’s responsibility, not the model’s, so they live in your audio graph, not here. The calibration accessors (NamModel::loudness etc.) give you the numbers to do that gain-staging yourself.

Metadata. NamModel::metadata_typed parses the file’s metadata block into a Metadata struct — name, who captured it, gear make/model/type, tone type, trainer, export date, loudness and calibration levels — everything a model browser shows and nothing the forward pass uses. See Metadata for the full field list; all fields are optional and parsed leniently, and the unparsed block remains on NamModel::metadata. One derived helper, Metadata::includes_cab, answers the question that changes a signal chain: whether the capture already contains a speaker cabinet, and so whether an impulse response after it would be a second one.

§Design contract

  1. Parity with the reference. The forward pass must produce output equal (within float tolerance) to the canonical Python/C++ NAM implementations for the same .nam file and input. This is enforced by tests/parity.rs.
  2. Real-time safety. The runtime’s process_buffer (on both WaveNet and Lstm, reached via Model) performs zero heap allocations, locks, or system calls. All scratch buffers are pre-allocated at construction. This is enforced by tests/rt_safety.rs.

§Example

use nam_rs::{NamModel, Model};

// From disk you'd use `NamModel::from_file("model.nam")?`.
// Here we use a tiny in-line model for illustration.
let json = r#"{
    "version": "0.5.4", "architecture": "WaveNet",
    "config": {
        "layers": [{
            "input_size": 1, "condition_size": 1, "channels": 1, "head_size": 1,
            "kernel_size": 1, "dilations": [1], "activation": "ReLU",
            "gated": false, "head_bias": false
        }],
        "head": null, "head_scale": 1.0
    },
    "weights": [1.0, 2.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0]
}"#;

let model = NamModel::from_json_str(json)?;
let mut amp = Model::from_nam(&model)?;   // picks the architecture from the file

// On the audio thread: process in place, no allocation.
let mut buffer = [0.1_f32, 0.2, 0.3, 0.4];
amp.process_buffer(&mut buffer);

§Attribution

This is a derivative work. The algorithm and weight layout are ported from the following projects (see NOTICE for license texts):

  • neural-amp-modeler — Steven Atkinson’s reference trainer + .nam exporter (Python, MIT). The source of truth for export_weights / export_config.
  • NeuralAmpModelerCore — the canonical C++ inference library (MIT).
  • NeuralAudio — Mike Oliphant’s high-performance C++ NAM/RTNeural runtime, designed to match NAM Core exactly (MIT). Primary porting reference.

Structs§

Date
The calendar timestamp NAM stamps into metadata.date when the model is exported. No timezone is recorded, so treat it as a naive local timestamp.
FilmConfig
One FiLM block (*_pre_film / *_post_film): conditions a scale (+ optional shift) from the conditioning signal. Absent or false ⇒ inactive.
Head1x1Config
A layer’s head 1×1 (head1x1): an optional 1×1 producing this layer’s head contribution. Inactive by default (then the head contribution is the activated bottleneck directly).
Layer1x1Config
A layer’s residual 1×1 (layer1x1): maps the activated bottleneck back to channels. Active by default (the A1 _1x1).
LayerArrayConfig
Configuration for one WaveNet layer-array, normalized so every per-layer quantity is a Vec of length dilations.len(). Built from the on-disk JSON by the internal RawLayerArrayConfig::normalize; A1 files fill the A2 fields with defaults.
Lstm
A ready-to-run LSTM, all scratch pre-allocated in Lstm::new.
LstmConfig
LSTM configuration (NAM _export_config).
Metadata
The fields NAM may write into a .nam file’s metadata block.
NamModel
A parsed .nam model file.
PostStackHeadConfig
Post-stack head (config.head): a stack of activation → Conv1D applied after the layer-arrays. None for A1 / current A2 defaults.
Slimmable
A width-selectable set of pre-built submodels (NAM Core SlimmableContainer).
SlimmableConfig
SlimmableContainer configuration: an ordered list of standalone submodels selected at runtime by a width dial. The container holds no weights of its own.
SlimmableSubmodel
One entry in a SlimmableConfig: a complete standalone submodel plus the width-dial threshold at which it becomes active.
WaveNet
A ready-to-run WaveNet, with all scratch buffers pre-allocated.
WaveNetConfig
WaveNet configuration: layer-arrays, optional post-stack head + condition DSP, and the output scale. Per-layer quantities are normalized into Vecs.

Enums§

ActivationSpec
How a layer-array’s activation field was specified in the .nam.
Error
Errors produced when loading or building a NAM model.
GatingMode
Activation gating mode for a WaveNet layer (NAMCore GatingMode).
Model
A runnable NAM model of any supported architecture.
ModelConfig
Architecture-specific configuration, tagged by NamModel.architecture.

Constants§

DEFAULT_SAMPLE_RATE
Sample rate assumed when a .nam file omits the sample_rate field.