- C++ 76.6%
- Starlark 19.1%
- Shell 2.6%
- Python 1.3%
- Swift 0.4%
|
All checks were successful
ci / macos (push) Successful in 22s
ci / default (push) Successful in 2m47s
ci / opt (push) Successful in 2m48s
ci / asan (push) Successful in 2m53s
ci / ubsan (push) Successful in 2m55s
ci / tsan (push) Successful in 2m55s
ci / pi (push) Successful in 3m7s
ci / coverage (push) Has been skipped
Reviewed-on: #4 |
||
|---|---|---|
| .forgejo/workflows | ||
| examples | ||
| platforms | ||
| plugin_kit | ||
| rules | ||
| third_party | ||
| tools | ||
| .bazelignore | ||
| .bazelrc | ||
| .bazelversion | ||
| .clangd | ||
| .gitignore | ||
| BUILD.bazel | ||
| LICENSE | ||
| MODULE.bazel | ||
| MODULE.bazel.lock | ||
| README.md | ||
| suite.bzl | ||
| THIRD_PARTY_NOTICES.md | ||
| version.bzl | ||
plugin-kit
What every audio plugin of mine is built from, as a Bazel module: a plugin's repository depends on it, and holds only its own sound, window and names. Each plugin it builds is a CLAP and a VST3 on Linux and macOS, and an Audio Unit (AUv3) on macOS. A fix here reaches every plugin when its repository moves its pin forward.
plugin-template is the repository to start from. It's a suite of one plugin, set up as described below.
What is here
| Where | What |
|---|---|
rules/plugin.bzl |
audio_plugin: one plugin's .clap, .vst3, Audio Unit, and the tests every plugin gets |
rules/suite.bzl |
plugin_suite: every plugin of a repository, as one Mac app and disk image, one install target and one pair of Linux packages, with any files of its own under /usr/share/<package> |
plugin_kit/plugin/ |
plugin.h, the class a plugin subclasses; the CLAP entry and factory; notes.h, which turns CLAP notes, MIDI and MIDI 2.0 into MIDI 1.0 at each event's sample; the window's X11 and Cocoa sides; thread-to-thread helpers |
plugin_kit/ui/ |
the canvas and faces a view draws with, view.h (what a plugin's view is), and the placeholder view |
plugin_kit/auv3/ |
the Audio Unit in front of a plugin, and the app that carries a suite's |
plugin_kit/testing/ |
the tests every plugin gets, and its window-to-PNG tool |
examples/starter/ |
the skeleton plugin: the kit's own test plugin, and what new_plugin copies |
tools/new_plugin.sh |
adds a plugin to a repository |
third_party/, MODULE.bazel |
CLAP, the VST3 SDK, clap-wrapper, stb_truetype and the Goldman font, by hash |
A plugin
A plugin is a directory with a cc_library that subclasses plugin_kit::Plugin and
defines plugin_kit::MakePlugin, plus one audio_plugin call:
load("@plugin_kit//rules:plugin.bzl", "PLUGIN_COPTS", "audio_plugin")
cc_library(
name = "plugin",
srcs = ["plugin.cc"],
copts = PLUGIN_COPTS,
deps = ["@plugin_kit//:plugin"],
)
audio_plugin(
name = "tapeecho",
display_name = "Tape Echo",
id = "com.djehuti.tapeecho",
kind = "effect", # or "instrument"
version = VERSION,
vendor = "djehuti",
au_manufacturer = "Djht",
au_subtype = "Tpec",
icon = "tapeecho.icns",
deps = [":plugin"],
)
class TapeEcho final : public plugin_kit::Plugin {
public:
explicit TapeEcho(const clap_host_t* host) : Plugin(host) {}
protected:
void Process(const float* in_left, const float* in_right, float* out_left,
float* out_right, int frames) override;
void Midi(const plugin_kit::MidiMessage& message) override;
};
std::unique_ptr<plugin_kit::Plugin> plugin_kit::MakePlugin(const clap_host_t* host) {
return std::make_unique<TapeEcho>(host);
}
plugin.h lists what else a plugin may override: Activate, Reset, Event for
parameters and the transport, Extension for CLAP's state, parameters and the rest,
and MakeView for its window. A plugin that takes the host's events itself, to batch
them or to send its own, overrides ProcessBlock and is handed the whole of CLAP's
process; ProcessingStarted, ProcessingStopped, OnMainThread and WindowHidden
are CLAP's calls of those names.
audio_plugin also takes what only some plugins need: features, CLAP's beyond the
kind's; note_input = False, for an effect that hears nothing played;
entry_hooks = True, when the plugin's code defines plugin_kit/plugin/entry_hooks.h's
work at the entry's init and deinit and further factories, as one for presets; and
for the Audio Unit, au_state_key, a key its saved state had before, and
au_home_directory, a directory of the user's that its sandbox is let at, and
au_bundle_suffix, what its extension's bundle identifier had after the app's before.
plugin_kit/plugin/audio_unit_ext.h is what an Audio Unit can ask that CLAP cannot:
a new parameter list with the sound going on, a tail at a tempo, and user presets. The kit handles audio ports by kind, the note port,
splitting the block at each event's sample, the window, the entry, the processor
check, the VST3 through clap-wrapper and the Audio Unit.
bazel run @plugin_kit//tools:new_plugin -- NAME SUBTYPE effect|instrument writes such
a directory from examples/starter.
A repository that uses it
Its MODULE.bazel has:
bazel_dep(name = "plugin_kit", version = "0.1.0")
git_override(
module_name = "plugin_kit",
commit = "...",
remote = "https://forge.djehuti.com/bcox/plugin-kit.git",
)
It also has the toolchain, which only the root module may declare: toolchains_llvm's
clang, a Debian 12 sysroot from @plugin_kit//tools/linux:debian.bzl, and its
toolchain constrained to @plugin_kit//platforms:debian12. plugin-template's
MODULE.bazel is the one to copy. Its .bazelrc names
@plugin_kit//platforms:linux_x86_64 for --config=linux_x86_64, and
tools/workspace_status.sh for the commit a window shows.
At its top it has suite.bzl (the suite's identity), version.bzl, plugins.bzl
(the list) and a BUILD.bazel calling plugin_suite, all as plugin-template has
them.
To work on the kit and a plugin together, build the plugin with
--override_module=plugin_kit=../plugin-kit. Then move the pin once the kit's change
is committed.
Building the kit itself
bazel test //... # its own tests, and examples/starter as a plugin
bazel test --config=asan //... # also opt, ubsan, tsan
bazel test --test_env=DISPLAY --test_env=XAUTHORITY //... # and the X11 tests
tools/coverage.sh # line coverage, into coverage/
bazel build --config=linux_x86_64 --config=opt --strip=always //examples:packages
bazel build --config=pi //... # for the Raspberry Pi 3B, aarch64 Linux
--config=pi cross-builds for the Pi as ../mini-800 does: the same clang
targeting aarch64 against Debian 12's arm64 glibc, with LLVM's aarch64 libc++
linked statically, its toolchain registered first (MODULE.bazel). It builds
only, as nothing here can run aarch64 code, so a .vst3 for it has no
moduleinfo.json. A repository that builds for the Pi copies that toolchain and
//platforms:pi3_aarch64; the packages say arm64 there.
CI (.forgejo/workflows/ci.yml) runs six lanes on a push to main and on a pull
request: the default build with -Werror, opt, asan, ubsan and tsan, each
tools/ci_test.sh in a Debian 12 container with Xvfb for the X11 tests, which
skip without a display, and pi, which builds everything for the Pi with
-Werror and runs nothing. On a pull request a coverage job follows them and posts
the coverage of the changed lines on the pull request. Every job asks for a runner
labeled x86-64-v3, since the tests run the plugins, which need AVX2 and FMA; a
plugin repository's CI needs such a runner too, and for TSan one whose kernel has
vm.mmap_rnd_bits at 28 or less (.bazelrc says why). The sanitizer lanes leave
out tests tagged no-sanitizer, which is the per-plugin old_cpu_test, run under
qemu.
Under a sanitizer, --define=plugin_kit_sanitizer=1 links a plugin's shared object
without -z defs, since the runtime is the loading program's. A plugin repository
with lanes of its own sets that define too (the kit's .bazelrc shows how).
The Audio Unit's code and the Mac's rules build only on macOS, so CI has a macos
job too, on the runner labeled macos-arm64: bazel test --copt=-Werror //...,
and on a pull request tools/coverage.sh, whose total the coverage comment gives
beside the Linux one and whose report is the coverage-macos artifact. The kit is
its own root for this, with the toolchain as a dev dependency.
License
Released into the public domain under CC0 1.0 Universal. Third-party parts keep their own terms, listed in THIRD_PARTY_NOTICES.md.