Files
nih-plug-template/README.md
T
mikkeli 8c24b83ea3 refactor: move branding into tools/ and drive the checker from a table
Three changes that only make sense together.

**tools/**. check-branding.sh, brand-plugin.sh and branding.conf were
accumulating in the repo root, and a deploy.sh/bat/ps1 set would land
there too. They now live in tools/ and resolve the repo root explicitly
rather than assuming their own directory is it, so they behave the same
however they are invoked.

**One name drives the rest.** branding.conf now derives the package name,
the Rust types and the display name from PLUGIN_NAME, each overridable.
Derivation-by-default removes a class of drift; the override matters
because the derivation is sometimes invalid — "206 Prototype" derives to
"206_prototype", which cargo rejects outright — and because PLUGIN_NAME is
cosmetic while the package name is structural. A display tweak should not
silently rename the crate, its bundle lookup, the CI command and the
lockfile. This template overrides three of them, which a real plugin
normally will not: its names are the template's own rather than derived
from a product name.

An overridden PKG that no longer matches the derivation is reported at
info level, never failed. The override is usually deliberate; a display
rename that forgot the package looks identical from here, and is not.

**The site table is data.** branding-sites.tsv lists every place a name
must appear and in what form; check-branding.sh iterates it instead of
carrying 29 check calls in shell. The scripts are now identical across
every plugin built from this template, with only the table and conf
differing — which matters because the template and codename-206 had
already diverged by two checks, so every sync was a shell merge.

brand-plugin.sh regenerates the rows it owns and preserves the rest, so a
hand-added row for a file this template does not ship survives a rename.
Custom rows are value-substituted like any other tracked file, so they
track the rename rather than going stale — the first cut excluded the
whole table from substitution and got that wrong.

brand-plugin.sh also gained an interactive mode: run it with no arguments
and it prompts for each value with the current one as the default, so
Enter keeps it and a run that answers nothing changes nothing. It only
engages when there is a terminal to ask at, so CI gets the usage message
and exit 2 rather than a hang.

Verified by applying a full rename to a sandboxed copy: 8 files rewritten,
29 owned rows regenerated, 1 custom row preserved and substituted, checker
green at 48/0/7, and the result compiles as bari_206.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHt4DykxXaQnyaJfyS8KCn
2026-09-01 10:15:34 +09:00

177 lines
7.8 KiB
Markdown

# 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/<name>.clap` (and `<name>.vst3` with `--features vst3`), where
`<name>` 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.