Files
nih-plug-template/README.md
T
mikkeli 054deea900 ci: gate pushes on a build of both feature sets
Adds the minimal check a plugin repo actually needs: does it still build,
and do the identity strings still agree.

Both feature sets are built rather than just the default, because the
`vst3` cfg gates are exactly the kind of thing that rots — a default build
never compiles the Vst3Plugin impl, so a break there would otherwise
surface only when someone tried to ship a VST3. The bundle step is
included for the same reason: `cargo build` never exercises the xtask
bundler, and bundler.toml drifting from the crate name is invisible until
you try to produce an artifact.

The branding check runs first because it needs no toolchain and finishes
in under a second, so a half-landed rename fails before a build is spent
on it.

Performance testing is deliberately absent. Realtime cost is only
meaningful against an 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, so no external crate
can implement the trait. A static lint for locks in the audio path was
considered and dropped — `lock` and `borrow` are ordinary identifiers, not
reserved words, so a substring search matches every occurrence of "block"
in DSP code while missing `println!`, `recv()` and `Condvar::wait()`,
which are the likelier offenders. `assert_process_allocs` remains the real
guarantee, and it only fires in a debug build inside a host.

Verified by running the exact step sequence as the gitea-runner user under
the service's environment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHt4DykxXaQnyaJfyS8KCn
2026-08-31 22:27:35 +09:00

6.7 KiB

NIH-plug Template

Minimal scaffold for new NIH-plug audio plugins (Rust + CLAP/VST3 + egui).

Based on patterns from musiikki/codename-206.

Quick Start

# 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

Branding is duplicated across Cargo.toml, bundler.toml, src/lib.rs and the docs, and nothing makes those agree — so a half-finished rename builds a bundle under one name while the docs and deploy script look for another.

check-branding.sh declares the identity once at the top and asserts every copy matches:

sh check-branding.sh      # exit 0 = consistent, 1 = drift found

It covers the identity a host actually sees (package and bundle names, Plugin::NAME, VENDOR, EMAIL, CLAP_ID, VST3_CLASS_ID, the descriptions) and the Rust type names (MyPlugin, PluginParams) at every site they appear. Renaming the types is cosmetic — no host ever sees them — but leaving MyPlugin in a shipped plugin is the kind of residue that says nobody read the file. Leave them as-is and those checks simply stay green.

To rename: edit the canonical block at the top of the script, run it, and fix everything it reports. The placeholder spellings are pre-seeded into its STALE list, and each one arms itself as soon as you change the matching canonical value — so leftovers surface without you having to track them. It also asserts VST3_CLASS_ID is exactly 16 bytes and that CLAP_ID is reverse-DNS safe, both of which are otherwise a compile error or a silent identity change between machines.

Checks for files the template doesn't ship (e.g. a deploy.ps1) report as skip and start enforcing once you add them.

Build

# 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:

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

# 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
check-branding.sh   # Asserts the plugin's identity strings agree everywhere (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.