# NIH-plug Template Minimal scaffold for new [NIH-plug](https://github.com/robbert-vdh/nih-plug) audio plugins (Rust + CLAP/VST3 + egui). Based on patterns from [`musiikki/codename-206`](http://192.168.2.199:3005/musiikki/codename-206). ## Quick Start ```bash # Clone this repo as your starting point git clone http://192.168.2.199:3005/musiikki/nih-plug-template my-plugin cd my-plugin # Edit: src/lib.rs, Cargo.toml, bundler.toml — replace placeholder values # See musiikki/codename-206 for a real-world example of how to fill these in # Then verify the rename landed everywhere sh check-branding.sh ``` ## Renaming Identity is duplicated across `Cargo.toml`, `bundler.toml`, `src/lib.rs`, `NOTICE`, `Cargo.lock`, the CI workflow and the docs. Nothing makes those agree, so a half-finished rename builds a bundle under one name while CI bundles another. `branding.conf` holds the canonical identity once. Two scripts read it: ```bash sh brand-plugin.sh --display "Fuzz Box" # derive, validate, show the plan (dry run) sh brand-plugin.sh --display "Fuzz Box" --apply sh check-branding.sh # exit 0 = consistent, 1 = drift ``` **`brand-plugin.sh` changes names.** It derives the package name and Rust types from the display name, validates them against what Cargo actually accepts, rewrites every tracked file, appends the retired spellings to `STALE`, and finishes by running the checker — a run that ends green is a rename that landed. It is a dry run unless you pass `--apply`. **`check-branding.sh` verifies them.** Both directions: every canonical value is asserted present where it belongs, *and* no retired spelling survives anywhere. Absence alone is not enough — renaming to the wrong new spelling would pass a stale-sweep and still break CI. Three things it will refuse or warn about, all learned the hard way: - **A package name Cargo rejects.** Names cannot start with a digit (`206 Prototype` → `206_prototype`, rejected), be a Rust keyword, or collide with a built-in crate. - **Moving frozen identity.** `CLAP_ID` and `VST3_CLASS_ID` are how hosts recognise saved projects and presets. Change either and every existing session silently loses the plugin — the track comes up empty, with no error. Pick them once before the first release; after that they never move. `--allow-identity-change` is required to touch them, and the helper proposes a valid 16-byte class ID rather than deriving one, because that choice is yours. - **Development history in `CLAP_DESCRIPTION`.** Hosts show it to users who never knew your codename. Any retired name or the repo's own codename appearing there is rejected. The repo name is deliberately *not* derived. It is fixed when the repo is created and keeps its development codename while the plugin gets a product name; `CODENAME` in `branding.conf` records it so it can be kept out of user-facing text. Checks for files the template doesn't ship (a `deploy.ps1`, say) report as `skip` and start enforcing once you add them. ## Build ```bash # Install Rust via rustup: https://rustup.rs # No VST3 SDK needed — NIH-plug bundles its own bindings. # Build + bundle the CLAP (default) cargo xtask bundle nih_plug_template --release # Add the VST3 as well — see the licensing note below before you do this cargo xtask bundle nih_plug_template --release --features vst3 ``` **Output:** `target/bundled/.clap` (and `.vst3` with `--features vst3`), where `` is the `name` set in `bundler.toml` — **not** the crate name. Out of the box that means `target/bundled/Plugin Name.clap`. On Linux the egui editor needs the GUI development headers: ```bash sudo apt install pkg-config libgl-dev libx11-dev libxcursor-dev libxcb1-dev \ libxcb-icccm4-dev libxcb-dri2-0-dev libx11-xcb-dev libxkbcommon-dev libxkbcommon-x11-dev ``` ## Deploy ### FL Studio Build with `--features vst3` first if you want the VST3 (see the licensing note below). FL Studio scans `C:\Program Files\Common Files\VST3` by default, **ignores directory junctions** (so a symlinked bundle is invisible to its scanner), and caches failed scans. 1. Copy the `.vst3` bundle to a folder FL scans (e.g. `C:\Program Files\Common Files\VST3`). 2. Copy the `.clap` bundle to a folder FL scans (e.g. `C:\Program Files\Common Files\CLAP`). 3. In FL Studio: **Options → Manage plugins → tick "Rescan previously failed plugins" → Find installed plugins**, then search for your plugin name (set in `bundler.toml`). ### Linux ```bash # CLAP cp -r "target/bundled/Plugin Name.clap" ~/.clap/ # VST3 (only exists if you built with --features vst3) cp -r "target/bundled/Plugin Name.vst3" ~/.vst3/ # Or globally: # sudo cp -r "target/bundled/Plugin Name.clap" /usr/lib/clap/ # sudo cp -r "target/bundled/Plugin Name.vst3" /usr/lib/vst3/ ``` ## Project Structure ``` Cargo.toml # Package deps + the opt-in `vst3` feature — NIH-plug pinned to codename-206's rev bundler.toml # Bundle name for `cargo xtask bundle` src/lib.rs # Plugin skeleton — params, DSP, and egui editor, with one example gain param xtask/ # Bundler xtask (cargo xtask bundle) .cargo/config.toml # xtask alias branding.conf # Canonical identity — the one place names are declared check-branding.sh # Asserts every name is present where it belongs and no old one survives brand-plugin.sh # Renames the plugin everywhere, then verifies with the checker .gitattributes # Pins *.sh to LF so Windows checkouts keep a valid shebang .github/workflows/ # CI: branding check, both feature sets build, test, bundle ``` ## CI `.github/workflows/ci.yaml` gates pushes and PRs on the branding check, a build of both feature sets, `cargo test`, and a bundle — enough to catch a rename that only half-landed, a `cfg(feature = "vst3")` gate that stopped compiling, or a `bundler.toml` that no longer matches the crate name. It targets `runs-on: rust-audio`, an **org-scoped** runner label. A plugin cloned into a different org will not see that runner and its jobs will queue indefinitely rather than fail — register a runner advertising the label for that org, or change `runs-on`. Deliberately not covered: plugin performance. Realtime cost can only be measured meaningfully against a real audio deadline, and `Plugin::process()` cannot be called from a test at all (`ProcessContext` requires a `Transport`, whose constructor and fields are `pub(crate)` to nih-plug). Benchmark your DSP types directly if you need it. ## License **GPL-3.0-or-later.** See `LICENSE` for the terms and `NOTICE` for third-party attribution. Plugins cloned from this template inherit GPL-3.0-or-later, because the template's own code is under it. If you need a permissive plugin, this is the wrong starting point — write the skeleton yourself against NIH-plug directly, which is ISC. Parts of `src/lib.rs` derive from Robbert van der Helm's NIH-plug cookiecutter template and `gain` example, both ISC. ISC is GPL-compatible, so that notice is retained in `NOTICE` rather than superseded. ## VST3 and Steinberg `vst3-sys`, which `nih_export_vst3!()` needs, is GPLv3-or-later — chosen by its own authors, who describe their port as a derivative of Steinberg's SDK that "was not made in isolation." That obligation is what makes a distributed VST3 build GPL-licensed, and it holds regardless of any question about Steinberg's own rights. Being GPL-3.0-or-later already, this template satisfies it. No Steinberg SDK source is vendored or redistributed here, so there is nothing to accept at build time. Separately from copyright, **"VST" is a trademark of Steinberg Media Technologies GmbH**; describing a distributed product as a VST plugin is governed by their trademark terms. If you intend to distribute commercially under that name, that is the part worth legal advice. CLAP carries none of this — no GPL dependency and no trademark encumbrance.