Skip to content

Repository files navigation

NAM Mixer

Turn your Neural Amp Modeler captures into one new, trainable .nam model — either by blending two amps into one playable hybrid, or by sweeping several fixed-gain captures of one amp into a single Input-gain-driven model.

Ever wanted a clean Fender that opens up into a Marshall crunch the harder you dig in, without switching presets? Or a Gain-1-to-10 amp pack collapsed into one NAM you sweep with the ordinary Input knob instead of ten separate files? NAM Mixer renders your own .nam captures through the same real NAMCore inference engine used by the official plugin, combines them with one of three Builder design modes (or, for one amp, the Continuous Gain workflow), and trains the result into a brand-new standalone NAM model — no switching, no source models required at inference time.

Everything runs locally — as the desktop app or a small Flask app run from source: your amp captures, DI files, and generated training material never leave your computer unless you explicitly send a training job to your own Kaggle account for free GPU time.

Status: Beta. It renders source NAMs through real NAMCore inference and generates trainable A2 bundles end to end — listen to and validate every generated model before you trust it.

NAM Mixer's Dynamic Hybrid tab, with the guided Tone Wizard open

Get NAM Mixer

Download the desktop app

No Python or renderer setup needed — the desktop app bundles the backend, the web UI and the native nam_render. Direct downloads (always the latest release; all builds are on the Releases page):

Platform Download Notes
macOS (Apple Silicon) NAM-Mixer-macOS-arm64.dmg Not notarised: on first launch use Open from the Finder context menu (or approve it in Privacy & Security).
Windows (x64) NAM-Mixer-Windows-x64-setup.exe · .msi Installer (.exe) or MSI package.
Linux (x64) NAM-Mixer-Linux-x64.AppImage · .deb Portable AppImage, or a Debian/Ubuntu package.

Older versions and release notes: all releases. See Desktop app downloads for what the desktop build does and does not include.

Or run it from source

Clone the repo and run it as a local Flask app with python3 — see Quick start below. This gives you the full render/design/audition/generate experience described in this README, plus direct access to the code. Local A2 training uses a separate environment and needs Python 3 installed; Kaggle training remains opt-in and uses your own Kaggle CLI/account — see Workflow.

Why this exists

Splitting your tone across two amp captures — a clean voice for verses, a driven one for choruses — usually means a preset switch, a MIDI patch change, or an A/B/Y box. NAM Mixer instead builds a continuous, level-driven transition between two captures, so the amp itself responds to how hard you play, the way a real tube amp's character shifts with picking dynamics. It then lets you freeze that behavior into one trained model, so the hybrid becomes something you can load anywhere a NAM runs.

Features

🎚️ Three design modes Dynamic Hybrid — level-driven crossfade between Amp A and Amp B. Parallel Blend — constant-ratio mix of both amps, independent of playing level. Character Blend — a deterministic teacher built from one drive-selected donor plus measured tone/feel corrections.
🎧 Real NAM inference Amps are rendered through the same NeuralAmpModelerCore C++ engine the official plugin uses — not a reimplementation, not an approximation.
🔊 Live audition A/B the two source amps against the blended result over a bundled library of real guitar/bass performances, with input-profile simulation, test gain, coverage analysis, and an optional cabinet IR — all without re-running inference on every tweak.
🧠 Train a real model Generate a proper training bundle from the official NAM training excitation and train a new A2 model on it — locally, or with one click on a private Kaggle GPU.
✍️ Optional AI recipes A local, Cloudflare Workers AI, or custom OpenAI-compatible model can turn a plain-English tone description into editable blend settings; built-in rules remain available with no AI service.
Built-in validation Every exported model is re-rendered and compared against its frozen teacher (ESR, RMS, peak, quiet-response) so you know exactly how close the trained model landed, not just that training finished.
🎛️ Continuous Gain Turn several fixed-gain captures of one amp into one standard .nam you sweep with an ordinary player's Input gain — captures are analysed, a subset is chosen from measurements, and the model is trained locally or on Kaggle. See Continuous Gain.
💾 Sessions library Save, reload, import/export, and revisit designs and completed models without losing your place.
🔒 Local-first & private No cloud dependency, no telemetry, no account required. Kaggle training is fully opt-in and uses your own credentials and quota.
⚙️ In-app Settings Configure the renderer path, AI provider, and TONE3000 API key from a Settings tab — no shell environment variables required. See Settings.
Getting-started checklist The Settings tab's "Getting started" section shows at a glance what's ready and what's still optional (renderer, training input, local A2 training env, Kaggle, AI provider).

The app never claims that a completed training run sounds identical to its teacher. Always listen to and validate exported models.

Contents

What this is

NAM Mixer is a small local tool with two ways to build a new, trainable NAM model from captures you already have:

  • Builder (the Dynamic Hybrid/Parallel Blend/Character Blend tab) combines two existing Neural Amp Modeler (NAM) amp captures — for example, moving from a Fender clean model toward a Marshall crunch model as you play harder.
  • Continuous Gain takes several fixed-gain captures of one amp and channel (Gain 1 … 10, say) and builds one standard .nam you sweep with an ordinary player's Input gain instead of switching between capture files.

Both generate a trainable target, then train and validate a single resulting NAM A2 model that reproduces the chosen design without requiring the source models at inference time.

This is NOT model-weight merging

We are not averaging, interpolating, or otherwise combining the weights of two NAM neural networks. That's a different (and much harder, largely unsolved) problem — two independently-trained networks don't share a latent space, so blending their weights doesn't produce anything musically meaningful.

Instead, the approach is:

  1. Render the same input audio through Amp A and through Amp B separately (two ordinary, unmodified NAM inference passes).
  2. Blend the resulting audio, sample-by-sample, using a crossfade curve driven by the level of the original dry input — quiet input leans toward Amp A's render, loud input leans toward Amp B's render, with a smooth transition in between.
  3. That blended audio becomes a synthetic training target.
  4. Eventually, train a fresh NAM A2 model against that synthetic input/output pair, producing one model that reproduces the hybrid behavior directly — at that point the two source .nam files are no longer needed at inference time.

Step 4 is available through the UI for either Kaggle GPU or local training. Every generated model still needs listening and validation; the tool does not make a claim of perceptual equivalence merely because training completes.

Why the dry input's level controls the transition

The crossover has to be driven by something that exists independently of which amp is currently being auditioned — otherwise there's a chicken-and-egg problem (you'd need to know the blend to compute the blend). The dry guitar signal before either amp model touches it is the one signal that's the same regardless of which amp(s) you render it through, so it's the natural, stable control signal for "how hard is the player driving this right now."

Concretely: level = envelope(dry_input), and that per-sample level (in dBFS) is what decides the Amp A/Amp B mix weight, not the loudness of either processed output. See hybrid/envelope.py.

Why automatic level matching is needed

Two independently-trained/captured NAM models will almost never happen to sit at the same loudness for a given input level. If Amp B is simply louder than Amp A at the crossover point, the transition will sound like a volume jump rather than a change in amp character — exactly the artifact we're trying to avoid. Rather than normalizing the two amps' overall loudness (which doesn't guarantee anything about how they compare specifically at the crossover point, where it actually matters), this project measures each amp's loudness using only the portion of the render that falls near the chosen crossover level and computes a trim from that. See hybrid/level_match.py.

Input profile vs. crossover vs. NAM calibration — three separate knobs

It's easy to conflate these; they operate at different stages and have very different costs:

  • Input profile (hybrid/input_profiles.py, render_pair()): simulates a different instrument/pickup driving the signal chain before it reaches either NAM. This changes the actual audio both amps receive, so changing it is EXPENSIVE — it requires re-running NAM inference for both amps. See docs/history/INPUT_PROFILE_RESEARCH.md for the research behind the presets and why active pickups deliberately have no fixed preset.
  • Crossover / transition width / manual trim (build_hybrid()): changes only how the already-rendered Amp A/B responses are blended together. This is CHEAP — pure numpy, no NAM inference, safe to recompute on every slider move.
  • NAM input calibration (hybrid/calibration.py): when both .nam captures report their own recording calibration (input_level_dbu), applies the official NAM plugin's per-model compensation formula so two differently-calibrated captures see the same virtual physical input level. This also happens at render time (it's folded into render_pair() alongside the input profile), but it's a distinct concept from "how hard is the (virtual) instrument driving the amps" — one is about the instrument, the other is about reconciling two amp captures' own assumptions about their input level.

Why the genre/style DI files are included

assets/di/ contains real recorded DI (direct input) guitar/bass performances, copied from the NAMtoClo project (see assets/di/README.md for exactly which files, where they came from, and their measured characteristics). They exist so you can audition a hybrid crossover on real playing dynamics — palm-muted riffing, clean chords, dynamic picking, etc. — without needing your own guitar/interface set up, and so the project has a consistent, versioned set of fixtures for regression testing the blend engine itself.

Preview DIs vs. NAM training material — an important distinction

genre/style DI (assets/di/*.wav)
    ↓
preview, auditioning, crossover analysis,
automatic level-match testing, regression testing

official NAM v3.0.0 training input (a proper calibrated reamp/DI signal, e.g.
the kind of stimulus NAMtoClo itself uses as nam_input_wav.wav — bundled at
assets/training/, seeded automatically on first run; see that folder's README)
    ↓
Amp A render
Amp B render
    ↓
dynamic blend
    ↓
synthetic training output
    ↓
A2 training

The genre DIs are musical performances — useful for judging how a hybrid sounds and behaves, but not designed to exercise the amp's full frequency/ level response the way a proper reamping signal is. The actual synthetic training pair used to eventually train an A2 model must come from a real NAM training/reamping signal, not from assets/di/. This distinction is deliberate and should not be blurred — see assets/di/README.md for more.

Workflow

The interface is organised into four stages. You can move between them at any time; the same sources, preview material, cabinet, output safety, and training tools remain shared across all three design modes.

  1. Sources — select Amp A and Amp B .nam files, a preview DI, an instrument/input profile, and (when needed) advanced NAM calibration or per-amp input trims. Render Amps runs the two expensive NAM inference passes.

  2. Shape — choose Dynamic Hybrid crossover/transition, Parallel Blend mix, or Character Blend tone/feel/drive controls, then set level matching. These controls recombine the cached pair and do not re-run NAM inference.

  3. Listen — compare Amp A, the current result, and Amp B against a chosen genre DI, using an input profile to simulate different pickups/output levels (DESIGN/preview context only — see "Input profile vs. crossover vs. NAM calibration" above). The genre DI is a convenience audition performance, not engineered to hit every level a real player might reach — use Test gain (an additional real gain on top of the profile, applied before both amps render) to deliberately push the level up/down and stress-test the crossfade beyond whatever that clip's own dynamics happen to cover. Test gain automatically re-renders the pair after you stop dragging. Cabinet and output-gain controls are optional finishing tools; Dynamic Hybrid analysis is available on demand.

  4. Create & train — the official NAM training excitation is bundled and ready by default (upload a different one only if you want to); click "Generate Training Bundle" — this freezes the current design into an immutable HybridDesign (hybrid/design.py) and blends the official training input (not the preview DI, and not with the input-profile gain applied — the profile only shaped design/preview, never the actual training excitation) through Amp A/Amp B with that frozen design (hybrid/training_target.py). Produces input.wav, hybrid_target.wav (+ hybrid_target_raw.wav for comparison), hybrid.hybrid.json, and training_manifest.json under work/a2/<design_id>/. Then train a real A2 (PackedWaveNet) model on the bundle, either:

    • Kaggle GPU (recommended — no local Torch install, trains on a free private Kaggle T4) — see Setting up Kaggle GPU training below, or
    • Local, with scripts/train_a2.py (needs a separate Torch/ neural-amp-modeler environment — see requirements-training.txt / scripts/setup_a2_env.sh (macOS/Linux) / scripts/setup_a2_env.ps1 (Windows) — never the app's own Python environment).

    Both paths validate the exported .nam identically: loading and rendering its Full and Lite branches through the existing native NAMCore renderer, recording raw/gain-normalized ESR, and checking quiet response against an equivalent processed reference when one is available. Training completion and technical validation quality are reported separately.

Setting up Kaggle GPU training

Kaggle GPU is optional — the preview/design UI and local training work fully without it. To enable the one-click "Train A2" button on the "Create A2" card:

  1. Have (or create) a free Kaggle account.
  2. Verify your phone number on kaggle.com/settings (Account tab) — Kaggle requires this before it will grant GPU/TPU accelerator quota to any account, regardless of how you submit the job. This is the step people most often miss.
  3. Install and authenticate the Kaggle CLI. The Create A2 card offers an explicit Install Kaggle CLI button when it is missing; alternatively, install it once in your normal shell (the app never imports Kaggle's library; it invokes the CLI/module only for cloud operations):
    pip install kaggle
    kaggle auth login
    This opens a browser to sign in and stores a credential locally (~/.kaggle/kaggle.json or KAGGLE_API_TOKEN) that the Kaggle CLI manages entirely on its own — NAM Mixer never reads, stores, or logs it.
  4. Reload the "Create A2" card in the app; it should show "Connected ✓" and enable the Train A2 (Kaggle) button.

Each training run stages a private, uniquely-named Kaggle dataset + kernel under your account, polls it without blocking the app, downloads the result, and re-validates it locally before calling the job complete — see the in-app setup checklist and Setting up Kaggle GPU training for the mechanics, troubleshooting, and how to recover a job that trained but failed to download. Kaggle's free T4 quota is weekly and account-wide; the "Create A2" card shows your remaining quota before you submit.

Sessions

The Sessions tab is a project library, not a popup. Use it to save the current controls under a name, load or inspect an earlier design, export a portable NAM Mixer JSON file, import one, or delete a session. Generated training bundles are also saved as sessions automatically so their completed NAM can be downloaded again or opened in Tools. Validation reports are bound to the completed NAM's SHA-256; editing the NAM invalidates that report. A completed bundle also offers a synchronized Teacher/Full/Lite comparison on the selected musical DI at normal or quiet input level. It reconstructs the teacher from the saved design, never from current controls.

A session restores the selected settings and app-managed NAM/cabinet file references, but it deliberately does not render automatically. After loading, use Render Amps to rebuild the pair and verify that the referenced files are still available. Training manifests under work/a2 are separate from sessions and are not interchangeable with session JSON files.

Continuous Gain: one amp, one NAM

The Continuous Gain tab is separate from the Builder's two-amp modes. You give it several fixed-gain captures of one amp and channel (for example Gain 1 … 10), and it builds one standard .nam whose whole gain range you explore with an ordinary NAM player's Input gain — no model switching, no extra runtime processing. It approximates the amp's range; it does not reproduce every knob position exactly.

It follows the Builder's guided flow — four stages, one centred column of cards:

  1. Add captures — drag in the .nam files and confirm each one's physical gain position (file names only suggest it). Missing or duplicate positions, wrong sample rates and too-few captures are flagged before anything runs.
  2. Analyse & select — every capture is probed, audited (VALID / CORRECTED / SUSPECT; nothing uncertain is silently fixed) and profiled; a training subset is chosen from those measurements (Automatic), or you use all captures or pick your own (Custom). You see why each capture was chosen or omitted, how well the omitted ones are reproduced, the measured response, and the Input-gain mapping that will be used. If no subset meets the coverage rule it says so and falls back to all eligible captures.
  3. Train — training files are created and trained with the same Kaggle / local training section as the Builder (same setup, presets, progress and logs). The default recipe is the validated one: official NAM input plus real guitar DIs at level offsets, response-distance anchors on −20…+14 dB, one peak-ceiling output scale (shown as the Output gain to set in your player). Fixed 4 dB anchors are available under Advanced as an explicit alternative.
  4. Test & export — automated checks (standard NAM structure, Full/Lite renders, output safety, per-position measured comparison with your real captures on held-out DIs, direction reversals), an optional Input-gain sweep and A/B clips against the originals, and an export package (.nam, JSON metadata, player guide). Export is never blocked by validation or by listening.

Projects appear in Sessions labelled Continuous Gain (Sessions → Load opens this tab on that project; Delete removes its files). Analysis parallelises the native renders and caches probes per capture, so re-analysing after adding a capture is quick.

What to expect: a 60-epoch model takes roughly half an hour to train; validation figures are measurements, not a listening result — always listen. The tab's design, the training-material experiments and the frozen configurations it reproduces are documented in docs/continuous_gain_tab.md.

Design modes and the shared Cabinet stage

The workflow above describes Dynamic Hybrid mode, the original/default mode. Two further modes are available from the same mode selector:

  • Dynamic Hybrid (hybrid/blend.py, hybrid/design.py, hybrid/training_target.py): changes from Amp A toward Amp B according to playing level, via the crossover/transition envelope described above.
  • Parallel Blend (hybrid/fixed_blend.py, hybrid/blend_training_target.py): always combines the two amp responses at one constant, user-chosen ratio (result = A * (1 - mix_b) + B * mix_b), independent of playing level — no crossover envelope at all. Its own auto level-match uses the DI's ACTIVE playing material (silence excluded) rather than a crossover band, since there's no crossover region to match around (see hybrid.fixed_blend.compute_active_trim).
  • Character Blend (hybrid/character_blend.py): uses a continuous, residual-bounded nonlinear carrier plus measured EQ and compression corrections to produce a deterministic teacher design. Tone, Feel, and Drive are not a simple parallel waveform mix. Drive preserves exact Amp A/B carriers outside a 35-65% soft region and can optionally vary by input level.

All modes share Amp A/Amp B, the preview DI, input profile/calibration, render, test gain, the Listen controls, the Cabinet IR stage, the official training input, A2 quality, and training — switching tabs never re-runs NAM inference; the already-rendered RenderedPair (hybrid/pipeline.py) is reused by whichever mode you're auditioning.

A third, mode-independent stage — Cabinet IR (hybrid/cab_ir.py) — sits AFTER the amp combination in any mode. Preview is independent of training: all A2 training and validation uses the head-only target. When an IR is selected, the export choices are:

  • No cabinet: train/export the conventional head-only A2. A selected IR remains a reusable preview/bundle artifact, not part of the target.
  • Create both (embedded, advanced): retain the tested conventional head-only A2 and derive a second NAM containing an explicitly extracted Full WaveNet followed by canonical Linear FIR taps in a NAM Sequential model. The prepared IR WAV is also retained. The two artifacts are offered as clearly separate head-only and -with-cab.nam downloads. This option is hidden by default; enable Settings → Advanced → Enable experimental NAM architectures before selecting it.

Older saved designs that use the legacy learned mode remain readable, but the current UI no longer applies the cabinet to the training target.

The cabinet output folds the recorded post-cab safety scalar into the Linear weights and is downloadable only after validation using the bundled, Sequential-capable renderer. NAM format validity and NAM A2 compatibility are two separate claims: A2-only players may reject a Sequential model. Use the separately provided head-only download for broad compatibility. The setting is enforced server-side as well as in the UI, so a client cannot request the experimental export while it is disabled.

Receptive-field policy: one hard check, two advisory ones. Amp A/Amp B (+, for Hybrid and Character, the bounded crossover envelope) are the CORE dependency — this must fit inside the destination A2's actual receptive field, or generation/training is refused exactly as before. Two further dependencies are always calculated and reported honestly, but never block training by themselves, because in both cases the A2 is being trained to approximate the rendered teacher rather than to compile its signal graph exactly:

  • Character processing. Character Blend's donor transitions, smoothing, and correction filters add their own formal temporal dependency (formal_character_required_samples). If it exceeds the A2's receptive field, training still proceeds as an approximation — the Full/Lite export and quiet-response checks stay authoritative for judging the result.
  • Baked cabinet. A baked cabinet adds len(ir) - 1 samples of serial temporal dependency on top of the core (see hybrid/receptive_field.py's combine_required_history). A baked cab whose formal total exceeds the A2's receptive field does not block training either — the A2 learns an approximation of the post-cab response within its available capacity.

Either case prints an explicit "CABINET APPROXIMATION" or "CHARACTER APPROXIMATION" notice from scripts/train_a2.py/the Kaggle cloud worker — validate the result by listening and by checking the printed ESR/RMS metrics against the baked target. The exact same full-length cabinet IR is used for preview and for baking in either case; only the training-time gating differs. Local and Kaggle training implement this policy independently but are kept from silently diverging by tests/test_receptive_field_parity.py.

Because raw WAV/FIR length is a poor proxy for how much of a captured IR is actually audible signal, the Cabinet card also reports cumulative-energy diagnostics (e.g. "99.9% energy by: 42.7 ms" for a nominally-500ms IR) — purely informational, never used to shorten the actual convolution.

Quick start

Optional: local AI tone recipes

The Tone Wizard works without an AI service. The provided .env enables its optional local-AI suggestion with your local Ollama model (gemma4:e4b at http://127.0.0.1:11434/v1). Start Ollama, then launch NAM Mixer with:

scripts/run.sh

The launcher reads .env automatically. To change machines or models, edit your untracked .env; .env.example documents the available defaults.

The base URL must be a loopback HTTP URL (localhost, 127.0.0.1, or ::1) and is deliberately read only from the process environment. The browser cannot choose an endpoint. Only the written tone request and a fixed recipe schema are sent to the local service—never NAM files or audio. If the model is down, slow, or returns invalid settings, NAM Mixer uses its built-in recipe rules.

Every platform needs Python 3.10+ plus a working nam_render — the native NAM inference executable. You don't need a C++ compiler to get one: CI builds nam_render for macOS, Linux, and Windows on renderer releases (see .github/workflows/build-nam-render.yml). Version tags such as v0.2.0 also build and attach the complete NAM Mixer desktop installers for macOS, Linux, and Windows (see .github/workflows/build-desktop.yml). Downloading a prebuilt binary is the default path below. Building from source is the fallback, for a platform/architecture CI doesn't cover or if you'd rather not run a downloaded binary.

Desktop app downloads

Installers are built by .github/workflows/build-desktop.yml and published on the project's GitHub Releases page for every version tag (v*). Each release carries the same five files under fixed names, so these links always point at the newest release:

Platform Direct download What it contains
macOS Apple Silicon NAM-Mixer-macOS-arm64.dmg Tauri app for arm64 Macs
Windows x64 NAM-Mixer-Windows-x64-setup.exe Windows installer
Windows x64 NAM-Mixer-Windows-x64.msi Windows MSI package
Linux x64 NAM-Mixer-Linux-x64.AppImage Portable app
Linux x64 NAM-Mixer-Linux-x64.deb Debian/Ubuntu package

To get a specific version, open its page under Releases (for example https://github.com/daverage/nam-mixer/releases/tag/v0.3.3) and download from Assets.

The desktop release bundles the Flask backend, web UI, training support files, and the native nam_render executable; users do not need to install Python or the renderer separately. Local A2 training still creates its own training environment the first time it is used, and Kaggle training still needs Kaggle authentication.

Releases are currently unsigned, so macOS may require Open from the Finder context menu (or an approval in Privacy & Security) on first launch. If a download link returns "not found", no release has been published yet: use the standard browser setup below or run the desktop build locally as documented in desktop/README.md.

After first-time setup below, scripts/run.sh (macOS/Linux) or scripts/run.ps1 (Windows) is a one-line way to relaunch later — it creates/ activates .venv, installs/updates dependencies only when requirements.txt has changed, opens the browser on the selected local port, and starts the app; it does not fetch nam_render for you.

macOS
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -r requirements.txt
scripts/download_nam_render.sh
scripts/run.sh

scripts/run.sh (and app.py directly) default to port 5001, not 5000 -- macOS reserves 5000 for AirPlay Receiver, which silently returns 403 instead of refusing the connection. To use a different port, run PORT=5003 scripts/run.sh.

Build from source instead

Requires Xcode's Command Line Tools (xcode-select --install) and CMake 3.18+. The first build downloads NeuralAmpModelerCore + its dependencies (a few hundred MB, one-time):

The normal renderer supports both conventional NAM and canonical Sequential/Linear NAM. If you previously built the older renderer, remove native/nam_render/build first so CMake picks up the new pinned core.

rm -rf native/nam_render/build  # only needed when replacing an older build
cmake -B native/nam_render/build -S native/nam_render -DCMAKE_BUILD_TYPE=Release
cmake --build native/nam_render/build --config Release --target nam_render -j 4
Linux
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -r requirements.txt
scripts/download_nam_render.sh
scripts/run.sh
Build from source instead

Requires a C++ toolchain and CMake 3.18+, e.g. on Debian/Ubuntu:

sudo apt-get update && sudo apt-get install -y build-essential cmake

Then:

rm -rf native/nam_render/build  # only needed when replacing an older build
cmake -B native/nam_render/build -S native/nam_render -DCMAKE_BUILD_TYPE=Release
cmake --build native/nam_render/build --config Release --target nam_render -j "$(nproc)"
Windows
python -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txt
powershell -ExecutionPolicy Bypass -File scripts/download_nam_render.ps1
powershell -ExecutionPolicy Bypass -File scripts/run.ps1

If Activate.ps1 is blocked, run PowerShell as: powershell -ExecutionPolicy Bypass.

Don't double-click nam_render.exe. It's a command-line helper tool that hybrid/render.py calls automatically with the right arguments — it's not the app. Double-clicking it in File Explorer runs it with no arguments, so it prints a usage error and the console window closes instantly, which looks like a crash but isn't one. Always launch the app itself via scripts/run.ps1 (or python app.py); that's what finds and invokes nam_render.exe for you behind the scenes.

Build from source instead

Requires CMake and the "Desktop development with C++" workload from the Visual Studio Build Tools (the MSVC toolchain nam_render needs), then:

Remove-Item -Recurse -Force native/nam_render/build  # only needed when replacing an older build
cmake -B native/nam_render/build -S native/nam_render -DCMAKE_BUILD_TYPE=Release
cmake --build native/nam_render/build --config Release --target nam_render

This produces native/nam_render/build/Release/nam_render.exe, supporting both conventional NAM and Sequential/Linear NAM models.

Then run scripts/run.sh (macOS/Linux) or scripts/run.ps1 (Windows) — the launcher opens the browser on the selected local port, and the app discovers the nam_render executable it just downloaded/built automatically, wherever it landed for your platform. From here:

  • Live preview/design/audition works immediately — no further setup.
  • Local A2 training needs a separate, dedicated training environment (never the app's own Python environment — see requirements-training.txt): run scripts/setup_a2_env.sh (macOS/Linux) or scripts/setup_a2_env.ps1 (Windows) to create it, then follow the Torch install command it prints for your platform/GPU.
  • Kaggle GPU training needs no local training environment at all — see Setting up Kaggle GPU training above.

Settings

The Settings tab (next to Sessions) covers anything that used to only be configurable via a shell environment variable or an .env file. Its Updates section has a Check for updates button — it only ever contacts GitHub when you click it (never automatically), and reports either "you're up to date" or a direct download link to the newer installer for your OS (desktop app), or a link to the release notes plus a git pull hint (running from source). Its Getting started section gives an at-a-glance checklist of what's ready and what's still optional (renderer, training input, local A2 training environment, Kaggle, AI provider) so a fresh checkout doesn't require hunting for each setup button individually.

  • NAM render executable — normally auto-detected (downloaded via scripts/download_nam_render.*, or the in-app Download nam_render automatically button). Change it here only if you built/downloaded a custom nam_render.
  • AI Assistant — choose Local, Cloudflare Workers AI, or Custom OpenAI-compatible. Local uses a localhost /v1 endpoint and needs only a model name. Custom remote endpoints must use HTTPS; their hostname is resolved and private/loopback/link-local/reserved addresses are rejected. Conversation Markdown exports can optionally include a structured debug appendix containing the exact bounded model messages, research queries and results, warnings, and parsed responses. API keys and authorization headers are never included.
  • Cloudflare Workers AI — in the Cloudflare dashboard, open Workers AI → Use REST API → Create a Workers AI API Token, then copy the token and the account ID. In NAM Mixer choose Cloudflare, enter the 32-character Account ID, the token, and a JSON-mode model such as @cf/meta/llama-3.3-70b-instruct-fp8-fast; the app builds https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/ai/v1 and sends the request to /chat/completions for you. A manually created token needs account-scoped Workers AI Read and Workers AI Edit permissions (the dashboard's “Create a Workers AI API Token” template is the easiest route). Cloudflare quota, capacity limits, and billing may apply. Test connection performs a small real JSON-mode inference request, so it may consume quota.
  • Automation boundary — NAM Mixer automates endpoint construction, authentication headers, JSON-mode requests, connection testing, and safe error handling. It deliberately does not create Cloudflare accounts/tokens, store tokens in browser code, or silently rotate/reuse tokens when switching providers. Use the explicit Clear API token action to remove a saved token from .env and the running process.
  • Local AI assistant — any host that speaks the OpenAI-compatible /v1 API works (Ollama, LM Studio, llama.cpp server, ...); Google's Gemma (gemma4:e4b) is a good default if you don't already run something else, and a Pull via Ollama button offers a one-click download when Ollama is installed but nothing's running yet.
  • TONE3000 API key — enables the TONE3000 tab's capture search. Get a key from your account at tone3000.com; saved keys are never echoed back by the app once entered.
  • Advanced → Enable experimental NAM architectures — off by default. Turning it on reveals Create both, which exports the tested head-only NAM plus an exact Sequential Embedded cabinet derivative. Leave it off for ordinary use unless you specifically need the advanced export. Settings are saved to the source checkout's own .env file — never uploaded anywhere.

Current status and limitations

Implemented

  • Real NAM inference: hybrid/render.py calls the native nam_render C++ CLI in native/nam_render/, built against NeuralAmpModelerCore. This keeps inference independent of Python torch/neural-amp-modeler and lets NAMCore remain authoritative for .nam model loading. Build steps are in native/nam_render/README.md.
  • .nam loading and calibration: hybrid/nam_loader.py reads model metadata, including input_level_dbu and output_level_dbu when present. Older or uncalibrated files remain usable and are reported as having unavailable calibration metadata.
  • The complete app workflow: the Flask app and browser UI connect real rendering, preview, coverage analysis, all three design modes, cabinet IR, Sessions, target generation, local training, and optional private Kaggle training. The main routes include /api/render_pair, /api/preview, /api/blend_info, /api/blend_curve, /api/input_profiles, /api/profile_coverage, and /api/generate.
  • Renderer readiness: the app performs a cheap --help launch check before preview inference, distinguishes missing from unusable executables, shows expandable setup help, and can retry after installation without a restart.
  • Mechanical safeguards: envelope following, blend math, level matching, optional alignment, receptive-field checks, NaN/Inf and silence checks, and non-limiting training-target peak control are implemented and covered by automated tests against synthetic signals.

Important validation boundaries

  • The Phase 4 real-render harness passed with two distinct local source models, two musical DIs, all three design modes, cabinet on/off, and input levels of -12/0/+12 dB. It produced deterministic rerenders and exact frozen-teacher equivalence for that run. Auto-calibrated cases were unavailable because those source files did not declare input-level metadata. Most automated pipeline tests still use mocked renders. Other real source-model combinations can still expose latency, calibration, or musical problems; listen to every preview and validate every exported model against its target.
  • A/B alignment is deliberately off by default. hybrid/align.py cross-correlates the two rendered signals, so a tonal or phase difference between dissimilar amps can look like latency. Enable it only when the timing behavior of the source models is known.
  • Character Blend is a deterministic teacher design, not a perceptual-match guarantee. No automated system judges tone, feel, or musical quality; the checks can only catch mechanical problems such as clipping, discontinuities, silence, or non-finite samples.
  • Local A2 training needs the separate environment described in requirements-training.txt. Kaggle training needs a configured Kaggle account and network access; neither is required to run the preview UI.
  • Continuous Gain reproduces its frozen reference configurations bit-for-bit and has been exercised end to end (local and Kaggle GPU training, in-browser) — see docs/continuous_gain_tab.md. Not yet established: model quality at the full 60-epoch preset by listening (only measurements so far), and the packaged desktop build's Continuous Gain tab on Windows/Linux.
  • Uploaded amp/cab files and internal render-source copies are only freed when the session(s) that used them are deleted (a startup sweep also clears render copies that never became a saved session) — see app.py's _sweep_orphaned_uploads/_sweep_orphaned_render_sources.

Safety: training target vs. live preview

The generated hybrid training target must never be run through a limiter — that would distort the very dynamic behavior we're trying to capture. If the generated hybrid exceeds a target peak ceiling (default -3 dBFS), a single fixed gain reduction is applied to the whole file instead (hybrid.safety.apply_peak_ceiling). A limiter (hybrid.safety. preview_safety_limiter) exists only as a speaker/headphone safety net on the live preview/playback path and must never touch a file destined to become (or derive) a training target.

Project layout

hybrid-nam-builder/
├── app.py                 -- Flask entry point
├── requirements.txt
├── hybrid/                -- core library (no Flask/UI dependencies)
│   ├── nam_loader.py       -- parse .nam files + calibration metadata
│   ├── render.py           -- NAM inference (shells out to native/nam_render)
│   ├── envelope.py         -- dry-input level/envelope extraction
│   ├── level_match.py      -- crossover-region auto level-match trim
│   ├── align.py            -- sample-offset detection/correction (optional, off by default)
│   ├── blend.py            -- the dynamic crossfade itself
│   ├── fixed_blend.py      -- Parallel Blend mode (fixed-ratio combination)
│   ├── character_blend.py  -- Character Blend teacher and low-level check
│   ├── cab_ir.py           -- shared cabinet IR convolution (preview + baked target)
│   ├── training_target.py  -- Dynamic Hybrid A2 training-target generation
│   ├── blend_training_target.py -- Parallel Blend A2 target generation
│   ├── character_training_target.py -- Character Blend A2 target generation
│   ├── receptive_field.py  -- mode/cab-aware temporal-dependency accounting
│   ├── validation.py        -- frozen-teacher/model rendering and shared metrics
│   ├── validation_report.py -- versioned Full/Lite/quiet quality reports
│   ├── kaggle_training.py  -- private Kaggle GPU job and local validation
│   ├── cg_*.py             -- Continuous Gain: probe, audit, profile, selection, anchors, bundle, project, validation, excitation
│   ├── nam_tools.py        -- safe output-volume and metadata editing
│   ├── wizard.py           -- guided setup flow shared by the UI modes
│   ├── safety.py           -- NaN/clip checks, non-limiting peak ceiling
│   ├── settings.py         -- Settings tab registry
│   ├── render_bootstrap.py -- in-app "download nam_render" for Settings
│   └── metadata.py         -- hybrid provenance metadata (JSON sidecar)
├── native/nam_render/      -- C++ NAM inference tool (NeuralAmpModelerCore), see its README
├── assets/nam_models/      -- user's own .nam amp captures (gitignored)
├── assets/di/              -- genre/style DI library + its own README
├── cg_routes.py            -- /api/cg/* routes for the Continuous Gain tab (static/cg.js)
├── templates/, static/     -- minimal HTML/CSS/JS UI (no build step)
├── tests/                  -- unit tests for the hybrid/ modules
├── scripts/                -- run/setup/download helpers, train_a2.py, validate_a2.py, analyze_di.py (regenerates assets/di/_analysis.json)
└── work/                   -- gitignored scratch output directory

Running it

See Quick start above for first-time setup on your platform. Once dependencies are installed and nam_render is built:

scripts/run.sh
# or: PORT=5001 scripts/run.sh
python3 -m pytest -q
# `pytest -q` works too; pytest.ini resolves the repository modules.

The test suite exercises hybrid/envelope.py, hybrid/blend.py, hybrid/level_match.py, hybrid/align.py, hybrid/safety.py, hybrid/nam_loader.py, hybrid/input_profiles.py, hybrid/calibration.py, hybrid/coverage.py, hybrid/pipeline.py, hybrid/design.py, hybrid/training_target.py, hybrid/fixed_blend.py, hybrid/blend_training_target.py, hybrid/character_blend.py, hybrid/character_training_target.py, hybrid/cab_ir.py, hybrid/receptive_field.py, hybrid/a2_training_settings.py, and hybrid/kaggle_training.py against synthetic signals and mocked renders — none of it requires torch, neural-amp-modeler, or the native nam_render tool to be built. tests/test_render.py exercises real NAM inference and is skipped automatically unless both native/nam_render has been built (see its README) and a real .nam file is present at assets/nam_models/FenderSuperReverb1977_Clean.nam. tests/test_receptive_field_parity.py loads the local and Kaggle trainers side by side and fails the suite if their receptive-field policies ever diverge, rather than letting that drift go unnoticed.

For repeatable opt-in real-render coverage across all three design modes, use the explicit harness rather than relying on that personal-model test:

python3 scripts/real_render_regression.py \
  --amp-a assets/test_cabs/Clean_NoCab_Fender_Deluxe_Reverb_Head_2.nam \
  --amp-b 'assets/test_cabs/HighGain_NoCab_SLASH AFD#2 Head.nam' \
  --di assets/di/high_thrash.wav --di assets/di/clean_smooth.wav \
  --cab 'assets/nam_models/V30 LL 4FB 4x12 SM57 1.00in 0.0in SA73.wav' \
  --out-dir work/real-render-regression --release

It writes listenable stems and real_render_report.json beneath ignored work/; --release fails if prerequisites are missing or a checked case is invalid/silent. The Phase 4 implementation was exercised with this command in release mode: 6 cases and 36 listenable artifacts passed, with no invariant or determinism failures. Calibration combinations remain explicitly unavailable when both source models lack input_level_dbu metadata.

torch/neural-amp-modeler are no longer in requirements.txt — inference is handled entirely by the native nam_render tool now. They only matter for local A2 training, and live in requirements-training.txt instead (install into a separate, supported-Python-version environment when you get to that step).

Relationship to NAMtoClo

This project reuses the genre/style DI WAV files bundled in the NAMtoClo fork this repository's author also maintains, purely as audio fixtures (see assets/di/README.md). It shares no code with NAMtoClo and solves a completely different problem — NAMtoClo converts a single existing NAM model to Valeton hardware's CLO format; this project builds new hybrid NAM training material out of two existing NAM models. Nothing here depends on NAMtoClo at runtime.

NAM Tools: output volume and metadata

The Tools tab can open a NAM from your computer or select the latest model generated locally by this app. It always creates a new download and never overwrites the source file.

The output-volume tool applies 10 ** (dB / 20) to the recognised final audio head_scale. head_scale is the output scale after the model has generated its signal, so this changes output level without retraining or changing the learned tone, distortion, dynamics, or input response. It also updates each available metadata.loudness by the same dB amount. It deliberately does not change metadata.gain: that describes separate metadata/calibration intent, not the final output scale.

Modern A2 SlimmableContainer NAMs have one final audio model per submodel; the tool edits only config.submodels[*].model.config.head_scale. Older single-model files are supported only when their root config.head_scale is present. Unknown layouts are refused rather than guessed. Before saving, a recursive JSON diff must match exactly the approved output-scale and loudness paths; weights and every other model field are therefore unchanged.

Examples: enter +3, +6, or -6 in the Output volume field. A +6 dB change uses a multiplier of approximately 1.995262; -6 dB uses 0.501187. Boosts above +12 dB are allowed but may clip in a host or target hardware.

The same safe operation is available from a terminal:

python3 scripts/nam_volume.py Mesa_Boogie.nam +3
python3 scripts/nam_volume.py Mesa_Boogie.nam +6 --dry-run
python3 scripts/nam_volume.py Mesa_Boogie.nam -6 --output Mesa_Boogie_quieter.nam

The metadata editor loads and can safely change every official NAM A2 UserMetadata field: name, modeled-by, gear type/make/model, tone type, and input/output dBu. Export date, trainer details, and measured loudness remain untouched; use the Output volume slider for loudness. New NAMs identify their creator as NAM Mixer; physical gear make/model and tone metadata are left blank until the user can provide factual values.

License and attribution

NAM Mixer is copyright © 2026 Andrzej Marczewski and is released under the MIT License. The bundled genre/style DI recordings are credited and documented separately in assets/di/README.md; their upstream terms continue to apply. Neural Amp Modeler, NAMCore, and other third-party components retain their own copyrights and licenses.

About

Blend two Neural Amp Modeler captures into one new amp — playable live, and trainable into a single standalone .nam model.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages