# 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. Everything for it lives in `tools/`: ``` tools/branding.conf the names, and what derives from what tools/branding-sites.tsv where each name has to appear, and in what form tools/brand-plugin.sh changes names tools/check-branding.sh verifies them ``` ```bash sh tools/brand-plugin.sh # interactive; Enter keeps a value sh tools/brand-plugin.sh --display "Fuzz Box" # or non-interactive; dry run sh tools/brand-plugin.sh --display "Fuzz Box" --apply sh tools/check-branding.sh # exit 0 = consistent, 1 = drift ``` **One name drives the rest.** `PLUGIN_NAME` derives the package name (`Fuzz Box` → `fuzz_box`), the Rust types (`FuzzBox`, `FuzzBoxParams`) and the display name. Each is overridable in `branding.conf`, because the derivation is sometimes wrong and sometimes outright invalid — `206 Prototype` derives to `206_prototype`, which Cargo rejects since a package name cannot start with a digit. The helper refuses those, along with Rust keywords and built-in crate names. **The checker does both halves.** Every canonical value is asserted *present* where it belongs, and every retired spelling is swept for. Absence alone is not enough: renaming to the wrong new spelling would pass a stale-sweep and still break CI. **Two things it will not do for you.** `CLAP_ID` and `VST3_CLASS_ID` are how hosts recognise saved projects and presets — change either and every existing session silently loses the plugin, with no error. They need `--allow-identity-change`, and the 16-byte class ID is *proposed* rather than derived, because that choice is yours. And `CLAP_DESCRIPTION` is rejected if it carries a retired name or the repo codename: hosts show it to users who never knew your development history. **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` records it so it can be kept out of user-facing text. Adding a site the template doesn't ship is one row in `branding-sites.tsv` with owner `custom` — no shell to edit, and it survives regeneration. Rows the helper owns are rewritten on each rename; custom rows are carried through and value-substituted like any other file. Both files can be hand-edited; the helper is what keeps them agreeing, so run the checker after if you do. ## 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 tools/ # Branding toolkit — see Renaming .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.