Compare commits
15 Commits
6ab9fb9c3f
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 74dfdc50cc | |||
| 94d41d5c8a | |||
| b27ef57676 | |||
| c940f31ec7 | |||
| a8f1244b00 | |||
| d107127017 | |||
| d51de80de8 | |||
| 7a92485cf1 | |||
| 6c0332d79e | |||
| 309a152d58 | |||
| a62efe44b3 | |||
| 3a144064e3 | |||
| 3161aa9388 | |||
| d87934c428 | |||
| 1361d7c919 |
+19
-3
@@ -1,4 +1,20 @@
|
|||||||
# Shell scripts must keep LF endings. core.autocrlf=true on Windows would
|
# Line endings: LF in the repository and in every working tree, on every OS.
|
||||||
# otherwise check them out CRLF, which a strict POSIX sh (dash, CI containers)
|
#
|
||||||
# rejects at the shebang.
|
# This file is the policy, not each machine's git config. Git for Windows installs
|
||||||
|
# with core.autocrlf=true, which checks text out as CRLF; an attribute here beats
|
||||||
|
# that setting, so a fresh clone gets LF regardless of how its machine is set up.
|
||||||
|
#
|
||||||
|
# It matters beyond tidiness. tools/branding-sites.tsv is read line by line with
|
||||||
|
# `read`, and under CRLF every row's last field gained a trailing CR byte - so the
|
||||||
|
# branding checker reported all 29 sites as missing on Windows while passing on the
|
||||||
|
# Linux CI runner. text=auto leaves genuinely binary files alone.
|
||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
# Shell scripts are pinned explicitly as well: a strict POSIX sh (dash, CI
|
||||||
|
# containers) rejects a CRLF shebang outright rather than misbehaving quietly.
|
||||||
*.sh text eol=lf
|
*.sh text eol=lf
|
||||||
|
|
||||||
|
# The exception. cmd.exe misparses LF-only batch files around labels and long
|
||||||
|
# lines, so these alone are checked out CRLF. The repository copy is still LF.
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.cmd text eol=crlf
|
||||||
|
|||||||
@@ -9,9 +9,25 @@ on:
|
|||||||
- 'Cargo.toml'
|
- 'Cargo.toml'
|
||||||
- 'Cargo.lock'
|
- 'Cargo.lock'
|
||||||
- 'bundler.toml'
|
- 'bundler.toml'
|
||||||
- 'check-branding.sh'
|
- 'tools/**'
|
||||||
|
# This workflow validates itself: a broken step, a moved script path or a
|
||||||
|
# wrong runner label then fails loudly on the commit that caused it, rather
|
||||||
|
# than surfacing later against an unrelated source change. Note the limit -
|
||||||
|
# it checks the job body, not the trigger block above. Break `on:` and the
|
||||||
|
# symptom is silence, which looks like success.
|
||||||
|
- '.github/workflows/**'
|
||||||
|
# Mirrors the push filter. Without it a PR touching only the README would run a
|
||||||
|
# full build - the branch filter alone constrains where, not what.
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'xtask/**'
|
||||||
|
- 'Cargo.toml'
|
||||||
|
- 'Cargo.lock'
|
||||||
|
- 'bundler.toml'
|
||||||
|
- 'tools/**'
|
||||||
|
- '.github/workflows/**'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
# Target the self-hosted Rust/audio runner registered to the `musiikki` org.
|
# Target the self-hosted Rust/audio runner registered to the `musiikki` org.
|
||||||
@@ -40,7 +56,7 @@ jobs:
|
|||||||
# Cheap and needs no toolchain, so it runs first: a rename that only
|
# Cheap and needs no toolchain, so it runs first: a rename that only
|
||||||
# landed in half the files is worth catching before spending a build on it.
|
# landed in half the files is worth catching before spending a build on it.
|
||||||
- name: Branding consistency
|
- name: Branding consistency
|
||||||
run: sh check-branding.sh
|
run: sh tools/check-branding.sh
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --release
|
run: cargo build --release
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ members = ["xtask"]
|
|||||||
# `cdylib` is the plugin binary; `lib` (rlib) lets `cargo test` link the unit tests.
|
# `cdylib` is the plugin binary; `lib` (rlib) lets `cargo test` link the unit tests.
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
||||||
|
# Future-incompatibility warnings are deferred hard errors: rustc is telling you
|
||||||
|
# this code stops compiling on some later release. Denying the group turns that
|
||||||
|
# into a build failure the day it appears, rather than 16 of them accumulating
|
||||||
|
# unnoticed until a toolchain bump breaks the build on a day you did not choose.
|
||||||
|
# Style and clippy lints are deliberately untouched - they stay warnings.
|
||||||
|
[lints.rust]
|
||||||
|
future_incompatible = { level = "deny", priority = -1 }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Pinned to the exact commit of the local ./nih-plug reference clone so the build
|
# Pinned to the exact commit of the local ./nih-plug reference clone so the build
|
||||||
# is reproducible and matches the examples we read from. Bump the `rev` together
|
# is reproducible and matches the examples we read from. Bump the `rev` together
|
||||||
|
|||||||
@@ -1,217 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
# check-branding.sh - verify the plugin's branding/identity strings agree everywhere.
|
|
||||||
#
|
|
||||||
# Plugin branding is duplicated across the build manifest, the bundler manifest,
|
|
||||||
# the source, the docs and the deploy script. Nothing makes them agree, so a
|
|
||||||
# rename drifts silently: the bundle builds under one name and the deploy script
|
|
||||||
# looks for another. This asserts they match, and that the identity constants
|
|
||||||
# are legal.
|
|
||||||
#
|
|
||||||
# Kept in sync with musiikki/nih-plug-template's copy - fixes belong there
|
|
||||||
# first, then come back here. The differences are structural, not stylistic:
|
|
||||||
# this repo keeps its params in src/params.rs and wraps CLAP_DESCRIPTION.
|
|
||||||
#
|
|
||||||
# Usage: sh check-branding.sh (exit 0 = consistent, 1 = drift found)
|
|
||||||
#
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# CANONICAL IDENTITY - the single source of truth. Edit these to rename, then
|
|
||||||
# run this script; every failure it prints is a place still holding the old name.
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
PKG="bari_206" # Cargo package name == bundler.toml table key
|
|
||||||
DISPLAY="Bari 206" # bundle name -> "<DISPLAY>.vst3" / "<DISPLAY>.clap"
|
|
||||||
PLUGIN_NAME="Bari 206" # Plugin::NAME - what the DAW displays
|
|
||||||
VENDOR="Novoyuuparosk" # Plugin::VENDOR - the org, not the person
|
|
||||||
EMAIL="mikkeli@novoyuuparosk.org" # Plugin::EMAIL
|
|
||||||
HOMEPAGE="https://github.com/mikkeli/codename-206" # Cargo homepage -> Plugin::URL
|
|
||||||
CLAP_ID="org.novoyuuparosk.bari-206" # CLAP_ID - stable plugin identity
|
|
||||||
VST3_CLASS_ID="Bari206Multiband" # VST3_CLASS_ID - stable identity, MUST be 16 bytes
|
|
||||||
DESCRIPTION="A VST3/CLAP multiband compressor/limiter with a custom gain-curve display"
|
|
||||||
CLAP_DESCRIPTION="Multiband compressor/limiter (stage 3: 3-band + 'All' channel)"
|
|
||||||
|
|
||||||
# Rust type names. Cosmetic - they never reach a host - but a rename that leaves
|
|
||||||
# half of them behind is the residue that says nobody read the file.
|
|
||||||
STRUCT="Bari206" # the Plugin impl target (src/lib.rs)
|
|
||||||
PARAMS_STRUCT="Bari206Params" # the Params impl target (src/params.rs)
|
|
||||||
|
|
||||||
# Old spellings retired by the "Codename 206" -> "Bari 206" rename, plus the new
|
|
||||||
# ones so the NEXT rename is self-checking too. Each entry is skipped while it is
|
|
||||||
# still one of the canonical values above and arms the moment it stops being one.
|
|
||||||
#
|
|
||||||
# Deliberately NOT listed: the bare "codename-206". The repo, its remote and the
|
|
||||||
# homepage URL all keep that name on purpose, so it is current branding, not
|
|
||||||
# residue. The retired CLAP ID is listed in full instead, which cannot collide
|
|
||||||
# with the homepage.
|
|
||||||
STALE="codename_206
|
|
||||||
Codename 206
|
|
||||||
Codename206
|
|
||||||
Codename206Params
|
|
||||||
Codename206Maxi!
|
|
||||||
206 prototype
|
|
||||||
com.mikkeli.codename-206
|
|
||||||
bari_206
|
|
||||||
Bari 206
|
|
||||||
Bari206
|
|
||||||
Bari206Params
|
|
||||||
Bari206Multiband
|
|
||||||
org.novoyuuparosk.bari-206"
|
|
||||||
|
|
||||||
set -u
|
|
||||||
cd "$(dirname "$0")" || exit 1
|
|
||||||
|
|
||||||
pass=0; fail=0; skip=0
|
|
||||||
ok() { pass=$((pass+1)); printf ' ok %s\n' "$1"; }
|
|
||||||
bad() { fail=$((fail+1)); printf ' FAIL %s\n' "$1"; [ $# -gt 1 ] && printf ' %s\n' "$2"; return 0; }
|
|
||||||
note() { skip=$((skip+1)); printf ' skip %s\n' "$1"; }
|
|
||||||
# Reported for awareness only - never counted, never fails the run.
|
|
||||||
info() { printf ' -- %s\n' "$1"; }
|
|
||||||
|
|
||||||
# has FILE PATTERN -> fixed-string search
|
|
||||||
has() { grep -qF "$2" "$1" 2>/dev/null; }
|
|
||||||
|
|
||||||
check() { # check LABEL FILE LITERAL
|
|
||||||
if [ ! -f "$2" ]; then bad "$1" "missing file: $2"; return; fi
|
|
||||||
if has "$2" "$3"; then ok "$1"; else bad "$1" "$2 does not contain: $3"; fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Same as check, but a missing file is "not applicable yet" rather than drift.
|
|
||||||
# The template ships without a deploy script; this starts checking once you add one.
|
|
||||||
opt_check() { # opt_check LABEL FILE LITERAL
|
|
||||||
if [ ! -f "$2" ]; then note "$1 ($2 not present)"; return; fi
|
|
||||||
if has "$2" "$3"; then ok "$1"; else bad "$1" "$2 does not contain: $3"; fi
|
|
||||||
}
|
|
||||||
|
|
||||||
printf 'branding check (canonical: %s / "%s")\n\n' "$PKG" "$DISPLAY"
|
|
||||||
|
|
||||||
printf 'build manifests\n'
|
|
||||||
check "Cargo.toml package name" Cargo.toml "name = \"$PKG\""
|
|
||||||
check "Cargo.toml homepage" Cargo.toml "homepage = \"$HOMEPAGE\""
|
|
||||||
check "Cargo.toml description" Cargo.toml "description = \"$DESCRIPTION\""
|
|
||||||
check "bundler.toml table key" bundler.toml "[$PKG]"
|
|
||||||
check "bundler.toml display name" bundler.toml "name = \"$DISPLAY\""
|
|
||||||
|
|
||||||
printf '\nsource identity (src/lib.rs)\n'
|
|
||||||
check "Plugin::NAME" src/lib.rs "const NAME: &'static str = \"$PLUGIN_NAME\""
|
|
||||||
check "Plugin::VENDOR" src/lib.rs "const VENDOR: &'static str = \"$VENDOR\""
|
|
||||||
check "Plugin::EMAIL" src/lib.rs "const EMAIL: &'static str = \"$EMAIL\""
|
|
||||||
check "CLAP_ID" src/lib.rs "const CLAP_ID: &'static str = \"$CLAP_ID\""
|
|
||||||
# CLAP_DESCRIPTION wraps onto a second line here, so the const name and its
|
|
||||||
# value are never on one line. Match the value alone.
|
|
||||||
check "CLAP_DESCRIPTION" src/lib.rs "Some(\"$CLAP_DESCRIPTION\")"
|
|
||||||
check "VST3_CLASS_ID" src/lib.rs "const VST3_CLASS_ID: [u8; 16] = *b\"$VST3_CLASS_ID\""
|
|
||||||
|
|
||||||
# Plugin::URL is env!("CARGO_PKG_HOMEPAGE"), so the manifest is the only place
|
|
||||||
# it is spelled. Assert the indirection is still in place rather than hardcoded.
|
|
||||||
check "Plugin::URL from manifest" src/lib.rs "const URL: &'static str = env!(\"CARGO_PKG_HOMEPAGE\")"
|
|
||||||
|
|
||||||
# VST3 class IDs are a fixed 16-byte array. A shorter or longer literal is a
|
|
||||||
# compile error, but it is far clearer to say so here than to read rustc on it.
|
|
||||||
n=$(printf '%s' "$VST3_CLASS_ID" | wc -c | tr -d ' ')
|
|
||||||
if [ "$n" -eq 16 ]; then ok "VST3_CLASS_ID is 16 bytes"
|
|
||||||
else bad "VST3_CLASS_ID is 16 bytes" "got $n bytes: \"$VST3_CLASS_ID\""; fi
|
|
||||||
|
|
||||||
# CLAP IDs are reverse-DNS by convention and are compared verbatim by hosts, so
|
|
||||||
# a stray space or capital is a silent identity change between machines.
|
|
||||||
case "$CLAP_ID" in
|
|
||||||
*\ *|*[A-Z]*) bad "CLAP_ID is reverse-DNS safe" "no spaces or capitals allowed: \"$CLAP_ID\"" ;;
|
|
||||||
*.*) ok "CLAP_ID is reverse-DNS safe" ;;
|
|
||||||
*) bad "CLAP_ID is reverse-DNS safe" "expected dotted reverse-DNS, got: \"$CLAP_ID\"" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
printf '\nstructural names (src/lib.rs)\n'
|
|
||||||
# Every site the type names appear. A rename that misses one is a compile error
|
|
||||||
# rather than silent drift, but naming the site is faster than reading rustc,
|
|
||||||
# and the Vst3Plugin impl sits behind a cfg so a default build never reports it.
|
|
||||||
check "struct $STRUCT" src/lib.rs "struct $STRUCT {"
|
|
||||||
check "impl Plugin" src/lib.rs "impl Plugin for $STRUCT {"
|
|
||||||
check "impl ClapPlugin" src/lib.rs "impl ClapPlugin for $STRUCT {"
|
|
||||||
check "impl Vst3Plugin" src/lib.rs "impl Vst3Plugin for $STRUCT {"
|
|
||||||
check "nih_export_clap!" src/lib.rs "nih_export_clap!($STRUCT);"
|
|
||||||
check "nih_export_vst3!" src/lib.rs "nih_export_vst3!($STRUCT);"
|
|
||||||
check "struct $PARAMS_STRUCT" src/params.rs "struct $PARAMS_STRUCT {"
|
|
||||||
check "impl Default for params" src/params.rs "impl Default for $PARAMS_STRUCT {"
|
|
||||||
check "params field type" src/lib.rs "params: Arc<$PARAMS_STRUCT>,"
|
|
||||||
|
|
||||||
printf '\ndocs\n'
|
|
||||||
opt_check "README bundle command" README.md "cargo xtask bundle $PKG"
|
|
||||||
|
|
||||||
printf '\nci\n'
|
|
||||||
# CI bundles by package name, so a rename that misses this file breaks the build
|
|
||||||
# rather than drifting quietly - but it breaks after a full compile, which is a slow
|
|
||||||
# way to learn about a typo. Checking it here costs nothing and fails in seconds.
|
|
||||||
opt_check "CI bundle command" .github/workflows/ci.yaml "cargo xtask bundle $PKG"
|
|
||||||
|
|
||||||
printf '\ndeploy script\n'
|
|
||||||
opt_check "deploy.ps1 default package" deploy.ps1 "\$Package = \"$PKG\""
|
|
||||||
opt_check "deploy.ps1 VST3 bundle" deploy.ps1 "\$vst3Name = \"$DISPLAY.vst3\""
|
|
||||||
opt_check "deploy.ps1 CLAP bundle" deploy.ps1 "\$clapName = \"$DISPLAY.clap\""
|
|
||||||
|
|
||||||
printf '\nstale spellings\n'
|
|
||||||
# File list for the scan below. Prefer git, so build output and any vendored
|
|
||||||
# tree stay out of it. Fall back to find when this is not a checkout - an
|
|
||||||
# exported tarball, or a CI step that fetched without git metadata - because
|
|
||||||
# `git ls-files` there returns nothing, and an empty list would report every
|
|
||||||
# stale spelling as absent. A check that passes hardest when it can see least
|
|
||||||
# is worse than no check.
|
|
||||||
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
||||||
file_list() { git ls-files; }
|
|
||||||
scan_source="git"
|
|
||||||
else
|
|
||||||
file_list() { find . -path ./.git -prune -o -path ./target -prune -o -type f -print | sed 's|^\./||'; }
|
|
||||||
scan_source="find (not a git checkout)"
|
|
||||||
fi
|
|
||||||
info "scanning tracked files via $scan_source"
|
|
||||||
# Every canonical spelling concatenated, for the substring test below.
|
|
||||||
CANONICAL_BLOB="$PKG
|
|
||||||
$DESCRIPTION
|
|
||||||
$CLAP_DESCRIPTION
|
|
||||||
$STRUCT
|
|
||||||
$PARAMS_STRUCT
|
|
||||||
$DISPLAY
|
|
||||||
$PLUGIN_NAME
|
|
||||||
$VENDOR
|
|
||||||
$EMAIL
|
|
||||||
$HOMEPAGE
|
|
||||||
$CLAP_ID
|
|
||||||
$VST3_CLASS_ID"
|
|
||||||
# Fed by here-doc rather than a pipe on purpose: a piped `while` runs in a
|
|
||||||
# subshell, and the pass/fail counters it increments would be discarded.
|
|
||||||
while IFS= read -r s; do
|
|
||||||
[ -n "$s" ] || continue
|
|
||||||
|
|
||||||
# Still part of a canonical value? Then it is current branding, not a leftover.
|
|
||||||
# This is what lets the list ship pre-seeded: entries arm themselves as you
|
|
||||||
# rename. Substring, not equality, because placeholders nest - "your-domain"
|
|
||||||
# lives inside the canonical CLAP_ID, and "YOUR-USERNAME" inside the homepage.
|
|
||||||
# Over-skipping is the safe direction: a false FAIL on a name you still use
|
|
||||||
# would train you to ignore this section.
|
|
||||||
case "$CANONICAL_BLOB" in
|
|
||||||
*"$s"*) note "\"$s\" (still canonical)"; continue ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# This script names every placeholder by definition, so it can never be a hit.
|
|
||||||
hits=$(file_list 2>/dev/null | while read -r f; do
|
|
||||||
[ "$f" = "check-branding.sh" ] || { [ -f "$f" ] && grep -lF "$s" "$f" 2>/dev/null; }
|
|
||||||
done)
|
|
||||||
if [ -z "$hits" ]; then ok "no occurrences of \"$s\""
|
|
||||||
else bad "no occurrences of \"$s\"" "still present in: $(echo "$hits" | tr '\n' ' ')"; fi
|
|
||||||
done <<STALE_LIST
|
|
||||||
$STALE
|
|
||||||
STALE_LIST
|
|
||||||
|
|
||||||
# Template scaffolding, not drift: TODOs are legitimate while you are still
|
|
||||||
# building. Reported so a finished plugin does not quietly ship with them.
|
|
||||||
# `grep -c` prints 0 AND exits 1 when there are no matches, so `|| echo 0`
|
|
||||||
# would append a second zero and make the count unparseable. Swallow the
|
|
||||||
# status instead, and default an empty result (missing file) to zero.
|
|
||||||
todos=$(grep -c 'TODO' src/lib.rs 2>/dev/null || :)
|
|
||||||
[ -n "$todos" ] || todos=0
|
|
||||||
printf '\nscaffolding\n'
|
|
||||||
if [ "$todos" -eq 0 ]; then ok "no TODO markers left in src/lib.rs"
|
|
||||||
else info "$todos TODO marker(s) still in src/lib.rs (not a failure)"; fi
|
|
||||||
|
|
||||||
printf '\n%d passed, %d failed' "$pass" "$fail"
|
|
||||||
[ "$skip" -gt 0 ] && printf ', %d skipped' "$skip"
|
|
||||||
printf '\n'
|
|
||||||
[ "$fail" -eq 0 ] || exit 1
|
|
||||||
+10
-9
@@ -11,6 +11,7 @@ use nih_plug_egui::egui::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::params::Bari206Params;
|
use crate::params::Bari206Params;
|
||||||
|
use super::palette;
|
||||||
|
|
||||||
/// Height of the handle strip.
|
/// Height of the handle strip.
|
||||||
const GRAPH_H: f32 = 54.0;
|
const GRAPH_H: f32 = 54.0;
|
||||||
@@ -55,19 +56,19 @@ pub(super) fn draw(ui: &mut egui::Ui, params: &Bari206Params, setter: &ParamSett
|
|||||||
let xhi = x_for(hi);
|
let xhi = x_for(hi);
|
||||||
|
|
||||||
// Three band regions.
|
// Three band regions.
|
||||||
p.rect_filled(Rect::from_min_max(pos2(left, top), pos2(xlo, bottom)), CornerRadius::ZERO, Color32::from_rgb(28, 38, 52));
|
p.rect_filled(Rect::from_min_max(pos2(left, top), pos2(xlo, bottom)), CornerRadius::ZERO, palette::CHANNELS[0].strip);
|
||||||
p.rect_filled(Rect::from_min_max(pos2(xlo, top), pos2(xhi, bottom)), CornerRadius::ZERO, Color32::from_rgb(30, 48, 36));
|
p.rect_filled(Rect::from_min_max(pos2(xlo, top), pos2(xhi, bottom)), CornerRadius::ZERO, palette::CHANNELS[1].strip);
|
||||||
p.rect_filled(Rect::from_min_max(pos2(xhi, top), pos2(right, bottom)), CornerRadius::ZERO, Color32::from_rgb(52, 38, 30));
|
p.rect_filled(Rect::from_min_max(pos2(xhi, top), pos2(right, bottom)), CornerRadius::ZERO, palette::CHANNELS[2].strip);
|
||||||
|
|
||||||
let band_label = |cx: f32, t: &str| {
|
let band_label = |cx: f32, t: &str| {
|
||||||
p.text(pos2(cx, top + 2.0), Align2::CENTER_TOP, t, FontId::proportional(11.0), Color32::from_gray(160));
|
p.text(pos2(cx, top + 2.0), Align2::CENTER_TOP, t, FontId::proportional(11.0), palette::TEXT);
|
||||||
};
|
};
|
||||||
band_label((left + xlo) * 0.5, "LOW");
|
band_label((left + xlo) * 0.5, "LOW");
|
||||||
band_label((xlo + xhi) * 0.5, "MID");
|
band_label((xlo + xhi) * 0.5, "MID");
|
||||||
band_label((xhi + right) * 0.5, "HIGH");
|
band_label((xhi + right) * 0.5, "HIGH");
|
||||||
|
|
||||||
handle(ui, &p, setter, "xover_lo", xlo, lo, ¶ms.crossover_low_hz, lo_min, lo_upper, left, width, top, bottom, Color32::from_rgb(120, 170, 230));
|
handle(ui, &p, setter, "xover_lo", xlo, lo, ¶ms.crossover_low_hz, lo_min, lo_upper, left, width, top, bottom, palette::CHANNELS[0].handle);
|
||||||
handle(ui, &p, setter, "xover_hi", xhi, hi, ¶ms.crossover_high_hz, hi_lower, hi_max, left, width, top, bottom, Color32::from_rgb(230, 150, 120));
|
handle(ui, &p, setter, "xover_hi", xhi, hi, ¶ms.crossover_high_hz, hi_lower, hi_max, left, width, top, bottom, palette::CHANNELS[2].handle);
|
||||||
|
|
||||||
// Number boxes (double-click to type). Clamped to the same dynamic limits.
|
// Number boxes (double-click to type). Clamped to the same dynamic limits.
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
@@ -129,9 +130,9 @@ fn handle(
|
|||||||
setter.end_set_parameter(param);
|
setter.end_set_parameter(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
let col = if resp.dragged() || resp.hovered() { Color32::WHITE } else { color };
|
let col = if resp.dragged() || resp.hovered() { palette::TEXT_HOVER } else { color };
|
||||||
p.line_segment([pos2(x, top), pos2(x, bottom)], Stroke::new(2.0, col));
|
p.line_segment([pos2(x, top), pos2(x, bottom)], Stroke::new(2.0_f32, col));
|
||||||
p.text(pos2(x, bottom - 1.0), Align2::CENTER_BOTTOM, fmt_hz(hz), FontId::proportional(10.0), Color32::from_gray(220));
|
p.text(pos2(x, bottom - 1.0), Align2::CENTER_BOTTOM, fmt_hz(hz), FontId::proportional(10.0), palette::TEXT_BRIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a param to `value` clamped to `[min, max]`, wrapped in its own gesture (for the number box).
|
/// Set a param to `value` clamped to `[min, max]`, wrapped in its own gesture (for the number box).
|
||||||
|
|||||||
+14
-13
@@ -5,12 +5,13 @@
|
|||||||
//! the channel's current input level — its right edge rides the curve (width = input, height = out).
|
//! the channel's current input level — its right edge rides the curve (width = input, height = out).
|
||||||
|
|
||||||
use nih_plug::prelude::util;
|
use nih_plug::prelude::util;
|
||||||
use nih_plug_egui::egui::{self, pos2, vec2, Align2, Color32, CornerRadius, FontId, Sense, Stroke};
|
use nih_plug_egui::egui::{self, pos2, vec2, Align2, CornerRadius, FontId, Sense, Stroke};
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
use crate::dsp::compressor::Compressor;
|
use crate::dsp::compressor::Compressor;
|
||||||
use crate::meters::Meters;
|
use crate::meters::Meters;
|
||||||
use crate::params::Bari206Params;
|
use crate::params::Bari206Params;
|
||||||
|
use super::palette;
|
||||||
|
|
||||||
/// Side length of the square plot.
|
/// Side length of the square plot.
|
||||||
const CURVE_SIZE: f32 = 150.0;
|
const CURVE_SIZE: f32 = 150.0;
|
||||||
@@ -37,7 +38,7 @@ pub(super) fn draw(ui: &mut egui::Ui, params: &Bari206Params, selected: usize, m
|
|||||||
ui.label(format!("Curve: {}", labels[ch]));
|
ui.label(format!("Curve: {}", labels[ch]));
|
||||||
let (rect, _) = ui.allocate_exact_size(vec2(CURVE_SIZE, CURVE_SIZE), Sense::hover());
|
let (rect, _) = ui.allocate_exact_size(vec2(CURVE_SIZE, CURVE_SIZE), Sense::hover());
|
||||||
let p = ui.painter_at(rect);
|
let p = ui.painter_at(rect);
|
||||||
p.rect_filled(rect, CornerRadius::ZERO, Color32::from_rgb(16, 16, 20));
|
p.rect_filled(rect, CornerRadius::ZERO, palette::SURFACE);
|
||||||
|
|
||||||
let inset = 2.0;
|
let inset = 2.0;
|
||||||
let (left, right, top, bottom) =
|
let (left, right, top, bottom) =
|
||||||
@@ -48,17 +49,17 @@ pub(super) fn draw(ui: &mut egui::Ui, params: &Bari206Params, selected: usize, m
|
|||||||
let y_for = |db: f32| bottom - (db - FLOOR_DB) / -FLOOR_DB * h;
|
let y_for = |db: f32| bottom - (db - FLOOR_DB) / -FLOOR_DB * h;
|
||||||
|
|
||||||
// -6 dBFS reference lines on both axes.
|
// -6 dBFS reference lines on both axes.
|
||||||
let g6 = Color32::from_gray(38);
|
let g6 = palette::GRID_FAINT;
|
||||||
let x6 = x_for(-6.0);
|
let x6 = x_for(-6.0);
|
||||||
let y6 = y_for(-6.0);
|
let y6 = y_for(-6.0);
|
||||||
p.line_segment([pos2(x6, top), pos2(x6, bottom)], Stroke::new(1.0, g6));
|
p.line_segment([pos2(x6, top), pos2(x6, bottom)], Stroke::new(1.0_f32, g6));
|
||||||
p.line_segment([pos2(left, y6), pos2(right, y6)], Stroke::new(1.0, g6));
|
p.line_segment([pos2(left, y6), pos2(right, y6)], Stroke::new(1.0_f32, g6));
|
||||||
|
|
||||||
// Unity reference (out = in), bottom-left to top-right.
|
// Unity reference (out = in), bottom-left to top-right.
|
||||||
p.line_segment([pos2(left, bottom), pos2(right, top)], Stroke::new(1.0, Color32::from_gray(45)));
|
p.line_segment([pos2(left, bottom), pos2(right, top)], Stroke::new(1.0_f32, palette::GRID));
|
||||||
// Threshold marker on the input axis — shifted left by pre-gain (the comp sees in + pre).
|
// Threshold marker on the input axis — shifted left by pre-gain (the comp sees in + pre).
|
||||||
let tx = x_for((threshold - pre).clamp(FLOOR_DB, 0.0));
|
let tx = x_for((threshold - pre).clamp(FLOOR_DB, 0.0));
|
||||||
p.line_segment([pos2(tx, top), pos2(tx, bottom)], Stroke::new(1.0, Color32::from_rgb(80, 60, 45)));
|
p.line_segment([pos2(tx, top), pos2(tx, bottom)], Stroke::new(1.0_f32, palette::THRESHOLD_LINE));
|
||||||
|
|
||||||
// Full wet transfer: drive into the comp, then makeup. (Mix not folded in.)
|
// Full wet transfer: drive into the comp, then makeup. (Mix not folded in.)
|
||||||
let n = 96;
|
let n = 96;
|
||||||
@@ -74,7 +75,7 @@ pub(super) fn draw(ui: &mut egui::Ui, params: &Bari206Params, selected: usize, m
|
|||||||
let driven_now = util::gain_to_db(meters.input_level[ch].load(Ordering::Relaxed));
|
let driven_now = util::gain_to_db(meters.input_level[ch].load(Ordering::Relaxed));
|
||||||
let ext_in = (driven_now - pre).clamp(FLOOR_DB, 0.0); // external input -> curve x
|
let ext_in = (driven_now - pre).clamp(FLOOR_DB, 0.0); // external input -> curve x
|
||||||
let x_now = x_for(ext_in);
|
let x_now = x_for(ext_in);
|
||||||
let fill_col = Color32::from_rgba_unmultiplied(120, 200, 160, 45);
|
let fill_col = palette::with_alpha(palette::CURVE, 45);
|
||||||
for seg in pts.windows(2) {
|
for seg in pts.windows(2) {
|
||||||
let a = seg[0];
|
let a = seg[0];
|
||||||
let mut b = seg[1];
|
let mut b = seg[1];
|
||||||
@@ -92,16 +93,16 @@ pub(super) fn draw(ui: &mut egui::Ui, params: &Bari206Params, selected: usize, m
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
p.add(egui::Shape::line(pts, Stroke::new(1.6, Color32::from_rgb(120, 200, 160))));
|
p.add(egui::Shape::line(pts, Stroke::new(1.6_f32, palette::CURVE)));
|
||||||
|
|
||||||
// Operating-point dot, on the curve at the current input.
|
// Operating-point dot, on the curve at the current input.
|
||||||
let driven = ext_in + pre;
|
let driven = ext_in + pre;
|
||||||
let gr = Compressor::gain_computer(driven, threshold, ratio, knee, low_slope, low_curve);
|
let gr = Compressor::gain_computer(driven, threshold, ratio, knee, low_slope, low_curve);
|
||||||
let out_op = (driven + gr + makeup).clamp(FLOOR_DB, 0.0);
|
let out_op = (driven + gr + makeup).clamp(FLOOR_DB, 0.0);
|
||||||
p.circle_filled(pos2(x_now, y_for(out_op)), 3.0, Color32::from_rgb(235, 240, 235));
|
p.circle_filled(pos2(x_now, y_for(out_op)), 3.0, palette::OPERATING_POINT);
|
||||||
|
|
||||||
// Corner dB ticks + the -6 dB reference.
|
// Corner dB ticks + the -6 dB reference.
|
||||||
p.text(pos2(left + 1.0, top + 1.0), Align2::LEFT_TOP, "0", FontId::proportional(9.0), Color32::from_gray(90));
|
p.text(pos2(left + 1.0, top + 1.0), Align2::LEFT_TOP, "0", FontId::proportional(9.0), palette::TEXT_DIM);
|
||||||
p.text(pos2(left + 1.0, bottom - 1.0), Align2::LEFT_BOTTOM, "-60", FontId::proportional(9.0), Color32::from_gray(90));
|
p.text(pos2(left + 1.0, bottom - 1.0), Align2::LEFT_BOTTOM, "-60", FontId::proportional(9.0), palette::TEXT_DIM);
|
||||||
p.text(pos2(x6 + 2.0, bottom - 1.0), Align2::LEFT_BOTTOM, "-6", FontId::proportional(9.0), Color32::from_gray(80));
|
p.text(pos2(x6 + 2.0, bottom - 1.0), Align2::LEFT_BOTTOM, "-6", FontId::proportional(9.0), palette::TEXT_DIM);
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-19
@@ -15,6 +15,7 @@ use std::sync::atomic::Ordering;
|
|||||||
|
|
||||||
use super::METER_FLOOR_DB;
|
use super::METER_FLOOR_DB;
|
||||||
use crate::meters::{Meters, NUM_CHANNELS};
|
use crate::meters::{Meters, NUM_CHANNELS};
|
||||||
|
use super::palette;
|
||||||
|
|
||||||
/// Full-scale of the gain-reduction bar (fills downward from the top).
|
/// Full-scale of the gain-reduction bar (fills downward from the top).
|
||||||
const GR_FULL_DB: f32 = 24.0;
|
const GR_FULL_DB: f32 = 24.0;
|
||||||
@@ -47,7 +48,7 @@ pub(super) fn draw(ui: &mut egui::Ui, meters: &Meters, state: &mut MeterState) {
|
|||||||
let (rect, _) =
|
let (rect, _) =
|
||||||
ui.allocate_exact_size(vec2(ui.available_width(), METER_PANEL_H), Sense::hover());
|
ui.allocate_exact_size(vec2(ui.available_width(), METER_PANEL_H), Sense::hover());
|
||||||
let p = ui.painter_at(rect);
|
let p = ui.painter_at(rect);
|
||||||
p.rect_filled(rect, CornerRadius::ZERO, Color32::from_rgb(20, 20, 24));
|
p.rect_filled(rect, CornerRadius::ZERO, palette::SURFACE_RAISED);
|
||||||
|
|
||||||
let top = rect.top() + 22.0; // leave a row at the top for the lamps
|
let top = rect.top() + 22.0; // leave a row at the top for the lamps
|
||||||
let bottom = rect.bottom() - 18.0; // and a row at the bottom for the labels
|
let bottom = rect.bottom() - 18.0; // and a row at the bottom for the labels
|
||||||
@@ -58,6 +59,22 @@ pub(super) fn draw(ui: &mut egui::Ui, meters: &Meters, state: &mut MeterState) {
|
|||||||
|
|
||||||
for i in 0..NUM_CHANNELS {
|
for i in 0..NUM_CHANNELS {
|
||||||
let cell_left = rect.left() + i as f32 * cell_w;
|
let cell_left = rect.left() + i as f32 * cell_w;
|
||||||
|
|
||||||
|
// Channel chrome. The four clusters are otherwise identical and told apart only by the
|
||||||
|
// label underneath, which is the one place channel identity is hardest to read. A dark
|
||||||
|
// wash behind the cell plus an accent rule under the bars fixes that without touching
|
||||||
|
// the bars themselves - see `palette`: hue is chrome, functional colour is data.
|
||||||
|
let cell = Rect::from_min_max(
|
||||||
|
pos2(cell_left + 1.0, rect.top()),
|
||||||
|
pos2(cell_left + cell_w - 1.0, rect.bottom()),
|
||||||
|
);
|
||||||
|
p.rect_filled(cell, CornerRadius::ZERO, palette::CHANNELS[i].chrome);
|
||||||
|
p.rect_filled(
|
||||||
|
Rect::from_min_max(pos2(cell.left(), bottom + 2.0), pos2(cell.right(), bottom + 4.0)),
|
||||||
|
CornerRadius::ZERO,
|
||||||
|
palette::CHANNELS[i].edge,
|
||||||
|
);
|
||||||
|
|
||||||
let group_w = bar_w * 3.0 + gap * 2.0;
|
let group_w = bar_w * 3.0 + gap * 2.0;
|
||||||
let bx = cell_left + (cell_w - group_w) * 0.5;
|
let bx = cell_left + (cell_w - group_w) * 0.5;
|
||||||
|
|
||||||
@@ -71,16 +88,16 @@ pub(super) fn draw(ui: &mut egui::Ui, meters: &Meters, state: &mut MeterState) {
|
|||||||
let gr_db = meters.gain_reduction_db[i].load(Ordering::Relaxed);
|
let gr_db = meters.gain_reduction_db[i].load(Ordering::Relaxed);
|
||||||
let gr_frac = (gr_db / GR_FULL_DB).clamp(0.0, 1.0);
|
let gr_frac = (gr_db / GR_FULL_DB).clamp(0.0, 1.0);
|
||||||
|
|
||||||
v_bar(&p, bx, bar_w, top, bottom, l_frac, level_color(l_db), false);
|
v_bar(&p, bx, bar_w, top, bottom, l_frac, palette::level_color(l_db), false);
|
||||||
v_bar(&p, bx + bar_w + gap, bar_w, top, bottom, gr_frac, Color32::from_rgb(240, 150, 60), true);
|
v_bar(&p, bx + bar_w + gap, bar_w, top, bottom, gr_frac, palette::SIGNAL_GR, true);
|
||||||
v_bar(&p, bx + 2.0 * (bar_w + gap), bar_w, top, bottom, r_frac, level_color(r_db), false);
|
v_bar(&p, bx + 2.0 * (bar_w + gap), bar_w, top, bottom, r_frac, palette::level_color(r_db), false);
|
||||||
|
|
||||||
p.text(
|
p.text(
|
||||||
pos2(cell_left + cell_w * 0.5, rect.bottom() - 2.0),
|
pos2(cell_left + cell_w * 0.5, rect.bottom() - 2.0),
|
||||||
Align2::CENTER_BOTTOM,
|
Align2::CENTER_BOTTOM,
|
||||||
labels[i],
|
labels[i],
|
||||||
FontId::proportional(12.0),
|
FontId::proportional(12.0),
|
||||||
Color32::from_gray(200),
|
palette::TEXT_BRIGHT,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Per-channel lamp: latch on output reaching 0 dBFS; the ALL channel also latches when the
|
// Per-channel lamp: latch on output reaching 0 dBFS; the ALL channel also latches when the
|
||||||
@@ -109,9 +126,9 @@ pub(super) fn draw(ui: &mut egui::Ui, meters: &Meters, state: &mut MeterState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let lamp = if state.ceiling_trigger[i].is_some() {
|
let lamp = if state.ceiling_trigger[i].is_some() {
|
||||||
Color32::from_rgb(255, 40, 40)
|
palette::LAMP_ON
|
||||||
} else {
|
} else {
|
||||||
Color32::from_rgb(40, 12, 12)
|
palette::LAMP_OFF
|
||||||
};
|
};
|
||||||
p.circle_filled(lamp_center, 5.0, lamp);
|
p.circle_filled(lamp_center, 5.0, lamp);
|
||||||
}
|
}
|
||||||
@@ -120,7 +137,7 @@ pub(super) fn draw(ui: &mut egui::Ui, meters: &Meters, state: &mut MeterState) {
|
|||||||
/// Draw a vertical bar within `[top, bottom]`. `frac` is 0..1; `from_top` fills downward from the
|
/// Draw a vertical bar within `[top, bottom]`. `frac` is 0..1; `from_top` fills downward from the
|
||||||
/// top (gain reduction) instead of upward from the bottom (level).
|
/// top (gain reduction) instead of upward from the bottom (level).
|
||||||
fn v_bar(p: &Painter, x: f32, w: f32, top: f32, bottom: f32, frac: f32, fill: Color32, from_top: bool) {
|
fn v_bar(p: &Painter, x: f32, w: f32, top: f32, bottom: f32, frac: f32, fill: Color32, from_top: bool) {
|
||||||
let track = Color32::from_rgb(34, 34, 40);
|
let track = palette::TRACK;
|
||||||
p.rect_filled(Rect::from_min_max(pos2(x, top), pos2(x + w, bottom)), CornerRadius::ZERO, track);
|
p.rect_filled(Rect::from_min_max(pos2(x, top), pos2(x + w, bottom)), CornerRadius::ZERO, track);
|
||||||
|
|
||||||
let h = (bottom - top) * frac.clamp(0.0, 1.0);
|
let h = (bottom - top) * frac.clamp(0.0, 1.0);
|
||||||
@@ -131,14 +148,3 @@ fn v_bar(p: &Painter, x: f32, w: f32, top: f32, bottom: f32, frac: f32, fill: Co
|
|||||||
};
|
};
|
||||||
p.rect_filled(filled, CornerRadius::ZERO, fill);
|
p.rect_filled(filled, CornerRadius::ZERO, fill);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Level-bar colour: green below -6 dB, yellow approaching, red near 0 dBFS.
|
|
||||||
fn level_color(db: f32) -> Color32 {
|
|
||||||
if db >= -1.0 {
|
|
||||||
Color32::from_rgb(235, 70, 60)
|
|
||||||
} else if db >= -6.0 {
|
|
||||||
Color32::from_rgb(230, 200, 70)
|
|
||||||
} else {
|
|
||||||
Color32::from_rgb(90, 200, 110)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+2
-1
@@ -23,6 +23,7 @@ use crate::Bari206;
|
|||||||
mod crossover;
|
mod crossover;
|
||||||
mod gain_curve;
|
mod gain_curve;
|
||||||
mod meter;
|
mod meter;
|
||||||
|
mod palette;
|
||||||
mod plot;
|
mod plot;
|
||||||
|
|
||||||
/// Bottom of the dB scale shared by the meters and the plot (top is 0 dBFS).
|
/// Bottom of the dB scale shared by the meters and the plot (top is 0 dBFS).
|
||||||
@@ -41,7 +42,7 @@ pub(crate) fn create(params: Arc<Bari206Params>, meters: Arc<Meters>) -> Option<
|
|||||||
create_egui_editor(
|
create_egui_editor(
|
||||||
params.editor_state.clone(),
|
params.editor_state.clone(),
|
||||||
EditorState::default(),
|
EditorState::default(),
|
||||||
|_, _| {},
|
|egui_ctx, _| palette::apply_visuals(egui_ctx),
|
||||||
move |egui_ctx, setter, state| {
|
move |egui_ctx, setter, state| {
|
||||||
// Keep frames coming so the meters animate and the lamp can time out while open.
|
// Keep frames coming so the meters animate and the lamp can time out while open.
|
||||||
egui_ctx.request_repaint();
|
egui_ctx.request_repaint();
|
||||||
|
|||||||
@@ -0,0 +1,265 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
//! The plugin's colour system.
|
||||||
|
//!
|
||||||
|
//! # The rule
|
||||||
|
//!
|
||||||
|
//! **Channel hue is chrome; functional colour is data.** Before picking a colour, ask what the
|
||||||
|
//! pixel is for. Is it showing a *value* — a level, a reduction, a clip? Then it comes from the
|
||||||
|
//! functional palette, and it means the same thing in every channel. Is it telling you *which
|
||||||
|
//! channel you are looking at*? Then it comes from that channel's hue, and it never touches the
|
||||||
|
//! data itself.
|
||||||
|
//!
|
||||||
|
//! This is what keeps the two systems from fighting. An earlier version of this UI used blue,
|
||||||
|
//! green and orange for LOW/MID/HIGH in the crossover *and* for input/output/gain-reduction in
|
||||||
|
//! the scope, so orange meant "the high band" in one widget and "gain reduction" in the one next
|
||||||
|
//! to it.
|
||||||
|
//!
|
||||||
|
//! # The two tiers
|
||||||
|
//!
|
||||||
|
//! 1. **Seeds** — the short, hand-picked list in the first half of this file. This is the part
|
||||||
|
//! you edit. Everything else is derived from it.
|
||||||
|
//! 2. **Derived** — [`Channel`] palettes built from a hue by [`channel`]. Change a hue and every
|
||||||
|
//! tint, edge and strip using it moves with it.
|
||||||
|
//!
|
||||||
|
//! All derivation is `const fn`, so the whole table is computed at compile time and costs nothing
|
||||||
|
//! at runtime. That is also why it uses plain HSV rather than `ecolor::Hsva`, whose conversion is
|
||||||
|
//! not `const`.
|
||||||
|
//!
|
||||||
|
//! # Editing this file
|
||||||
|
//!
|
||||||
|
//! * To recolour a channel, change one `HUE_*` constant. Never hand-edit a derived value.
|
||||||
|
//! * To retune the meter's headroom ramp, edit [`LEVEL_OK`] / [`LEVEL_WARN`] / [`LEVEL_HOT`] and
|
||||||
|
//! the thresholds in [`level_color`]. Both are deliberately literal rather than interpolated,
|
||||||
|
//! so you can set each one by eye without fighting a gradient.
|
||||||
|
//! * To change how loudly channel identity reads, edit the `(saturation, value)` pairs in
|
||||||
|
//! [`channel`].
|
||||||
|
//! * Anything new belongs here and must be reachable from a seed — a colour written at a call
|
||||||
|
//! site is the next magic number.
|
||||||
|
|
||||||
|
use nih_plug_egui::egui::Color32;
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// TIER 1 — SEEDS. This is the part to edit.
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
// --- Channel identity ------------------------------------------------------
|
||||||
|
// Hues in degrees: 0 = red, 120 = green, 240 = blue. Only the hue is chosen here; saturation and
|
||||||
|
// brightness are decided per role by `channel()`, so a channel reads as the same colour whether
|
||||||
|
// it is a 12%-brightness background wash or a full-strength drag handle.
|
||||||
|
//
|
||||||
|
// Keep neighbours far enough apart to survive being dimmed — most chrome lives at low
|
||||||
|
// saturation, where close hues merge. Roughly 60–90 degrees apart works; below ~40 does not.
|
||||||
|
pub const HUE_LOW: f32 = 233.0; // blue
|
||||||
|
pub const HUE_MID: f32 = 20.0; // orange
|
||||||
|
pub const HUE_HIGH: f32 = 155.0; // blueish green
|
||||||
|
pub const HUE_ALL: f32 = 280.0; // violet — the aggregate, distinct from its three inputs
|
||||||
|
|
||||||
|
// --- Functional colours ----------------------------------------------------
|
||||||
|
// Outside the hue system on purpose: these mean the same thing in every channel, so they must
|
||||||
|
// NOT be derived from a channel hue. Red is always "you have hit a limit".
|
||||||
|
|
||||||
|
/// Meter bar with comfortable headroom.
|
||||||
|
pub const LEVEL_OK: Color32 = Color32::from_rgb(90, 200, 110);
|
||||||
|
/// Meter bar approaching full scale.
|
||||||
|
pub const LEVEL_WARN: Color32 = Color32::from_rgb(230, 200, 70);
|
||||||
|
/// Meter bar at or near full scale.
|
||||||
|
pub const LEVEL_HOT: Color32 = Color32::from_rgb(235, 70, 60);
|
||||||
|
|
||||||
|
/// Latched over/ceiling lamp, lit.
|
||||||
|
pub const LAMP_ON: Color32 = Color32::from_rgb(255, 40, 40);
|
||||||
|
/// The same lamp unlit — dark red rather than grey, so it reads as "off", not "absent".
|
||||||
|
pub const LAMP_OFF: Color32 = Color32::from_rgb(40, 12, 12);
|
||||||
|
/// Ceiling-hit tick marks along the top edge of the scope.
|
||||||
|
pub const CLIP: Color32 = Color32::from_rgb(235, 45, 45);
|
||||||
|
|
||||||
|
// Scope traces. The scope is tabbed — one channel at a time — so these never compete with a
|
||||||
|
// channel hue: the tab chrome says which channel, these three say what each line is.
|
||||||
|
pub const SIGNAL_IN: Color32 = Color32::from_rgb(90, 170, 235);
|
||||||
|
pub const SIGNAL_OUT: Color32 = Color32::from_rgb(90, 200, 110);
|
||||||
|
pub const SIGNAL_GR: Color32 = Color32::from_rgb(240, 150, 60);
|
||||||
|
|
||||||
|
/// The transfer curve in the gain-curve display, and its translucent fill.
|
||||||
|
pub const CURVE: Color32 = Color32::from_rgb(120, 200, 160);
|
||||||
|
/// The live operating-point dot riding that curve — near-white, so it reads on top of the fill.
|
||||||
|
pub const OPERATING_POINT: Color32 = Color32::from_rgb(235, 240, 235);
|
||||||
|
/// Threshold marker on the gain curve. Dim on purpose: a reference line, not data.
|
||||||
|
pub const THRESHOLD_LINE: Color32 = Color32::from_rgb(80, 60, 45);
|
||||||
|
|
||||||
|
// --- Neutrals --------------------------------------------------------------
|
||||||
|
// Eight ad-hoc greys collapsed to six named steps. If you need another, add it here rather than
|
||||||
|
// reaching for `from_gray` at a call site.
|
||||||
|
|
||||||
|
/// Data panels — the scope and the gain curve.
|
||||||
|
pub const SURFACE: Color32 = Color32::from_rgb(16, 16, 20);
|
||||||
|
/// The meter panel, one step lighter so the cluster reads as its own surface. These were two
|
||||||
|
/// different blacks by accident before; they are two on purpose now. Set this equal to
|
||||||
|
/// [`SURFACE`] if you would rather they sat flush.
|
||||||
|
pub const SURFACE_RAISED: Color32 = Color32::from_rgb(20, 20, 24);
|
||||||
|
/// The empty part of a meter bar.
|
||||||
|
pub const TRACK: Color32 = Color32::from_rgb(34, 34, 40);
|
||||||
|
|
||||||
|
/// Structural lines — the unity diagonal, panel edges.
|
||||||
|
pub const GRID: Color32 = Color32::from_gray(45);
|
||||||
|
/// Reference lines that should recede — dB gridlines, −6 dB marks.
|
||||||
|
pub const GRID_FAINT: Color32 = Color32::from_gray(38);
|
||||||
|
|
||||||
|
/// Axis numbers and other text that must not compete with the data.
|
||||||
|
pub const TEXT_DIM: Color32 = Color32::from_gray(90);
|
||||||
|
/// Body text — band names on the crossover strip.
|
||||||
|
pub const TEXT: Color32 = Color32::from_gray(160);
|
||||||
|
/// Emphasised text — channel labels, readouts.
|
||||||
|
pub const TEXT_BRIGHT: Color32 = Color32::from_gray(210);
|
||||||
|
/// Hover and drag feedback, where the point is to be unmistakable.
|
||||||
|
pub const TEXT_HOVER: Color32 = Color32::WHITE;
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// TIER 2 — DERIVED. Generated from the seeds above; do not hand-edit.
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
/// One channel's colours, all derived from a single hue.
|
||||||
|
pub struct Channel {
|
||||||
|
/// Background wash behind a channel's cell. Very dark — it must associate the bars drawn on
|
||||||
|
/// top of it with a channel without competing with them.
|
||||||
|
pub chrome: Color32,
|
||||||
|
/// Band strip on the crossover widget: between [`Channel::chrome`] and [`Channel::edge`].
|
||||||
|
pub strip: Color32,
|
||||||
|
/// Accent rule under a meter cell, and the scope's tab underline. Bright enough to identify
|
||||||
|
/// at a glance.
|
||||||
|
pub edge: Color32,
|
||||||
|
/// Full strength — drag handles and anything else the user grabs.
|
||||||
|
pub handle: Color32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a channel's palette from its hue.
|
||||||
|
///
|
||||||
|
/// The `(saturation, value)` pairs below decide how loudly channel identity reads. Raise
|
||||||
|
/// `CHROME`'s value and the cell washes get more obvious; lower it and they recede into the
|
||||||
|
/// panel background. `CHROME` is deliberately near the floor: it sits *behind* meter bars.
|
||||||
|
pub const fn channel(hue: f32) -> Channel {
|
||||||
|
const CHROME: (f32, f32) = (0.45, 0.115);
|
||||||
|
const STRIP: (f32, f32) = (0.42, 0.20);
|
||||||
|
const EDGE: (f32, f32) = (0.60, 0.55);
|
||||||
|
const HANDLE: (f32, f32) = (0.55, 0.90);
|
||||||
|
Channel {
|
||||||
|
chrome: hsv(hue, CHROME.0, CHROME.1),
|
||||||
|
strip: hsv(hue, STRIP.0, STRIP.1),
|
||||||
|
edge: hsv(hue, EDGE.0, EDGE.1),
|
||||||
|
handle: hsv(hue, HANDLE.0, HANDLE.1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indexed by the `LOW`/`MID`/`HIGH`/`ALL` consts in `lib.rs`, so `CHANNELS[i]` lines up with
|
||||||
|
/// `meters.level_l[i]` and friends without a lookup step.
|
||||||
|
pub const CHANNELS: [Channel; 4] = [
|
||||||
|
channel(HUE_LOW),
|
||||||
|
channel(HUE_MID),
|
||||||
|
channel(HUE_HIGH),
|
||||||
|
channel(HUE_ALL),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// HSV to RGB, `const` so [`CHANNELS`] is built at compile time.
|
||||||
|
///
|
||||||
|
/// `h` in degrees (wrapped), `s` and `v` in `0..=1`. The standard sextant algorithm, written out
|
||||||
|
/// here only because `ecolor::Hsva`'s conversion is not a `const fn`.
|
||||||
|
pub const fn hsv(h: f32, s: f32, v: f32) -> Color32 {
|
||||||
|
// Wrap into 0..360. A loop rather than `%` because float remainder is not available in a
|
||||||
|
// const context on this toolchain; hues are small numbers, so this costs nothing.
|
||||||
|
let mut h = h;
|
||||||
|
while h < 0.0 {
|
||||||
|
h += 360.0;
|
||||||
|
}
|
||||||
|
while h >= 360.0 {
|
||||||
|
h -= 360.0;
|
||||||
|
}
|
||||||
|
let sector = h / 60.0;
|
||||||
|
let i = sector as i32;
|
||||||
|
let f = sector - i as f32;
|
||||||
|
let p = v * (1.0 - s);
|
||||||
|
let q = v * (1.0 - s * f);
|
||||||
|
let t = v * (1.0 - s * (1.0 - f));
|
||||||
|
let (r, g, b) = match i {
|
||||||
|
0 => (v, t, p),
|
||||||
|
1 => (q, v, p),
|
||||||
|
2 => (p, v, t),
|
||||||
|
3 => (p, q, v),
|
||||||
|
4 => (t, p, v),
|
||||||
|
_ => (v, p, q),
|
||||||
|
};
|
||||||
|
Color32::from_rgb(to_u8(r), to_u8(g), to_u8(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clamping float-to-byte, `const` for the same reason as [`hsv`].
|
||||||
|
const fn to_u8(x: f32) -> u8 {
|
||||||
|
let v = x * 255.0 + 0.5;
|
||||||
|
if v <= 0.0 {
|
||||||
|
0
|
||||||
|
} else if v >= 255.0 {
|
||||||
|
255
|
||||||
|
} else {
|
||||||
|
v as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Meter bar colour for an output level in dB.
|
||||||
|
///
|
||||||
|
/// Functional, not per-channel: −6 dB means the same thing in every band. Thresholds and colours
|
||||||
|
/// are both literal so either can be tuned by eye.
|
||||||
|
pub fn level_color(db: f32) -> Color32 {
|
||||||
|
if db >= -1.0 {
|
||||||
|
LEVEL_HOT
|
||||||
|
} else if db >= -6.0 {
|
||||||
|
LEVEL_WARN
|
||||||
|
} else {
|
||||||
|
LEVEL_OK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The same colour at `a`/255 opacity, for translucent fills under a trace.
|
||||||
|
///
|
||||||
|
/// Not `const`: `Color32::from_rgba_unmultiplied` is not a `const fn`, and the premultiplied
|
||||||
|
/// constructor that is would push the multiply out to every call site.
|
||||||
|
pub fn with_alpha(c: Color32, a: u8) -> Color32 {
|
||||||
|
Color32::from_rgba_unmultiplied(c.r(), c.g(), c.b(), a)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply the palette to egui's own widgets, so `ParamSlider`, labels and separators sit in the
|
||||||
|
/// same world as the hand-painted ones instead of on stock egui defaults.
|
||||||
|
///
|
||||||
|
/// Call once when the editor is created. Widget colours are per interaction state: `weak_bg_fill`
|
||||||
|
/// is an unfilled slider track, `bg_fill` the filled portion. Interactive states borrow the ALL
|
||||||
|
/// channel's hue, since egui widgets are not per-band — change that to a dedicated accent seed if
|
||||||
|
/// you would rather they were neutral.
|
||||||
|
pub fn apply_visuals(ctx: &nih_plug_egui::egui::Context) {
|
||||||
|
use nih_plug_egui::egui::{Stroke, Visuals};
|
||||||
|
|
||||||
|
let accent = &CHANNELS[3];
|
||||||
|
let mut v = Visuals::dark();
|
||||||
|
|
||||||
|
v.panel_fill = SURFACE;
|
||||||
|
v.window_fill = SURFACE;
|
||||||
|
v.extreme_bg_color = SURFACE;
|
||||||
|
v.faint_bg_color = SURFACE_RAISED;
|
||||||
|
|
||||||
|
let w = &mut v.widgets;
|
||||||
|
w.noninteractive.bg_fill = SURFACE_RAISED;
|
||||||
|
w.noninteractive.weak_bg_fill = SURFACE_RAISED;
|
||||||
|
w.noninteractive.bg_stroke = Stroke::new(1.0_f32, GRID_FAINT);
|
||||||
|
w.noninteractive.fg_stroke = Stroke::new(1.0_f32, TEXT);
|
||||||
|
|
||||||
|
w.inactive.bg_fill = TRACK;
|
||||||
|
w.inactive.weak_bg_fill = TRACK;
|
||||||
|
w.inactive.bg_stroke = Stroke::new(1.0_f32, GRID);
|
||||||
|
w.inactive.fg_stroke = Stroke::new(1.0_f32, TEXT_BRIGHT);
|
||||||
|
|
||||||
|
w.hovered.bg_fill = accent.edge;
|
||||||
|
w.hovered.weak_bg_fill = accent.strip;
|
||||||
|
w.hovered.bg_stroke = Stroke::new(1.0_f32, accent.edge);
|
||||||
|
w.hovered.fg_stroke = Stroke::new(1.0_f32, TEXT_HOVER);
|
||||||
|
|
||||||
|
w.active.bg_fill = accent.handle;
|
||||||
|
w.active.weak_bg_fill = accent.edge;
|
||||||
|
w.active.bg_stroke = Stroke::new(1.0_f32, accent.handle);
|
||||||
|
w.active.fg_stroke = Stroke::new(1.0_f32, TEXT_HOVER);
|
||||||
|
|
||||||
|
ctx.set_visuals(v);
|
||||||
|
}
|
||||||
+10
-9
@@ -12,15 +12,16 @@ use nih_plug_egui::egui::{self, pos2, vec2, Align2, Color32, CornerRadius, FontI
|
|||||||
|
|
||||||
use super::METER_FLOOR_DB;
|
use super::METER_FLOOR_DB;
|
||||||
use crate::meters::{Meters, BUCKET_HZ, NUM_CHANNELS};
|
use crate::meters::{Meters, BUCKET_HZ, NUM_CHANNELS};
|
||||||
|
use super::palette;
|
||||||
|
|
||||||
/// Height of the plot panel.
|
/// Height of the plot panel.
|
||||||
const PLOT_PANEL_H: f32 = 150.0;
|
const PLOT_PANEL_H: f32 = 150.0;
|
||||||
/// Number of columns held in the history ring.
|
/// Number of columns held in the history ring.
|
||||||
const PLOT_N: usize = 256;
|
const PLOT_N: usize = 256;
|
||||||
|
|
||||||
const COLOR_IN: Color32 = Color32::from_rgb(90, 170, 235);
|
const COLOR_IN: Color32 = palette::SIGNAL_IN;
|
||||||
const COLOR_OUT: Color32 = Color32::from_rgb(90, 200, 110);
|
const COLOR_OUT: Color32 = palette::SIGNAL_OUT;
|
||||||
const COLOR_GR: Color32 = Color32::from_rgb(240, 150, 60);
|
const COLOR_GR: Color32 = palette::SIGNAL_GR;
|
||||||
|
|
||||||
/// Rolling history for all channels: a per-channel ring of (in_db, out_db, gr_db) columns.
|
/// Rolling history for all channels: a per-channel ring of (in_db, out_db, gr_db) columns.
|
||||||
struct PlotHistory {
|
struct PlotHistory {
|
||||||
@@ -156,7 +157,7 @@ pub(super) fn draw(ui: &mut egui::Ui, meters: &Meters, state: &mut PlotState) {
|
|||||||
let (rect, _) =
|
let (rect, _) =
|
||||||
ui.allocate_exact_size(vec2(ui.available_width(), PLOT_PANEL_H), Sense::hover());
|
ui.allocate_exact_size(vec2(ui.available_width(), PLOT_PANEL_H), Sense::hover());
|
||||||
let p = ui.painter_at(rect);
|
let p = ui.painter_at(rect);
|
||||||
p.rect_filled(rect, CornerRadius::ZERO, Color32::from_rgb(16, 16, 20));
|
p.rect_filled(rect, CornerRadius::ZERO, palette::SURFACE);
|
||||||
|
|
||||||
let (top, bottom, left, right) =
|
let (top, bottom, left, right) =
|
||||||
(rect.top() + 4.0, rect.bottom() - 4.0, rect.left() + 4.0, rect.right() - 4.0);
|
(rect.top() + 4.0, rect.bottom() - 4.0, rect.left() + 4.0, rect.right() - 4.0);
|
||||||
@@ -169,13 +170,13 @@ pub(super) fn draw(ui: &mut egui::Ui, meters: &Meters, state: &mut PlotState) {
|
|||||||
// Gridlines (dB).
|
// Gridlines (dB).
|
||||||
for &g in &[0.0f32, -12.0, -24.0, -48.0] {
|
for &g in &[0.0f32, -12.0, -24.0, -48.0] {
|
||||||
let y = y_for_db(g);
|
let y = y_for_db(g);
|
||||||
p.line_segment([pos2(left, y), pos2(right, y)], Stroke::new(1.0, Color32::from_gray(40)));
|
p.line_segment([pos2(left, y), pos2(right, y)], Stroke::new(1.0_f32, palette::GRID_FAINT));
|
||||||
p.text(
|
p.text(
|
||||||
pos2(left + 2.0, y),
|
pos2(left + 2.0, y),
|
||||||
Align2::LEFT_BOTTOM,
|
Align2::LEFT_BOTTOM,
|
||||||
format!("{g:.0}"),
|
format!("{g:.0}"),
|
||||||
FontId::proportional(9.0),
|
FontId::proportional(9.0),
|
||||||
Color32::from_gray(90),
|
palette::TEXT_DIM,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +194,7 @@ pub(super) fn draw(ui: &mut egui::Ui, meters: &Meters, state: &mut PlotState) {
|
|||||||
// Fill as a strip of per-segment convex quads down to the baseline. A single
|
// Fill as a strip of per-segment convex quads down to the baseline. A single
|
||||||
// concave polygon mis-tessellates in egui (it fans from one corner, leaving stray
|
// concave polygon mis-tessellates in egui (it fans from one corner, leaving stray
|
||||||
// triangles), so build convex pieces — one box per time unit — instead.
|
// triangles), so build convex pieces — one box per time unit — instead.
|
||||||
let fill_col = Color32::from_rgba_unmultiplied(color.r(), color.g(), color.b(), 40);
|
let fill_col = palette::with_alpha(color, 40);
|
||||||
for seg in pts.windows(2) {
|
for seg in pts.windows(2) {
|
||||||
let (a, b) = (seg[0], seg[1]);
|
let (a, b) = (seg[0], seg[1]);
|
||||||
p.add(egui::Shape::convex_polygon(
|
p.add(egui::Shape::convex_polygon(
|
||||||
@@ -203,7 +204,7 @@ pub(super) fn draw(ui: &mut egui::Ui, meters: &Meters, state: &mut PlotState) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.add(egui::Shape::line(pts, Stroke::new(1.5, color)));
|
p.add(egui::Shape::line(pts, Stroke::new(1.5_f32, color)));
|
||||||
};
|
};
|
||||||
draw_series(&state.history.in_db[c], &|db| db, COLOR_IN, true);
|
draw_series(&state.history.in_db[c], &|db| db, COLOR_IN, true);
|
||||||
draw_series(&state.history.out_db[c], &|db| db, COLOR_OUT, true);
|
draw_series(&state.history.out_db[c], &|db| db, COLOR_OUT, true);
|
||||||
@@ -214,7 +215,7 @@ pub(super) fn draw(ui: &mut egui::Ui, meters: &Meters, state: &mut PlotState) {
|
|||||||
// hit the ceiling (global — shown on every channel's view). One column wide, so runs of
|
// hit the ceiling (global — shown on every channel's view). One column wide, so runs of
|
||||||
// hits merge into a continuous segment and a lone hit is just a dot. Nothing otherwise.
|
// hits merge into a continuous segment and a lone hit is just a dot. Nothing otherwise.
|
||||||
let dx = width / (PLOT_N - 1) as f32;
|
let dx = width / (PLOT_N - 1) as f32;
|
||||||
let marker = Color32::from_rgb(235, 45, 45);
|
let marker = palette::CLIP;
|
||||||
for k in 0..len {
|
for k in 0..len {
|
||||||
let idx = (write + PLOT_N - len + k) % PLOT_N;
|
let idx = (write + PLOT_N - len + k) % PLOT_N;
|
||||||
if state.history.hit[idx] {
|
if state.history.hit[idx] {
|
||||||
|
|||||||
Executable
+355
@@ -0,0 +1,355 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
# brand-plugin.sh - rename this plugin, everywhere, in one go.
|
||||||
|
#
|
||||||
|
# check-branding.sh tells you what disagrees. This changes it. They share
|
||||||
|
# branding.conf: this script rewrites that file and every copy of the old values
|
||||||
|
# in tracked files, then runs the checker and reports what it says. A run that
|
||||||
|
# ends with the checker green is a rename that landed.
|
||||||
|
#
|
||||||
|
# Dry run by default:
|
||||||
|
# sh brand-plugin.sh --display "Bari 206"
|
||||||
|
# sh brand-plugin.sh --display "Bari 206" --vendor Novoyuuparosk --apply
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# --display NAME the product name, with spaces and capitals. Everything
|
||||||
|
# else that can be derived, is derived from this.
|
||||||
|
# --pkg NAME override the derived Cargo package name.
|
||||||
|
# --vendor NAME --email ADDR --homepage URL
|
||||||
|
# --description D --clap-description D
|
||||||
|
# --struct NAME override the derived Rust type name.
|
||||||
|
# --allow-identity-change permit CLAP_ID / VST3_CLASS_ID to move. See below.
|
||||||
|
# --apply actually write. Without it, nothing is modified.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
# Resolve the repo root: these live in tools/, every path they touch is
|
||||||
|
# repo-relative, and they must behave the same wherever they are invoked from.
|
||||||
|
cd "$(dirname "$0")/.." || exit 1
|
||||||
|
[ -f tools/branding.conf ] || { printf 'tools/branding.conf not found\n' >&2; exit 1; }
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
. ./tools/branding.conf
|
||||||
|
|
||||||
|
OLD_PKG="$PKG"; OLD_DISPLAY="$DISPLAY"; OLD_PLUGIN_NAME="$PLUGIN_NAME"
|
||||||
|
OLD_VENDOR="$VENDOR"; OLD_EMAIL="$EMAIL"; OLD_HOMEPAGE="$HOMEPAGE"; OLD_AUTHOR="$AUTHOR"
|
||||||
|
OLD_DESCRIPTION="$DESCRIPTION"; OLD_CLAP_DESCRIPTION="$CLAP_DESCRIPTION"
|
||||||
|
OLD_CLAP_ID="$CLAP_ID"; OLD_VST3="$VST3_CLASS_ID"
|
||||||
|
OLD_STRUCT="$STRUCT"; OLD_PARAMS_STRUCT="$PARAMS_STRUCT"
|
||||||
|
|
||||||
|
new_display=""; new_pkg=""; new_vendor=""; new_email=""; new_homepage=""
|
||||||
|
new_description=""; new_clap_description=""; new_struct=""; new_author=""
|
||||||
|
new_clap_id=""; new_vst3=""; apply=0; allow_identity=0
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--display) new_display="${2:?}"; shift 2 ;;
|
||||||
|
--pkg) new_pkg="${2:?}"; shift 2 ;;
|
||||||
|
--vendor) new_vendor="${2:?}"; shift 2 ;;
|
||||||
|
--author) new_author="${2:?}"; shift 2 ;;
|
||||||
|
--email) new_email="${2:?}"; shift 2 ;;
|
||||||
|
--homepage) new_homepage="${2:?}"; shift 2 ;;
|
||||||
|
--description) new_description="${2:?}"; shift 2 ;;
|
||||||
|
--clap-description) new_clap_description="${2:?}"; shift 2 ;;
|
||||||
|
--struct) new_struct="${2:?}"; shift 2 ;;
|
||||||
|
--clap-id) new_clap_id="${2:?}"; shift 2 ;;
|
||||||
|
--vst3-class-id) new_vst3="${2:?}"; shift 2 ;;
|
||||||
|
--allow-identity-change) allow_identity=1; shift ;;
|
||||||
|
--apply) apply=1; shift ;;
|
||||||
|
-h|--help) sed -n '3,25p' "$0"; exit 0 ;;
|
||||||
|
*) printf 'unknown option: %s\n' "$1" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Interactive when nothing was passed and there is a terminal to ask at. Every
|
||||||
|
# prompt offers the current value; Enter keeps it, so a run that answers nothing
|
||||||
|
# is a no-op rather than a surprise. Optional fields additionally accept "-" to
|
||||||
|
# clear them.
|
||||||
|
ask() { # ask VARNAME PROMPT CURRENT
|
||||||
|
printf ' %s [%s]: ' "$2" "$3" > /dev/tty
|
||||||
|
IFS= read -r _reply < /dev/tty || _reply=""
|
||||||
|
[ -n "$_reply" ] || _reply="$3"
|
||||||
|
[ "$_reply" = "-" ] && _reply=""
|
||||||
|
eval "$1=\$_reply"
|
||||||
|
}
|
||||||
|
if [ -z "$new_display" ] && [ -t 0 ] && [ -c /dev/tty ]; then
|
||||||
|
printf 'branding this plugin - Enter keeps the current value, "-" clears an optional one\n\n'
|
||||||
|
ask new_display "display name " "$OLD_DISPLAY"
|
||||||
|
ask new_vendor "vendor (org) " "$OLD_VENDOR"
|
||||||
|
ask new_author "cargo author " "$OLD_AUTHOR"
|
||||||
|
ask new_email "email " "$OLD_EMAIL"
|
||||||
|
ask new_homepage "homepage " "$OLD_HOMEPAGE"
|
||||||
|
ask new_description "cargo description" "$OLD_DESCRIPTION"
|
||||||
|
ask new_clap_description "clap description" "$OLD_CLAP_DESCRIPTION"
|
||||||
|
printf '\n'
|
||||||
|
fi
|
||||||
|
[ -n "$new_display" ] || { printf 'nothing to do: pass --display "New Name" (or run interactively)\n' >&2; exit 2; }
|
||||||
|
|
||||||
|
# --- derivation -------------------------------------------------------------
|
||||||
|
# Package name: lowercase, every run of non-alphanumerics becomes one separator,
|
||||||
|
# ends trimmed. Cargo accepts both hyphens and underscores in a package name and
|
||||||
|
# converts to underscores for the built artifact, which nih_plug_xtask already
|
||||||
|
# accounts for; underscores are used here to match the Rust convention.
|
||||||
|
derive_pkg() {
|
||||||
|
printf '%s' "$1" | tr '[:upper:]' '[:lower:]' \
|
||||||
|
| sed -e 's/[^a-z0-9]\{1,\}/_/g' -e 's/^_//' -e 's/_$//'
|
||||||
|
}
|
||||||
|
# Rust type name: CamelCase, non-alphanumerics dropped entirely.
|
||||||
|
derive_struct() {
|
||||||
|
printf '%s' "$1" | sed -e 's/[^A-Za-z0-9]\{1,\}/ /g' \
|
||||||
|
| awk '{for(i=1;i<=NF;i++) printf "%s%s", toupper(substr($i,1,1)), substr($i,2); print ""}'
|
||||||
|
}
|
||||||
|
[ -n "$new_pkg" ] || new_pkg=$(derive_pkg "$new_display")
|
||||||
|
[ -n "$new_struct" ] || new_struct=$(derive_struct "$new_display")
|
||||||
|
new_params_struct="${new_struct}Params"
|
||||||
|
[ -n "$new_vendor" ] || new_vendor="$OLD_VENDOR"
|
||||||
|
[ -n "$new_author" ] || new_author="$OLD_AUTHOR"
|
||||||
|
[ -n "$new_email" ] || new_email="$OLD_EMAIL"
|
||||||
|
[ -n "$new_homepage" ] || new_homepage="$OLD_HOMEPAGE"
|
||||||
|
[ -n "$new_description" ] || new_description="$OLD_DESCRIPTION"
|
||||||
|
[ -n "$new_clap_description" ] || new_clap_description="$OLD_CLAP_DESCRIPTION"
|
||||||
|
|
||||||
|
# --- validation -------------------------------------------------------------
|
||||||
|
fail=0
|
||||||
|
die() { printf ' INVALID %s\n' "$1"; fail=$((fail+1)); }
|
||||||
|
|
||||||
|
case "$new_pkg" in
|
||||||
|
"") die "derived package name is empty" ;;
|
||||||
|
[0-9]*) die "package name \"$new_pkg\" starts with a digit - cargo rejects this outright" ;;
|
||||||
|
esac
|
||||||
|
case "$new_pkg" in
|
||||||
|
test|core|std|alloc|proc_macro)
|
||||||
|
die "package name \"$new_pkg\" collides with a built-in crate" ;;
|
||||||
|
as|break|const|continue|crate|dyn|else|enum|extern|false|fn|for|if|impl|in|let|loop|match|mod|move|mut|pub|ref|return|self|static|struct|super|trait|true|type|unsafe|use|where|while|async|await|box|do|final|macro|override|priv|typeof|unsized|virtual|yield|try)
|
||||||
|
die "package name \"$new_pkg\" is a Rust keyword - cargo rejects this" ;;
|
||||||
|
esac
|
||||||
|
# bundler.toml uses the package name as a TOML bare key.
|
||||||
|
case "$new_pkg" in *[!a-zA-Z0-9_-]*) die "package name \"$new_pkg\" is not a valid TOML bare key" ;; esac
|
||||||
|
case "$new_struct" in ""|[0-9]*) die "derived type name \"$new_struct\" is not a valid Rust identifier" ;; esac
|
||||||
|
|
||||||
|
# --- frozen identity --------------------------------------------------------
|
||||||
|
identity_note=""
|
||||||
|
if [ -n "$new_clap_id" ] || [ -n "$new_vst3" ]; then
|
||||||
|
if [ "$allow_identity" -eq 0 ]; then
|
||||||
|
die "CLAP_ID / VST3_CLASS_ID are frozen identity; pass --allow-identity-change to move them"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
[ -n "$new_clap_id" ] || new_clap_id="$OLD_CLAP_ID"
|
||||||
|
[ -n "$new_vst3" ] || new_vst3="$OLD_VST3"
|
||||||
|
|
||||||
|
# VST3_CLASS_ID is a fixed 16-byte array, so it cannot simply be derived. Propose
|
||||||
|
# one from the new name and let a human decide - it is frozen the moment it ships.
|
||||||
|
proposed_vst3=$(printf '%s' "${new_struct}Plugin000000000000" | cut -c1-16)
|
||||||
|
n=$(printf '%s' "$new_vst3" | wc -c | tr -d ' ')
|
||||||
|
[ "$n" -eq 16 ] || die "VST3_CLASS_ID \"$new_vst3\" is $n bytes, must be exactly 16"
|
||||||
|
|
||||||
|
case "$new_clap_id" in
|
||||||
|
*\ *|*[A-Z]*) die "CLAP_ID \"$new_clap_id\" must be lowercase with no spaces" ;;
|
||||||
|
*.*) ;;
|
||||||
|
*) die "CLAP_ID \"$new_clap_id\" should be dotted reverse-DNS" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# CLAP_DESCRIPTION is user-facing: keep development history out of it.
|
||||||
|
for hist in "$CODENAME" "$OLD_DISPLAY" "$OLD_PKG"; do
|
||||||
|
case "$new_clap_description" in
|
||||||
|
*"$hist"*) die "CLAP_DESCRIPTION still mentions \"$hist\" - it is shown to users, who never knew the development name" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- plan -------------------------------------------------------------------
|
||||||
|
printf 'rename plan\n\n'
|
||||||
|
row() { [ "$2" = "$3" ] && printf ' %-18s %s (unchanged)\n' "$1" "$2" || printf ' %-18s %s -> %s\n' "$1" "$2" "$3"; }
|
||||||
|
row "package" "$OLD_PKG" "$new_pkg"
|
||||||
|
row "display" "$OLD_DISPLAY" "$new_display"
|
||||||
|
row "Plugin::NAME" "$OLD_PLUGIN_NAME" "$new_display"
|
||||||
|
row "vendor" "$OLD_VENDOR" "$new_vendor"
|
||||||
|
row "cargo author" "$OLD_AUTHOR" "$new_author"
|
||||||
|
row "email" "$OLD_EMAIL" "$new_email"
|
||||||
|
row "homepage" "$OLD_HOMEPAGE" "$new_homepage"
|
||||||
|
row "description" "$OLD_DESCRIPTION" "$new_description"
|
||||||
|
row "clap desc" "$OLD_CLAP_DESCRIPTION" "$new_clap_description"
|
||||||
|
row "struct" "$OLD_STRUCT" "$new_struct"
|
||||||
|
row "params struct" "$OLD_PARAMS_STRUCT" "$new_params_struct"
|
||||||
|
row "CLAP_ID" "$OLD_CLAP_ID" "$new_clap_id"
|
||||||
|
row "VST3_CLASS_ID" "$OLD_VST3" "$new_vst3"
|
||||||
|
printf '\n repo codename stays %s - fixed at creation, never renamed\n' "$CODENAME"
|
||||||
|
if [ "$new_vst3" = "$OLD_VST3" ]; then
|
||||||
|
printf ' suggested VST3_CLASS_ID for this name (16 bytes): "%s"\n' "$proposed_vst3"
|
||||||
|
printf ' apply with --vst3-class-id "%s" --allow-identity-change, and only before first release\n' "$proposed_vst3"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$fail" -ne 0 ]; then
|
||||||
|
printf '\n%d problem(s); nothing written.\n' "$fail"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$apply" -eq 0 ]; then
|
||||||
|
printf '\nDry run. Re-run with --apply to write these changes.\n'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- apply ------------------------------------------------------------------
|
||||||
|
# Three scratch files, one trap: the regenerated site table, the rows carried
|
||||||
|
# through from the old one, and the value-substitution pairs.
|
||||||
|
gen="$(mktemp)"; keep="$(mktemp)"; subst_file="$(mktemp)"
|
||||||
|
trap 'rm -f "$gen" "$keep" "$subst_file"' EXIT
|
||||||
|
|
||||||
|
T="$(printf '\t')"
|
||||||
|
{
|
||||||
|
printf 'req%spkg.cargo%sCargo.toml%sname = "%s"\n' "$T" "$T" "$T" "$new_pkg"
|
||||||
|
printf 'req%spkg.bundler%sbundler.toml%s[%s]\n' "$T" "$T" "$T" "$new_pkg"
|
||||||
|
printf 'opt%spkg.lock%sCargo.lock%sname = "%s"\n' "$T" "$T" "$T" "$new_pkg"
|
||||||
|
printf 'opt%spkg.ci%s.github/workflows/ci.yaml%scargo xtask bundle %s\n' "$T" "$T" "$T" "$new_pkg"
|
||||||
|
printf 'opt%spkg.readme%sREADME.md%scargo xtask bundle %s\n' "$T" "$T" "$T" "$new_pkg"
|
||||||
|
printf 'req%sdisplay.bundler%sbundler.toml%sname = "%s"\n' "$T" "$T" "$T" "$new_display"
|
||||||
|
printf 'opt%sdisplay.notice%sNOTICE%s%s\n' "$T" "$T" "$T" "$new_display"
|
||||||
|
printf 'req%sname.plugin%s%s%sconst NAME: &'"'"'static str = "%s"\n' "$T" "$T" "$LIB_FILE" "$T" "$new_display"
|
||||||
|
printf 'req%svendor.plugin%s%s%sconst VENDOR: &'"'"'static str = "%s"\n' "$T" "$T" "$LIB_FILE" "$T" "$new_vendor"
|
||||||
|
printf 'req%semail.plugin%s%s%sconst EMAIL: &'"'"'static str = "%s"\n' "$T" "$T" "$LIB_FILE" "$T" "$new_email"
|
||||||
|
printf 'req%sauthors.cargo%sCargo.toml%sauthors = ["%s <%s>"]\n' "$T" "$T" "$T" "$new_author" "$new_email"
|
||||||
|
printf 'req%shomepage.cargo%sCargo.toml%shomepage = "%s"\n' "$T" "$T" "$T" "$new_homepage"
|
||||||
|
printf 'req%surl.plugin%s%s%sconst URL: &'"'"'static str = env!("CARGO_PKG_HOMEPAGE")\n' "$T" "$T" "$LIB_FILE" "$T"
|
||||||
|
printf 'req%sdescription.cargo%sCargo.toml%sdescription = "%s"\n' "$T" "$T" "$T" "$new_description"
|
||||||
|
# Narrowest distinctive fragment, not the whole statement: rustfmt wraps a long
|
||||||
|
# CLAP_DESCRIPTION onto its own line, and a pattern spanning the assignment
|
||||||
|
# would then never match a correctly formatted file.
|
||||||
|
printf 'req%sdescription.clap%s%s%sSome("%s")\n' "$T" "$T" "$LIB_FILE" "$T" "$new_clap_description"
|
||||||
|
printf 'req%sclapid.plugin%s%s%sconst CLAP_ID: &'"'"'static str = "%s"\n' "$T" "$T" "$LIB_FILE" "$T" "$new_clap_id"
|
||||||
|
printf 'req%svst3id.plugin%s%s%sconst VST3_CLASS_ID: [u8; 16] = *b"%s"\n' "$T" "$T" "$LIB_FILE" "$T" "$new_vst3"
|
||||||
|
printf 'req%sstruct.def%s%s%sstruct %s {\n' "$T" "$T" "$LIB_FILE" "$T" "$new_struct"
|
||||||
|
printf 'req%sstruct.plugin%s%s%simpl Plugin for %s {\n' "$T" "$T" "$LIB_FILE" "$T" "$new_struct"
|
||||||
|
printf 'req%sstruct.clap%s%s%simpl ClapPlugin for %s {\n' "$T" "$T" "$LIB_FILE" "$T" "$new_struct"
|
||||||
|
printf 'req%sstruct.vst3%s%s%simpl Vst3Plugin for %s {\n' "$T" "$T" "$LIB_FILE" "$T" "$new_struct"
|
||||||
|
printf 'req%sstruct.exportclap%s%s%snih_export_clap!(%s);\n' "$T" "$T" "$LIB_FILE" "$T" "$new_struct"
|
||||||
|
printf 'req%sstruct.exportvst3%s%s%snih_export_vst3!(%s);\n' "$T" "$T" "$LIB_FILE" "$T" "$new_struct"
|
||||||
|
printf 'req%sparams.def%s%s%sstruct %s {\n' "$T" "$T" "$PARAMS_FILE" "$T" "$new_params_struct"
|
||||||
|
printf 'req%sparams.default%s%s%simpl Default for %s {\n' "$T" "$T" "$PARAMS_FILE" "$T" "$new_params_struct"
|
||||||
|
printf 'req%sparams.field%s%s%sparams: Arc<%s>,\n' "$T" "$T" "$LIB_FILE" "$T" "$new_params_struct"
|
||||||
|
printf 'opt%sdeploy.pkg%sdeploy.ps1%s$Package = "%s"\n' "$T" "$T" "$T" "$new_pkg"
|
||||||
|
printf 'opt%sdeploy.vst3%sdeploy.ps1%s$vst3Name = "%s.vst3"\n' "$T" "$T" "$T" "$new_display"
|
||||||
|
printf 'opt%sdeploy.clap%sdeploy.ps1%s$clapName = "%s.clap"\n' "$T" "$T" "$T" "$new_display"
|
||||||
|
} > "$gen"
|
||||||
|
|
||||||
|
# Pass 1 - site-directed. Each owned row in the existing table carries the OLD
|
||||||
|
# resolved literal for its site; the regenerated row carries the new one. Rewriting
|
||||||
|
# by that pair is unambiguous, which plain value substitution is not: AUTHOR
|
||||||
|
# defaults to VENDOR, so both fields can hold the same old string and a global
|
||||||
|
# replace cannot tell which occurrence belongs to which. This must run BEFORE
|
||||||
|
# pass 2, or the global replace consumes the old literals first and leaves the
|
||||||
|
# precise rewrite with nothing to match.
|
||||||
|
sites="tools/branding-sites.tsv"
|
||||||
|
T="$(printf '\t')"
|
||||||
|
if [ -f "$sites" ]; then
|
||||||
|
while IFS="$T" read -r ok_ oo_ of_ op_; do
|
||||||
|
case "$ok_" in ''|\#*) continue ;; esac
|
||||||
|
np_=$(awk -F"$T" -v o="$oo_" '$2==o {print $4; exit}' "$gen")
|
||||||
|
[ -n "$np_" ] || continue
|
||||||
|
[ "$np_" != "$op_" ] || continue
|
||||||
|
[ -f "$of_" ] || continue
|
||||||
|
esc_o=$(printf '%s' "$op_" | sed 's/[][\.*^$/|&]/\\&/g')
|
||||||
|
esc_n=$(printf '%s' "$np_" | sed 's/[\\/&|]/\\&/g')
|
||||||
|
tmp_=$(mktemp)
|
||||||
|
sed "s|$esc_o|$esc_n|g" "$of_" > "$tmp_" && cat "$tmp_" > "$of_"
|
||||||
|
rm -f "$tmp_"
|
||||||
|
done < "$sites"
|
||||||
|
printf ' pass 1: rewrote known sites from the table\n'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Pass 2 - value substitution, for everything the table does not enumerate:
|
||||||
|
# doc comments, use sites in other modules, prose in the README.
|
||||||
|
# Longest values first: a shorter spelling can be a substring of a longer one
|
||||||
|
# (the package name inside a URL, say), and replacing it first would corrupt the
|
||||||
|
# longer match. branding.conf is excluded and rewritten wholesale below, so its
|
||||||
|
# STALE list is never rewritten into meaninglessness.
|
||||||
|
{
|
||||||
|
printf '%s\t%s\n' "$OLD_CLAP_ID" "$new_clap_id"
|
||||||
|
printf '%s\t%s\n' "$OLD_HOMEPAGE" "$new_homepage"
|
||||||
|
printf '%s\t%s\n' "$OLD_CLAP_DESCRIPTION" "$new_clap_description"
|
||||||
|
printf '%s\t%s\n' "$OLD_DESCRIPTION" "$new_description"
|
||||||
|
printf '%s\t%s\n' "$OLD_PARAMS_STRUCT" "$new_params_struct"
|
||||||
|
printf '%s\t%s\n' "$OLD_VST3" "$new_vst3"
|
||||||
|
printf '%s\t%s\n' "$OLD_PLUGIN_NAME" "$new_display"
|
||||||
|
printf '%s\t%s\n' "$OLD_DISPLAY" "$new_display"
|
||||||
|
printf '%s\t%s\n' "$OLD_STRUCT" "$new_struct"
|
||||||
|
printf '%s\t%s\n' "$OLD_EMAIL" "$new_email"
|
||||||
|
printf '%s\t%s\n' "$OLD_AUTHOR" "$new_author"
|
||||||
|
printf '%s\t%s\n' "$OLD_VENDOR" "$new_vendor"
|
||||||
|
printf '%s\t%s\n' "$OLD_PKG" "$new_pkg"
|
||||||
|
} | awk -F'\t' '$1 != $2 { print length($1)"\t"$0 }' | sort -rn | cut -f2- > "$subst_file"
|
||||||
|
|
||||||
|
changed=0
|
||||||
|
git ls-files 2>/dev/null | while IFS= read -r f; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
# branding.conf alone is excluded: it is rewritten wholesale below, and its
|
||||||
|
# STALE list names every retired spelling by design. The site table IS
|
||||||
|
# substituted, so hand-added custom rows track the rename; owned rows are
|
||||||
|
# regenerated afterwards regardless.
|
||||||
|
case "$f" in tools/branding.conf) continue ;; esac
|
||||||
|
before=$(cat "$f")
|
||||||
|
after="$before"
|
||||||
|
while IFS="$(printf '\t')" read -r old new; do
|
||||||
|
[ -n "$old" ] || continue
|
||||||
|
after=$(printf '%s' "$after" | sed "s|$(printf '%s' "$old" | sed 's/[][\.*^$/|&]/\\&/g')|$(printf '%s' "$new" | sed 's/[\\/&|]/\\&/g')|g")
|
||||||
|
done < "$subst_file"
|
||||||
|
if [ "$before" != "$after" ]; then printf '%s\n' "$after" > "$f"; printf ' rewrote %s\n' "$f"; fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# branding.conf last, with the retired spellings appended to STALE so the next
|
||||||
|
# run of the checker sweeps for anything this pass missed.
|
||||||
|
# Deduplicated: two fields can retire to the same spelling - DISPLAY and
|
||||||
|
# PLUGIN_NAME are commonly identical - and listing it twice makes the checker
|
||||||
|
# report the same drift twice. Note that neither this script nor the checker
|
||||||
|
# contains a branding literal anywhere: both read every name from
|
||||||
|
# branding.conf, so a rename never has to rewrite its own tooling.
|
||||||
|
retired=$(cut -f1 "$subst_file" | awk '!seen[$0]++')
|
||||||
|
sed -e "s|^PKG=.*|PKG=\"$new_pkg\"|" \
|
||||||
|
-e "s|^DISPLAY=.*|DISPLAY=\"$new_display\"|" \
|
||||||
|
-e "s|^PLUGIN_NAME=.*|PLUGIN_NAME=\"$new_display\"|" \
|
||||||
|
-e "s|^VENDOR=.*|VENDOR=\"$new_vendor\"|" \
|
||||||
|
-e "s|^# *AUTHOR=.*|AUTHOR=\"$new_author\"|" \
|
||||||
|
-e "s|^AUTHOR=.*|AUTHOR=\"$new_author\"|" \
|
||||||
|
-e "s|^EMAIL=.*|EMAIL=\"$new_email\"|" \
|
||||||
|
-e "s|^HOMEPAGE=.*|HOMEPAGE=\"$new_homepage\"|" \
|
||||||
|
-e "s|^DESCRIPTION=.*|DESCRIPTION=\"$new_description\"|" \
|
||||||
|
-e "s|^CLAP_DESCRIPTION=.*|CLAP_DESCRIPTION=\"$new_clap_description\"|" \
|
||||||
|
-e "s|^CLAP_ID=.*|CLAP_ID=\"$new_clap_id\"|" \
|
||||||
|
-e "s|^VST3_CLASS_ID=.*|VST3_CLASS_ID=\"$new_vst3\"|" \
|
||||||
|
-e "s|^STRUCT=.*|STRUCT=\"$new_struct\"|" \
|
||||||
|
-e "s|^PARAMS_STRUCT=.*|PARAMS_STRUCT=\"$new_params_struct\"|" \
|
||||||
|
tools/branding.conf > tools/branding.conf.new
|
||||||
|
if [ -n "$retired" ]; then
|
||||||
|
{ sed '$ s/"$//' tools/branding.conf.new; printf '%s"\n' "$retired"; } > tools/branding.conf
|
||||||
|
else
|
||||||
|
# Nothing retired (a no-op rename, or a regeneration): leave STALE untouched
|
||||||
|
# rather than appending a blank line to it on every run.
|
||||||
|
cat tools/branding.conf.new > tools/branding.conf
|
||||||
|
fi
|
||||||
|
rm -f tools/branding.conf.new
|
||||||
|
if [ "$new_author" = "$new_vendor" ]; then
|
||||||
|
# Identical to VENDOR, so re-comment it: the conf should keep showing that this
|
||||||
|
# value derives rather than pinning a duplicate that then has to be maintained.
|
||||||
|
tmp_c=$(mktemp)
|
||||||
|
sed "s|^AUTHOR=.*|# AUTHOR=\"$new_author\"|" tools/branding.conf > "$tmp_c" && cat "$tmp_c" > tools/branding.conf
|
||||||
|
rm -f "$tmp_c"
|
||||||
|
fi
|
||||||
|
printf ' rewrote tools/branding.conf (retired spellings appended to STALE)\n'
|
||||||
|
|
||||||
|
# Regenerate the site table. Rows whose owner this script recognises are
|
||||||
|
# rewritten with the new resolved literals; every other row - anything marked
|
||||||
|
# custom, or added by hand for a file this template does not ship - is carried
|
||||||
|
# through untouched. Clobbering those silently is how a tool stops being trusted.
|
||||||
|
sites="tools/branding-sites.tsv"
|
||||||
|
|
||||||
|
owned=$(cut -f2 "$gen")
|
||||||
|
if [ -f "$sites" ]; then
|
||||||
|
grep '^#' "$sites" > "$keep" 2>/dev/null || : > "$keep"
|
||||||
|
while IFS="$T" read -r k o f pat; do
|
||||||
|
case "$k" in ''|\#*) continue ;; esac
|
||||||
|
printf '%s\n' "$owned" | grep -qxF "$o" || printf '%s%s%s%s%s%s%s\n' "$k" "$T" "$o" "$T" "$f" "$T" "$pat" >> "$keep"
|
||||||
|
done < "$sites"
|
||||||
|
else
|
||||||
|
: > "$keep"
|
||||||
|
fi
|
||||||
|
{ cat "$keep"; cat "$gen"; } > "$sites"
|
||||||
|
printf ' rewrote %s (%s owned rows, %s preserved)\n' "$sites" "$(wc -l < "$gen" | tr -d ' ')" "$(grep -cv '^#' "$keep" 2>/dev/null || echo 0)"
|
||||||
|
|
||||||
|
printf '\nverifying with check-branding.sh\n\n'
|
||||||
|
sh tools/check-branding.sh
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
req pkg.cargo Cargo.toml name = "bari_206"
|
||||||
|
req pkg.bundler bundler.toml [bari_206]
|
||||||
|
opt pkg.lock Cargo.lock name = "bari_206"
|
||||||
|
opt pkg.ci .github/workflows/ci.yaml cargo xtask bundle bari_206
|
||||||
|
opt pkg.readme README.md cargo xtask bundle bari_206
|
||||||
|
req display.bundler bundler.toml name = "Bari 206"
|
||||||
|
opt display.notice NOTICE Bari 206
|
||||||
|
req name.plugin src/lib.rs const NAME: &'static str = "Bari 206"
|
||||||
|
req vendor.plugin src/lib.rs const VENDOR: &'static str = "Novoyuuparosk"
|
||||||
|
req email.plugin src/lib.rs const EMAIL: &'static str = "mikkeli@novoyuuparosk.org"
|
||||||
|
req authors.cargo Cargo.toml authors = ["Mikkeli Matlock <mikkeli@novoyuuparosk.org>"]
|
||||||
|
req homepage.cargo Cargo.toml homepage = "https://github.com/mikkeli/codename-206"
|
||||||
|
req url.plugin src/lib.rs const URL: &'static str = env!("CARGO_PKG_HOMEPAGE")
|
||||||
|
req description.cargo Cargo.toml description = "A VST3/CLAP multiband compressor/limiter with a custom gain-curve display"
|
||||||
|
req description.clap src/lib.rs Some("Multiband compressor/limiter (stage 3: 3-band + 'All' channel)")
|
||||||
|
req clapid.plugin src/lib.rs const CLAP_ID: &'static str = "org.novoyuuparosk.bari-206"
|
||||||
|
req vst3id.plugin src/lib.rs const VST3_CLASS_ID: [u8; 16] = *b"Bari206Multiband"
|
||||||
|
req struct.def src/lib.rs struct Bari206 {
|
||||||
|
req struct.plugin src/lib.rs impl Plugin for Bari206 {
|
||||||
|
req struct.clap src/lib.rs impl ClapPlugin for Bari206 {
|
||||||
|
req struct.vst3 src/lib.rs impl Vst3Plugin for Bari206 {
|
||||||
|
req struct.exportclap src/lib.rs nih_export_clap!(Bari206);
|
||||||
|
req struct.exportvst3 src/lib.rs nih_export_vst3!(Bari206);
|
||||||
|
req params.def src/params.rs struct Bari206Params {
|
||||||
|
req params.default src/params.rs impl Default for Bari206Params {
|
||||||
|
req params.field src/lib.rs params: Arc<Bari206Params>,
|
||||||
|
opt deploy.pkg deploy.ps1 $Package = "bari_206"
|
||||||
|
opt deploy.vst3 deploy.ps1 $vst3Name = "Bari 206.vst3"
|
||||||
|
opt deploy.clap deploy.ps1 $clapName = "Bari 206.clap"
|
||||||
|
Can't render this file because it contains an unexpected character in line 1 and column 33.
|
@@ -0,0 +1,66 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
# branding.conf - this plugin's canonical identity.
|
||||||
|
#
|
||||||
|
# Managed by tools/brand-plugin.sh, read by tools/check-branding.sh. Hand-editing
|
||||||
|
# works and is sometimes the quickest thing, but the helper is what keeps this
|
||||||
|
# file, the source tree and tools/branding-sites.tsv agreeing with each other.
|
||||||
|
# Edit here and you are on the hook for running the checker afterwards.
|
||||||
|
|
||||||
|
# --- derivations ------------------------------------------------------------
|
||||||
|
_derive_pkg() {
|
||||||
|
printf '%s' "$1" | tr '[:upper:]' '[:lower:]' \
|
||||||
|
| sed -e 's/[^a-z0-9]\{1,\}/_/g' -e 's/^_//' -e 's/_$//'
|
||||||
|
}
|
||||||
|
_derive_struct() {
|
||||||
|
printf '%s' "$1" | sed -e 's/[^A-Za-z0-9]\{1,\}/ /g' \
|
||||||
|
| awk '{for(i=1;i<=NF;i++) printf "%s%s", toupper(substr($i,1,1)), substr($i,2); print ""}'
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- the names ---------------------------------------------------------------
|
||||||
|
PLUGIN_NAME="Bari 206"
|
||||||
|
VENDOR="Novoyuuparosk"
|
||||||
|
EMAIL="mikkeli@novoyuuparosk.org"
|
||||||
|
AUTHOR="Mikkeli Matlock"
|
||||||
|
HOMEPAGE="https://github.com/mikkeli/codename-206"
|
||||||
|
DESCRIPTION="A VST3/CLAP multiband compressor/limiter with a custom gain-curve display"
|
||||||
|
|
||||||
|
# What the plugin does *now*. No codename, no development stage.
|
||||||
|
CLAP_DESCRIPTION="Multiband compressor/limiter (stage 3: 3-band + 'All' channel)"
|
||||||
|
|
||||||
|
# --- frozen identity ---------------------------------------------------------
|
||||||
|
# Hosts key saved projects and presets off these two. Never move them after the
|
||||||
|
# first release: every existing session silently loses the plugin.
|
||||||
|
CLAP_ID="org.novoyuuparosk.bari-206"
|
||||||
|
VST3_CLASS_ID="Bari206Multiband"
|
||||||
|
|
||||||
|
# --- fixed at repo creation --------------------------------------------------
|
||||||
|
# The repo keeps its development codename while the plugin carries the product
|
||||||
|
# name. Recorded only so it can be kept out of user-facing text.
|
||||||
|
CODENAME="codename-206"
|
||||||
|
|
||||||
|
LIB_FILE="src/lib.rs"
|
||||||
|
PARAMS_FILE="src/params.rs"
|
||||||
|
|
||||||
|
# --- derived unless overridden -----------------------------------------------
|
||||||
|
# PKG, STRUCT and PARAMS_STRUCT all derive correctly from PLUGIN_NAME here, so
|
||||||
|
# none is pinned. DISPLAY defaults to PLUGIN_NAME.
|
||||||
|
: "${AUTHOR:=$VENDOR}"
|
||||||
|
: "${DISPLAY:=$PLUGIN_NAME}"
|
||||||
|
: "${PKG:=$(_derive_pkg "$PLUGIN_NAME")}"
|
||||||
|
: "${STRUCT:=$(_derive_struct "$PLUGIN_NAME")}"
|
||||||
|
: "${PARAMS_STRUCT:=${STRUCT}Params}"
|
||||||
|
|
||||||
|
# --- retired spellings -------------------------------------------------------
|
||||||
|
STALE="codename_206
|
||||||
|
Codename 206
|
||||||
|
Codename206
|
||||||
|
Codename206Params
|
||||||
|
Codename206Maxi!
|
||||||
|
206 prototype
|
||||||
|
com.mikkeli.codename-206
|
||||||
|
bari_206
|
||||||
|
Bari 206
|
||||||
|
Bari206
|
||||||
|
Bari206Params
|
||||||
|
Bari206Multiband
|
||||||
|
org.novoyuuparosk.bari-206"
|
||||||
Executable
+188
@@ -0,0 +1,188 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
# check-branding.sh - verify the plugin's branding/identity strings agree everywhere.
|
||||||
|
#
|
||||||
|
# Plugin branding is duplicated across the build manifest, the bundler manifest,
|
||||||
|
# the source, the docs and (once you add one) the deploy script. Nothing makes
|
||||||
|
# them agree, so a rename drifts silently: the bundle builds under one name and
|
||||||
|
# the deploy script looks for another. This asserts they match, and that the
|
||||||
|
# identity constants are legal.
|
||||||
|
#
|
||||||
|
# In a template this matters more than usual: renaming is the first thing you do
|
||||||
|
# with this repo, and it touches every file below. Run this immediately after,
|
||||||
|
# and the STALE machinery will list whatever you missed.
|
||||||
|
#
|
||||||
|
# Usage: sh tools/check-branding.sh (exit 0 = consistent, 1 = drift found)
|
||||||
|
#
|
||||||
|
# The canonical identity lives in branding.conf, shared with brand-plugin.sh.
|
||||||
|
# Edit it there, then run this to find every copy that still holds the old value.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
# Resolve the repo root: these live in tools/, every path they touch is
|
||||||
|
# repo-relative, and they must behave the same wherever they are invoked from.
|
||||||
|
cd "$(dirname "$0")/.." || exit 1
|
||||||
|
|
||||||
|
if [ ! -f tools/branding.conf ]; then
|
||||||
|
printf 'tools/branding.conf not found\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
. ./tools/branding.conf
|
||||||
|
|
||||||
|
# Every canonical spelling concatenated, for the substring test below.
|
||||||
|
CANONICAL_BLOB="$PKG
|
||||||
|
$DESCRIPTION
|
||||||
|
$CLAP_DESCRIPTION
|
||||||
|
$STRUCT
|
||||||
|
$PARAMS_STRUCT
|
||||||
|
$DISPLAY
|
||||||
|
$PLUGIN_NAME
|
||||||
|
$VENDOR
|
||||||
|
$EMAIL
|
||||||
|
$HOMEPAGE
|
||||||
|
$CLAP_ID
|
||||||
|
$VST3_CLASS_ID"
|
||||||
|
|
||||||
|
pass=0; fail=0; skip=0
|
||||||
|
ok() { pass=$((pass+1)); printf ' ok %s\n' "$1"; }
|
||||||
|
bad() { fail=$((fail+1)); printf ' FAIL %s\n' "$1"; [ $# -gt 1 ] && printf ' %s\n' "$2"; return 0; }
|
||||||
|
note() { skip=$((skip+1)); printf ' skip %s\n' "$1"; }
|
||||||
|
# Reported for awareness only - never counted, never fails the run.
|
||||||
|
info() { printf ' -- %s\n' "$1"; }
|
||||||
|
|
||||||
|
# has FILE PATTERN -> fixed-string search
|
||||||
|
has() { grep -qF "$2" "$1" 2>/dev/null; }
|
||||||
|
|
||||||
|
check() { # check LABEL FILE LITERAL
|
||||||
|
if [ ! -f "$2" ]; then bad "$1" "missing file: $2"; return; fi
|
||||||
|
if has "$2" "$3"; then ok "$1"; else bad "$1" "$2 does not contain: $3"; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Same as check, but a missing file is "not applicable yet" rather than drift.
|
||||||
|
# The template ships without a deploy script; this starts checking once you add one.
|
||||||
|
opt_check() { # opt_check LABEL FILE LITERAL
|
||||||
|
if [ ! -f "$2" ]; then note "$1 ($2 not present)"; return; fi
|
||||||
|
if has "$2" "$3"; then ok "$1"; else bad "$1" "$2 does not contain: $3"; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
printf 'branding check (canonical: %s / "%s")\n\n' "$PKG" "$DISPLAY"
|
||||||
|
|
||||||
|
# --- consistency: every site in the table holds its resolved literal ----------
|
||||||
|
# The table is data, so adding a site is a row rather than a shell edit, and the
|
||||||
|
# same script runs unmodified across every plugin built from this template.
|
||||||
|
SITES="tools/branding-sites.tsv"
|
||||||
|
if [ ! -f "$SITES" ]; then
|
||||||
|
bad "site table" "$SITES not found - run tools/brand-plugin.sh to generate it"
|
||||||
|
else
|
||||||
|
printf 'sites\n'
|
||||||
|
while IFS="$(printf '\t')" read -r kind owner file pattern; do
|
||||||
|
case "$kind" in ''|\#*) continue ;; esac
|
||||||
|
[ -n "$pattern" ] || continue
|
||||||
|
label="$owner"
|
||||||
|
if [ ! -f "$file" ]; then
|
||||||
|
case "$kind" in
|
||||||
|
opt) note "$label ($file not present)" ;;
|
||||||
|
*) bad "$label" "missing file: $file" ;;
|
||||||
|
esac
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if has "$file" "$pattern"; then ok "$label"
|
||||||
|
else bad "$label" "$file does not contain: $pattern"; fi
|
||||||
|
done < "$SITES"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- validity: rules the table cannot express --------------------------------
|
||||||
|
printf '\nvalidity\n'
|
||||||
|
|
||||||
|
# VST3 class IDs are a fixed 16-byte array. A wrong length is a compile error,
|
||||||
|
# but saying so here is clearer than reading rustc on it.
|
||||||
|
n=$(printf '%s' "$VST3_CLASS_ID" | wc -c | tr -d ' ')
|
||||||
|
if [ "$n" -eq 16 ]; then ok "VST3_CLASS_ID is 16 bytes"
|
||||||
|
else bad "VST3_CLASS_ID is 16 bytes" "got $n bytes: \"$VST3_CLASS_ID\""; fi
|
||||||
|
|
||||||
|
# CLAP IDs are reverse-DNS by convention and compared verbatim by hosts, so a
|
||||||
|
# stray space or capital is a silent identity change between machines.
|
||||||
|
case "$CLAP_ID" in
|
||||||
|
*\ *|*[A-Z]*) bad "CLAP_ID is reverse-DNS safe" "no spaces or capitals allowed: \"$CLAP_ID\"" ;;
|
||||||
|
*.*) ok "CLAP_ID is reverse-DNS safe" ;;
|
||||||
|
*) bad "CLAP_ID is reverse-DNS safe" "expected dotted reverse-DNS, got: \"$CLAP_ID\"" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# An overridden package name is legitimate - the derivation is sometimes invalid
|
||||||
|
# - but a display rename that forgot the package is not, and looks identical
|
||||||
|
# from here. Reported, never failed.
|
||||||
|
derived_pkg=$(_derive_pkg "$PLUGIN_NAME")
|
||||||
|
[ "$PKG" = "$derived_pkg" ] || info "PKG \"$PKG\" differs from the derivation of PLUGIN_NAME (\"$derived_pkg\") - fine if deliberate"
|
||||||
|
|
||||||
|
# CLAP_DESCRIPTION is shown to users in a host's plugin browser. "Free of
|
||||||
|
# development history" is a judgement a script cannot make, but the failure it
|
||||||
|
# produces is mechanical: a retired name or the repo codename surviving in it.
|
||||||
|
hygiene_hits=""
|
||||||
|
while IFS= read -r s_entry; do
|
||||||
|
[ -n "$s_entry" ] || continue
|
||||||
|
case "$CANONICAL_BLOB" in *"$s_entry"*) continue ;; esac
|
||||||
|
case "$CLAP_DESCRIPTION" in *"$s_entry"*) hygiene_hits="$hygiene_hits \"$s_entry\"" ;; esac
|
||||||
|
done <<HYGIENE_LIST
|
||||||
|
$STALE
|
||||||
|
$CODENAME
|
||||||
|
HYGIENE_LIST
|
||||||
|
if [ -z "$hygiene_hits" ]; then ok "CLAP_DESCRIPTION carries no retired name or codename"
|
||||||
|
else bad "CLAP_DESCRIPTION carries no retired name or codename" "found:$hygiene_hits"; fi
|
||||||
|
|
||||||
|
printf '\nstale spellings\n'
|
||||||
|
# File list for the scan below. Prefer git, so build output and any vendored
|
||||||
|
# tree stay out of it. Fall back to find when this is not a checkout - an
|
||||||
|
# exported tarball, or a CI step that fetched without git metadata - because
|
||||||
|
# `git ls-files` there returns nothing, and an empty list would report every
|
||||||
|
# stale spelling as absent. A check that passes hardest when it can see least
|
||||||
|
# is worse than no check.
|
||||||
|
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
|
file_list() { git ls-files; }
|
||||||
|
scan_source="git"
|
||||||
|
else
|
||||||
|
file_list() { find . -path ./.git -prune -o -path ./target -prune -o -type f -print | sed 's|^\./||'; }
|
||||||
|
scan_source="find (not a git checkout)"
|
||||||
|
fi
|
||||||
|
info "scanning tracked files via $scan_source"
|
||||||
|
# Fed by here-doc rather than a pipe on purpose: a piped `while` runs in a
|
||||||
|
# subshell, and the pass/fail counters it increments would be discarded.
|
||||||
|
while IFS= read -r s; do
|
||||||
|
[ -n "$s" ] || continue
|
||||||
|
|
||||||
|
# Still part of a canonical value? Then it is current branding, not a leftover.
|
||||||
|
# This is what lets the list ship pre-seeded: entries arm themselves as you
|
||||||
|
# rename. Substring, not equality, because placeholders nest - "your-domain"
|
||||||
|
# lives inside the canonical CLAP_ID, and "YOUR-USERNAME" inside the homepage.
|
||||||
|
# Over-skipping is the safe direction: a false FAIL on a name you still use
|
||||||
|
# would train you to ignore this section.
|
||||||
|
case "$CANONICAL_BLOB" in
|
||||||
|
*"$s"*) note "\"$s\" (still canonical)"; continue ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# These two name every retired spelling by definition - the STALE list lives in
|
||||||
|
# branding.conf and this script reads it - so neither can ever be a real hit.
|
||||||
|
hits=$(file_list 2>/dev/null | while read -r f; do
|
||||||
|
case "$f" in tools/check-branding.sh|tools/branding.conf) continue ;; esac
|
||||||
|
[ -f "$f" ] && grep -lF "$s" "$f" 2>/dev/null
|
||||||
|
done)
|
||||||
|
if [ -z "$hits" ]; then ok "no occurrences of \"$s\""
|
||||||
|
else bad "no occurrences of \"$s\"" "still present in: $(echo "$hits" | tr '\n' ' ')"; fi
|
||||||
|
done <<STALE_LIST
|
||||||
|
$STALE
|
||||||
|
STALE_LIST
|
||||||
|
|
||||||
|
# Template scaffolding, not drift: TODOs are legitimate while you are still
|
||||||
|
# building. Reported so a finished plugin does not quietly ship with them.
|
||||||
|
# `grep -c` prints 0 AND exits 1 when there are no matches, so `|| echo 0`
|
||||||
|
# would append a second zero and make the count unparseable. Swallow the
|
||||||
|
# status instead, and default an empty result (missing file) to zero.
|
||||||
|
todos=$(grep -c 'TODO' src/lib.rs 2>/dev/null || :)
|
||||||
|
[ -n "$todos" ] || todos=0
|
||||||
|
printf '\nscaffolding\n'
|
||||||
|
if [ "$todos" -eq 0 ]; then ok "no TODO markers left in src/lib.rs"
|
||||||
|
else info "$todos TODO marker(s) still in src/lib.rs (not a failure)"; fi
|
||||||
|
|
||||||
|
printf '\n%d passed, %d failed' "$pass" "$fail"
|
||||||
|
[ "$skip" -gt 0 ] && printf ', %d skipped' "$skip"
|
||||||
|
printf '\n'
|
||||||
|
[ "$fail" -eq 0 ] || exit 1
|
||||||
Reference in New Issue
Block a user