Compare commits
23 Commits
058d73f6cd
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 74dfdc50cc | |||
| 94d41d5c8a | |||
| b27ef57676 | |||
| c940f31ec7 | |||
| a8f1244b00 | |||
| d107127017 | |||
| d51de80de8 | |||
| 7a92485cf1 | |||
| 6c0332d79e | |||
| 309a152d58 | |||
| a62efe44b3 | |||
| 3a144064e3 | |||
| 3161aa9388 | |||
| d87934c428 | |||
| 1361d7c919 | |||
| 6ab9fb9c3f | |||
| 37f34e1175 | |||
| 73edf58b32 | |||
| 193cee0aef | |||
| 814dd5eacd | |||
| 466ab478c9 | |||
| 87a6f2e276 | |||
| 570dd65fa8 |
+19
-3
@@ -1,4 +1,20 @@
|
||||
# Shell scripts must keep LF endings. core.autocrlf=true on Windows would
|
||||
# otherwise check them out CRLF, which a strict POSIX sh (dash, CI containers)
|
||||
# rejects at the shebang.
|
||||
# Line endings: LF in the repository and in every working tree, on every OS.
|
||||
#
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'xtask/**'
|
||||
- 'Cargo.toml'
|
||||
- 'Cargo.lock'
|
||||
- 'bundler.toml'
|
||||
- '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:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'xtask/**'
|
||||
- 'Cargo.toml'
|
||||
- 'Cargo.lock'
|
||||
- 'bundler.toml'
|
||||
- 'tools/**'
|
||||
- '.github/workflows/**'
|
||||
workflow_dispatch:
|
||||
|
||||
# Target the self-hosted Rust/audio runner registered to the `musiikki` org.
|
||||
#
|
||||
# The runner is host-mode, so the rustup toolchain installed for the
|
||||
# `gitea-runner` user is used directly — nothing installs per run, and the cargo
|
||||
# registry cache stays warm between jobs.
|
||||
#
|
||||
# Adapted from musiikki/nih-plug-template. The template gates VST3 behind an
|
||||
# opt-in `vst3` feature and so builds twice; this plugin always ships VST3
|
||||
# (nih_plug is taken with default features), so one build covers both formats.
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: rust-audio
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Toolchain
|
||||
run: |
|
||||
cargo --version
|
||||
rustc --version
|
||||
echo "CARGO_HOME=${CARGO_HOME:?not set on this runner}"
|
||||
|
||||
# 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.
|
||||
- name: Branding consistency
|
||||
run: sh tools/check-branding.sh
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release
|
||||
|
||||
- name: Test
|
||||
run: cargo test --release
|
||||
|
||||
# Proves the xtask bundler still works, which a plain `cargo build` does
|
||||
# not exercise — the bundle step is where bundler.toml and the crate name
|
||||
# have to agree.
|
||||
- name: Bundle
|
||||
run: cargo xtask bundle bari_206 --release
|
||||
Generated
+8
-8
@@ -147,6 +147,14 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bari_206"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nih_plug",
|
||||
"nih_plug_egui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "baseview"
|
||||
version = "0.1.0"
|
||||
@@ -337,14 +345,6 @@ dependencies = [
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codename_206"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nih_plug",
|
||||
"nih_plug_egui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
|
||||
+11
-3
@@ -1,14 +1,14 @@
|
||||
[package]
|
||||
name = "codename_206"
|
||||
name = "bari_206"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Mikkeli Matlock <matlockib@gmail.com>"]
|
||||
authors = ["Mikkeli Matlock <mikkeli@novoyuuparosk.org>"]
|
||||
homepage = "https://github.com/mikkeli/codename-206"
|
||||
description = "A VST3/CLAP multiband compressor/limiter with a custom gain-curve display"
|
||||
# VST3 is on by default in nih_plug, and its bindings (vst3-sys) are GPLv3. Any
|
||||
# distributed VST3 binary built with them must therefore be GPL-3.0-or-later
|
||||
# (or covered by a commercial Steinberg licence). CLAP carries no such requirement.
|
||||
license = "Apache-2.0"
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[workspace]
|
||||
members = ["xtask"]
|
||||
@@ -17,6 +17,14 @@ members = ["xtask"]
|
||||
# `cdylib` is the plugin binary; `lib` (rlib) lets `cargo test` link the unit tests.
|
||||
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]
|
||||
# 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
|
||||
|
||||
@@ -1,201 +1,674 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
1. Definitions.
|
||||
Preamble
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
0. Definitions.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
1. Source Code.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
Copyright 2026 Mikkeli Matlock
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) 2026 Mikkeli Matlock
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) 2026 Mikkeli Matlock
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
Bari 206
|
||||
Copyright 2026 Mikkeli Matlock
|
||||
|
||||
Licensed under the GNU General Public License v3.0 or later.
|
||||
See LICENSE for the full terms.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Third-party material
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
The plugin scaffolding derives from the NIH-plug cookiecutter template and
|
||||
NIH-plug's example plugins, by Robbert van der Helm:
|
||||
|
||||
https://github.com/robbert-vdh/nih-plug-template
|
||||
https://github.com/robbert-vdh/nih-plug
|
||||
|
||||
Those portions are licensed under the ISC License:
|
||||
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2022 Robbert van der Helm
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||
OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
ISC is GPL-compatible, so this notice is retained rather than superseded.
|
||||
|
||||
"VST" is a trademark of Steinberg Media Technologies GmbH.
|
||||
@@ -1,4 +1,4 @@
|
||||
# Codename 206
|
||||
# Bari 206
|
||||
|
||||
*Called 206 because the Peugeot 206 has a 'maxi' variant. You'll know this is a Maximizer knockoff if you can follow that trail of thoughts.*
|
||||
Multiband Compressor / Limiter VST3 — Project Plan
|
||||
@@ -163,8 +163,8 @@ The project is already scaffolded (NIH-plug + nih_plug_egui, pinned to a fixed g
|
||||
|
||||
```powershell
|
||||
# Build + bundle the VST3 and CLAP
|
||||
cargo xtask bundle codename_206 --release
|
||||
# Output: target\bundled\Codename 206.vst3 and Codename 206.clap
|
||||
cargo xtask bundle bari_206 --release
|
||||
# Output: target\bundled\Bari 206.vst3 and Bari 206.clap
|
||||
```
|
||||
|
||||
### Deployment
|
||||
@@ -184,7 +184,7 @@ Use the provided script (no need to remember the details):
|
||||
`deploy.bat` is a double-click wrapper around the same script.
|
||||
|
||||
After deploying, in FL Studio: **Options → Manage plugins → tick "Rescan previously failed
|
||||
plugins" → Find installed plugins**, then search for **Codename 206**. (The rescan-failed step
|
||||
plugins" → Find installed plugins**, then search for **Bari 206**. (The rescan-failed step
|
||||
is essential — without it FL silently skips a plugin it has seen before.)
|
||||
|
||||
> **Known issue (deferred):** the **CLAP** build shows its name/vendor/type correctly in FL, but
|
||||
@@ -277,9 +277,34 @@ the editor being open:
|
||||
Never pass DSP state to the UI directly.
|
||||
|
||||
### VST3 licensing
|
||||
You must accept Steinberg's VST3 SDK licence before distributing VST3 binaries.
|
||||
NIH-plug's VST3 bindings are GPLv3; if you distribute, the plugin must also be GPLv3
|
||||
(or you need a commercial Steinberg licence). CLAP has no such restriction.
|
||||
This plugin is **GPL-3.0-or-later** (see `LICENSE`, and `NOTICE` for attribution).
|
||||
|
||||
`nih_export_vst3!()` links `vst3-sys`, which is GPLv3-or-later by its own authors' choice —
|
||||
they describe their port as a derivative of Steinberg's SDK that "was not made in isolation".
|
||||
That is what makes a distributed VST3 build GPL-licensed, and it holds regardless of any
|
||||
question about Steinberg's own rights.
|
||||
|
||||
No Steinberg SDK source is vendored or redistributed here, so there is nothing to accept at
|
||||
build time. Separately from copyright, **"VST" is a trademark of Steinberg Media Technologies
|
||||
GmbH**; describing a distributed product as a VST plugin is governed by their trademark terms,
|
||||
which no source licence resolves. CLAP carries neither encumbrance.
|
||||
|
||||
---
|
||||
|
||||
## CI
|
||||
|
||||
`.github/workflows/ci.yaml` gates pushes and PRs on the branding check, a release build,
|
||||
`cargo test`, and a bundle — enough to catch a rename that only half-landed, a build break,
|
||||
or a `bundler.toml` that no longer matches the crate name.
|
||||
|
||||
It targets `runs-on: rust-audio`, a self-hosted host-mode runner registered to the `musiikki`
|
||||
org. That label is org-scoped: a fork outside `musiikki` will not see the runner and its jobs
|
||||
will queue indefinitely rather than fail.
|
||||
|
||||
Plugin performance is deliberately not gated. Realtime cost is only meaningful against an
|
||||
audio deadline, and `Plugin::process()` cannot be called from a test at all — `ProcessContext`
|
||||
requires a `&Transport`, whose constructor and fields are `pub(crate)` to nih_plug. The DSP
|
||||
types under `src/dsp/` take no nih-plug context and are directly testable if you need it.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+2
-2
@@ -3,5 +3,5 @@
|
||||
# [package_name]
|
||||
# name = "Human Readable Plugin Name" # defaults to <package_name>
|
||||
|
||||
[codename_206]
|
||||
name = "Codename 206"
|
||||
[bari_206]
|
||||
name = "Bari 206"
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
#!/bin/sh
|
||||
# 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="codename_206" # Cargo package name == bundler.toml table key
|
||||
DISPLAY="Codename 206" # bundle name -> "<DISPLAY>.vst3" / "<DISPLAY>.clap"
|
||||
PLUGIN_NAME="206 prototype" # Plugin::NAME - what the DAW displays
|
||||
VENDOR="Novoyuuparosk" # Plugin::VENDOR
|
||||
EMAIL="mikkeli@novoyuuparosk.org" # Plugin::EMAIL
|
||||
HOMEPAGE="https://github.com/mikkeli/codename-206" # Cargo homepage -> Plugin::URL
|
||||
CLAP_ID="com.mikkeli.codename-206" # CLAP_ID - stable plugin identity
|
||||
VST3_CLASS_ID="Codename206Maxi!" # 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="Codename206" # the Plugin impl target (src/lib.rs)
|
||||
PARAMS_STRUCT="Codename206Params" # the Params impl target (src/params.rs)
|
||||
|
||||
# Seeded with this repo's CURRENT names, which is the point: each entry is
|
||||
# skipped while it is still canonical above, and arms itself the moment you
|
||||
# change that value. So the planned "codename 206" -> "206" rename is
|
||||
# self-checking - every spelling you miss turns into a FAIL here on the next run.
|
||||
STALE="codename_206
|
||||
codename-206
|
||||
Codename 206
|
||||
Codename206
|
||||
Codename206Params
|
||||
Codename206Maxi!
|
||||
206 prototype
|
||||
com.mikkeli.codename-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 '\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
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Build Codename 206 and install the VST3 (and CLAP) into the system plugin folders.
|
||||
Build Bari 206 and install the VST3 (and CLAP) into the system plugin folders.
|
||||
|
||||
.DESCRIPTION
|
||||
FL Studio scans `C:\Program Files\Common Files\VST3` by default and ignores
|
||||
@@ -25,7 +25,7 @@
|
||||
param(
|
||||
[switch]$SkipBuild,
|
||||
[switch]$User,
|
||||
[string]$Package = "codename_206"
|
||||
[string]$Package = "bari_206"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
@@ -42,8 +42,8 @@ if (-not $SkipBuild) {
|
||||
if ($LASTEXITCODE -ne 0) { throw "cargo xtask bundle failed (exit $LASTEXITCODE)" }
|
||||
}
|
||||
|
||||
$vst3Name = "Codename 206.vst3"
|
||||
$clapName = "Codename 206.clap"
|
||||
$vst3Name = "Bari 206.vst3"
|
||||
$clapName = "Bari 206.clap"
|
||||
$bundled = Join-Path $root "target\bundled"
|
||||
$vst3Src = Join-Path $bundled $vst3Name
|
||||
$clapSrc = Join-Path $bundled $clapName
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//! Generic second-order IIR biquad, Transposed Direct Form II.
|
||||
//!
|
||||
//! Coefficient formulas are the RBJ Audio EQ Cookbook
|
||||
|
||||
+82
-7
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//! Single full-band feed-forward compressor with look-ahead.
|
||||
//!
|
||||
//! Design follows Giannoulis, Massberg & Reiss, "Digital Dynamic Range Compressor
|
||||
@@ -50,7 +50,10 @@ const LOW_BULGE_MAX_DB: f32 = 12.0;
|
||||
|
||||
/// Hardcoded RMS averaging window (one-pole time constant). Deliberately small; can be
|
||||
/// promoted to a parameter later.
|
||||
const RMS_WINDOW_MS: f32 = 5.0;
|
||||
/// Default RMS averaging time constant for tests that do not care which band they model.
|
||||
/// Real instances pass a per-band value - see the `RMS_WINDOW_MS` table in `lib.rs`.
|
||||
#[cfg(test)]
|
||||
const RMS_WINDOW_DEFAULT_MS: f32 = 20.0;
|
||||
|
||||
// Denormals (the exponentially-decaying envelope/RMS tails and the IIR filter state) are handled
|
||||
// by the CPU's Flush-To-Zero mode, which NIH-plug enables around `process()`/`reset()` via its
|
||||
@@ -125,12 +128,23 @@ impl Compressor {
|
||||
|
||||
/// Allocate delay buffers for the worst-case look-ahead. Call from `initialize()`,
|
||||
/// where allocation is allowed — never from `process()`.
|
||||
pub fn prepare(&mut self, sample_rate: f32, num_channels: usize, max_lookahead_ms: f32) {
|
||||
/// `rms_window_ms` is the time constant of the RMS detector's one-pole average. It is a
|
||||
/// design constant per band rather than a user param: the squared signal carries a component
|
||||
/// at twice the fundamental, so a window that does not span a cycle leaves the detector
|
||||
/// rippling at 2f and the gain intermodulating with the bass. The needed window is therefore
|
||||
/// set by how low the band goes, which the band - not the compressor - knows.
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
sample_rate: f32,
|
||||
num_channels: usize,
|
||||
max_lookahead_ms: f32,
|
||||
rms_window_ms: f32,
|
||||
) {
|
||||
self.sample_rate = sample_rate;
|
||||
self.fixed_delay = (max_lookahead_ms * 0.001 * sample_rate).ceil() as usize;
|
||||
// +1 so the oldest (output) sample and the newest (write) sample never alias.
|
||||
self.capacity = self.fixed_delay + 1;
|
||||
self.rms_coef = Self::time_to_coef(RMS_WINDOW_MS, sample_rate);
|
||||
self.rms_coef = Self::time_to_coef(rms_window_ms, sample_rate);
|
||||
let channels = num_channels.clamp(1, MAX_CHANNELS);
|
||||
self.delay = vec![vec![0.0; self.capacity]; channels];
|
||||
self.reset();
|
||||
@@ -379,7 +393,7 @@ mod tests {
|
||||
// Constant 0.5 (-6.02 dBFS) into a -18 dB / 2:1 / hard-knee comp:
|
||||
// reduction = -0.5 * (−6.02 − −18) = −5.99 dB, so output ≈ 0.5 * 10^(−5.99/20).
|
||||
let mut comp = Compressor::new();
|
||||
comp.prepare(SR, 1, MAX_LOOKAHEAD_MS);
|
||||
comp.prepare(SR, 1, MAX_LOOKAHEAD_MS, RMS_WINDOW_DEFAULT_MS);
|
||||
let set = settings(-18.0, 2.0, 0.0);
|
||||
let mut out = [0.0f32];
|
||||
for _ in 0..SR as usize {
|
||||
@@ -399,7 +413,7 @@ mod tests {
|
||||
|
||||
fn output_rms(use_rms: bool) -> f32 {
|
||||
let mut comp = Compressor::new();
|
||||
comp.prepare(SR, 1, MAX_LOOKAHEAD_MS);
|
||||
comp.prepare(SR, 1, MAX_LOOKAHEAD_MS, RMS_WINDOW_DEFAULT_MS);
|
||||
let mut set = settings(-30.0, 4.0, 0.0);
|
||||
set.use_rms = use_rms;
|
||||
set.attack_coef = Compressor::time_to_coef(1.0, SR);
|
||||
@@ -433,7 +447,7 @@ mod tests {
|
||||
// knob must NOT change that (this is what keeps live adjustment from renegotiating
|
||||
// latency and crashing the host).
|
||||
let mut comp = Compressor::new();
|
||||
comp.prepare(SR, 1, 0.5); // small max so the fixed delay is quick to test
|
||||
comp.prepare(SR, 1, 0.5, RMS_WINDOW_DEFAULT_MS); // small max so the fixed delay is quick to test
|
||||
let d = comp.latency() as usize;
|
||||
assert!(d > 0);
|
||||
|
||||
@@ -455,4 +469,65 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Measure peak-to-peak gain-reduction ripple for a steady sine, with the ballistics
|
||||
/// disabled so the gain follows the detector directly. Squaring puts a component at 2f in
|
||||
/// the detector, so a window that does not span a cycle leaves the gain wobbling at 2f -
|
||||
/// which is intermodulation, heard as growl on bass.
|
||||
fn gr_ripple_db(rms_window_ms: f32, freq: f32) -> f32 {
|
||||
let mut c = Compressor::new();
|
||||
c.prepare(SR, 1, MAX_LOOKAHEAD_MS, rms_window_ms);
|
||||
let set = CompressorSettings {
|
||||
threshold_db: -40.0, ratio: 8.0, knee_db: 0.0,
|
||||
// coef 0 = no attack/release smoothing, so yl == the detector's target.
|
||||
attack_coef: 0.0, release_coef: 0.0,
|
||||
makeup_db: 0.0, mix: 1.0, lookahead_samples: 0,
|
||||
use_rms: true, low_slope: 1.0, low_curve: 0.0,
|
||||
};
|
||||
let mut out = [0.0f32];
|
||||
// Settle well past the longest window under test.
|
||||
let settle = (SR * 0.5) as usize;
|
||||
for i in 0..settle {
|
||||
let x = (i as f32 / SR * freq * std::f32::consts::TAU).sin();
|
||||
c.process(&[x], &mut out, &set);
|
||||
}
|
||||
let (mut lo, mut hi) = (f32::MAX, f32::MIN);
|
||||
for i in settle..settle + (SR / freq * 4.0) as usize {
|
||||
let x = (i as f32 / SR * freq * std::f32::consts::TAU).sin();
|
||||
c.process(&[x], &mut out, &set);
|
||||
let gr = c.gain_reduction_db();
|
||||
lo = lo.min(gr);
|
||||
hi = hi.max(gr);
|
||||
}
|
||||
hi - lo
|
||||
}
|
||||
|
||||
/// The per-band RMS windows in `lib.rs` exist to keep this ripple down; without a test the
|
||||
/// next person to "tune" them has nothing telling them what they are for.
|
||||
#[test]
|
||||
fn rms_window_suppresses_low_frequency_ripple() {
|
||||
// The LOW band's 50 ms window against the lowest content it can be asked to carry.
|
||||
let low_band = gr_ripple_db(50.0, 30.0);
|
||||
assert!(low_band < 1.0, "50 ms window rippled {low_band:.2} dB at 30 Hz, expected < 1 dB");
|
||||
|
||||
// A short window on the same signal must be visibly worse - this is what the per-band
|
||||
// table is avoiding, and it keeps the assertion above from passing for the wrong reason.
|
||||
let short = gr_ripple_db(5.0, 30.0);
|
||||
assert!(
|
||||
short > 3.0 * low_band,
|
||||
"5 ms ({short:.2} dB) should ripple far more than 50 ms ({low_band:.2} dB)"
|
||||
);
|
||||
|
||||
// Ripple must fall monotonically as the window grows.
|
||||
let mut prev = f32::MAX;
|
||||
for w in [5.0, 10.0, 20.0, 35.0, 50.0] {
|
||||
let r = gr_ripple_db(w, 30.0);
|
||||
assert!(r < prev, "ripple did not fall from the previous window at {w} ms ({r:.2} dB)");
|
||||
prev = r;
|
||||
}
|
||||
|
||||
// The HIGH band's short window is fine because nothing up there ripples.
|
||||
let high_band = gr_ripple_db(10.0, 5_000.0);
|
||||
assert!(high_band < 0.1, "10 ms window rippled {high_band:.2} dB at 5 kHz");
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//! 3-band Linkwitz-Riley (LR4, 24 dB/oct) crossover filterbank.
|
||||
//!
|
||||
//! Each crossover splits into a low-passed band output and a high-passed remainder that feeds
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//! Look-ahead brickwall peak limiter (base-rate).
|
||||
//!
|
||||
//! Guarantees the output never exceeds the ceiling. A short fixed look-ahead lets the gain ramp
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//! DSP building blocks for Codename 206.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//! DSP building blocks for Bari 206.
|
||||
//!
|
||||
//! The signal chain: a `crossover` filterbank splits into bands, each band (plus the summed
|
||||
//! 'All' channel) runs a `compressor`, and a `limiter` (with a true-peak `oversampler` detector)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//! 4× polyphase interpolation for **true-peak (inter-sample) detection only**.
|
||||
//!
|
||||
//! A band-limited signal can overshoot its sample values between samples, so the digital sample
|
||||
|
||||
+13
-12
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//! Draggable crossover handles — replaces the two plain crossover sliders.
|
||||
//!
|
||||
//! A horizontal log-frequency strip split into LOW / MID / HIGH by two draggable handles, plus a
|
||||
@@ -10,7 +10,8 @@ use nih_plug_egui::egui::{
|
||||
self, pos2, vec2, Align2, Color32, CornerRadius, DragValue, FontId, Painter, Rect, Sense, Stroke,
|
||||
};
|
||||
|
||||
use crate::params::Codename206Params;
|
||||
use crate::params::Bari206Params;
|
||||
use super::palette;
|
||||
|
||||
/// Height of the handle strip.
|
||||
const GRAPH_H: f32 = 54.0;
|
||||
@@ -30,7 +31,7 @@ fn fmt_hz(hz: f32) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn draw(ui: &mut egui::Ui, params: &Codename206Params, setter: &ParamSetter) {
|
||||
pub(super) fn draw(ui: &mut egui::Ui, params: &Bari206Params, setter: &ParamSetter) {
|
||||
let lo = params.crossover_low_hz.value();
|
||||
let hi = params.crossover_high_hz.value();
|
||||
// Each param's own min/max (normalized 0/1 map to the range ends).
|
||||
@@ -55,19 +56,19 @@ pub(super) fn draw(ui: &mut egui::Ui, params: &Codename206Params, setter: &Param
|
||||
let xhi = x_for(hi);
|
||||
|
||||
// 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(xlo, top), pos2(xhi, bottom)), CornerRadius::ZERO, Color32::from_rgb(30, 48, 36));
|
||||
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(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, palette::CHANNELS[1].strip);
|
||||
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| {
|
||||
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((xlo + xhi) * 0.5, "MID");
|
||||
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_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_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, palette::CHANNELS[2].handle);
|
||||
|
||||
// Number boxes (double-click to type). Clamped to the same dynamic limits.
|
||||
ui.horizontal(|ui| {
|
||||
@@ -129,9 +130,9 @@ fn handle(
|
||||
setter.end_set_parameter(param);
|
||||
}
|
||||
|
||||
let col = if resp.dragged() || resp.hovered() { Color32::WHITE } else { color };
|
||||
p.line_segment([pos2(x, top), pos2(x, bottom)], Stroke::new(2.0, col));
|
||||
p.text(pos2(x, bottom - 1.0), Align2::CENTER_BOTTOM, fmt_hz(hz), FontId::proportional(10.0), Color32::from_gray(220));
|
||||
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_f32, col));
|
||||
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).
|
||||
|
||||
+17
-16
@@ -1,23 +1,24 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//! Static gain-curve display: output level vs input level for the channel currently selected in
|
||||
//! the plot. Plots the wet transfer `out = (in + pre_gain) + gain_reduction(...) + makeup` (mix not
|
||||
//! folded in; output clamped at 0 dBFS), plus a live **operating-point fill** under the curve up to
|
||||
//! the channel's current input level — its right edge rides the curve (width = input, height = out).
|
||||
|
||||
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 crate::dsp::compressor::Compressor;
|
||||
use crate::meters::Meters;
|
||||
use crate::params::Codename206Params;
|
||||
use crate::params::Bari206Params;
|
||||
use super::palette;
|
||||
|
||||
/// Side length of the square plot.
|
||||
const CURVE_SIZE: f32 = 150.0;
|
||||
/// dB extent of both axes (bottom/left = FLOOR_DB, top/right = 0 dBFS).
|
||||
const FLOOR_DB: f32 = -60.0;
|
||||
|
||||
pub(super) fn draw(ui: &mut egui::Ui, params: &Codename206Params, selected: usize, meters: &Meters) {
|
||||
pub(super) fn draw(ui: &mut egui::Ui, params: &Bari206Params, selected: usize, meters: &Meters) {
|
||||
let labels = ["LOW", "MID", "HIGH", "ALL"];
|
||||
let ch = selected.min(3);
|
||||
let cp = match ch {
|
||||
@@ -37,7 +38,7 @@ pub(super) fn draw(ui: &mut egui::Ui, params: &Codename206Params, selected: usiz
|
||||
ui.label(format!("Curve: {}", labels[ch]));
|
||||
let (rect, _) = ui.allocate_exact_size(vec2(CURVE_SIZE, CURVE_SIZE), Sense::hover());
|
||||
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 (left, right, top, bottom) =
|
||||
@@ -48,17 +49,17 @@ pub(super) fn draw(ui: &mut egui::Ui, params: &Codename206Params, selected: usiz
|
||||
let y_for = |db: f32| bottom - (db - FLOOR_DB) / -FLOOR_DB * h;
|
||||
|
||||
// -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 y6 = y_for(-6.0);
|
||||
p.line_segment([pos2(x6, top), pos2(x6, bottom)], Stroke::new(1.0, g6));
|
||||
p.line_segment([pos2(left, y6), pos2(right, y6)], 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_f32, g6));
|
||||
|
||||
// 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).
|
||||
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.)
|
||||
let n = 96;
|
||||
@@ -74,7 +75,7 @@ pub(super) fn draw(ui: &mut egui::Ui, params: &Codename206Params, selected: usiz
|
||||
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 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) {
|
||||
let a = seg[0];
|
||||
let mut b = seg[1];
|
||||
@@ -92,16 +93,16 @@ pub(super) fn draw(ui: &mut egui::Ui, params: &Codename206Params, selected: usiz
|
||||
));
|
||||
}
|
||||
|
||||
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.
|
||||
let driven = ext_in + pre;
|
||||
let gr = Compressor::gain_computer(driven, threshold, ratio, knee, low_slope, low_curve);
|
||||
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.
|
||||
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, bottom - 1.0), Align2::LEFT_BOTTOM, "-60", FontId::proportional(9.0), Color32::from_gray(90));
|
||||
p.text(pos2(x6 + 2.0, bottom - 1.0), Align2::LEFT_BOTTOM, "-6", FontId::proportional(9.0), Color32::from_gray(80));
|
||||
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), palette::TEXT_DIM);
|
||||
p.text(pos2(x6 + 2.0, bottom - 1.0), Align2::LEFT_BOTTOM, "-6", FontId::proportional(9.0), palette::TEXT_DIM);
|
||||
}
|
||||
|
||||
+26
-20
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//! Per-channel level + gain-reduction meters, each with its own latching ceiling/over lamp.
|
||||
//!
|
||||
//! Each channel is a `|L | GR | R|` cluster (output level left/right, mono gain reduction in the
|
||||
@@ -15,6 +15,7 @@ use std::sync::atomic::Ordering;
|
||||
|
||||
use super::METER_FLOOR_DB;
|
||||
use crate::meters::{Meters, NUM_CHANNELS};
|
||||
use super::palette;
|
||||
|
||||
/// Full-scale of the gain-reduction bar (fills downward from the top).
|
||||
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, _) =
|
||||
ui.allocate_exact_size(vec2(ui.available_width(), METER_PANEL_H), Sense::hover());
|
||||
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 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 {
|
||||
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 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_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 + gap, bar_w, top, bottom, gr_frac, Color32::from_rgb(240, 150, 60), 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, 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, palette::SIGNAL_GR, true);
|
||||
v_bar(&p, bx + 2.0 * (bar_w + gap), bar_w, top, bottom, r_frac, palette::level_color(r_db), false);
|
||||
|
||||
p.text(
|
||||
pos2(cell_left + cell_w * 0.5, rect.bottom() - 2.0),
|
||||
Align2::CENTER_BOTTOM,
|
||||
labels[i],
|
||||
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
|
||||
@@ -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() {
|
||||
Color32::from_rgb(255, 40, 40)
|
||||
palette::LAMP_ON
|
||||
} else {
|
||||
Color32::from_rgb(40, 12, 12)
|
||||
palette::LAMP_OFF
|
||||
};
|
||||
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
|
||||
/// 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) {
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
+7
-6
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//! egui editor: assembly + control layout.
|
||||
//!
|
||||
//! The aggregator. Builds the editor window and lays out the heading, the meter panel
|
||||
@@ -17,12 +17,13 @@ use nih_plug_egui::{
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::meters::Meters;
|
||||
use crate::params::{Codename206Params, CompressorParams};
|
||||
use crate::Codename206;
|
||||
use crate::params::{Bari206Params, CompressorParams};
|
||||
use crate::Bari206;
|
||||
|
||||
mod crossover;
|
||||
mod gain_curve;
|
||||
mod meter;
|
||||
mod palette;
|
||||
mod plot;
|
||||
|
||||
/// Bottom of the dB scale shared by the meters and the plot (top is 0 dBFS).
|
||||
@@ -36,12 +37,12 @@ struct EditorState {
|
||||
}
|
||||
|
||||
/// Build the plugin editor over shared handles to the params and meter state.
|
||||
pub(crate) fn create(params: Arc<Codename206Params>, meters: Arc<Meters>) -> Option<Box<dyn Editor>> {
|
||||
pub(crate) fn create(params: Arc<Bari206Params>, meters: Arc<Meters>) -> Option<Box<dyn Editor>> {
|
||||
let egui_state = params.editor_state.clone();
|
||||
create_egui_editor(
|
||||
params.editor_state.clone(),
|
||||
EditorState::default(),
|
||||
|_, _| {},
|
||||
|egui_ctx, _| palette::apply_visuals(egui_ctx),
|
||||
move |egui_ctx, setter, state| {
|
||||
// Keep frames coming so the meters animate and the lamp can time out while open.
|
||||
egui_ctx.request_repaint();
|
||||
@@ -79,7 +80,7 @@ pub(crate) fn create(params: Arc<Codename206Params>, meters: Arc<Meters>) -> Opt
|
||||
.min_size(Vec2::new(480.0, 320.0))
|
||||
.show(egui_ctx, egui_state.as_ref(), |ui| {
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.heading(Codename206::NAME);
|
||||
ui.heading(Bari206::NAME);
|
||||
meter::draw(ui, &meters, &mut state.meter);
|
||||
ui.separator();
|
||||
// Gain curve (left, square) beside the scrolling plot (right, fills the rest).
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
+11
-10
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//! Rolling input/output/gain-reduction plot with per-channel tabs and a flow-speed selector.
|
||||
//!
|
||||
//! Histories for all four channels run continuously (cheap), so switching tabs shows that
|
||||
@@ -12,15 +12,16 @@ use nih_plug_egui::egui::{self, pos2, vec2, Align2, Color32, CornerRadius, FontI
|
||||
|
||||
use super::METER_FLOOR_DB;
|
||||
use crate::meters::{Meters, BUCKET_HZ, NUM_CHANNELS};
|
||||
use super::palette;
|
||||
|
||||
/// Height of the plot panel.
|
||||
const PLOT_PANEL_H: f32 = 150.0;
|
||||
/// Number of columns held in the history ring.
|
||||
const PLOT_N: usize = 256;
|
||||
|
||||
const COLOR_IN: Color32 = Color32::from_rgb(90, 170, 235);
|
||||
const COLOR_OUT: Color32 = Color32::from_rgb(90, 200, 110);
|
||||
const COLOR_GR: Color32 = Color32::from_rgb(240, 150, 60);
|
||||
const COLOR_IN: Color32 = palette::SIGNAL_IN;
|
||||
const COLOR_OUT: Color32 = palette::SIGNAL_OUT;
|
||||
const COLOR_GR: Color32 = palette::SIGNAL_GR;
|
||||
|
||||
/// Rolling history for all channels: a per-channel ring of (in_db, out_db, gr_db) columns.
|
||||
struct PlotHistory {
|
||||
@@ -156,7 +157,7 @@ pub(super) fn draw(ui: &mut egui::Ui, meters: &Meters, state: &mut PlotState) {
|
||||
let (rect, _) =
|
||||
ui.allocate_exact_size(vec2(ui.available_width(), PLOT_PANEL_H), Sense::hover());
|
||||
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) =
|
||||
(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).
|
||||
for &g in &[0.0f32, -12.0, -24.0, -48.0] {
|
||||
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(
|
||||
pos2(left + 2.0, y),
|
||||
Align2::LEFT_BOTTOM,
|
||||
format!("{g:.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
|
||||
// concave polygon mis-tessellates in egui (it fans from one corner, leaving stray
|
||||
// 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) {
|
||||
let (a, b) = (seg[0], seg[1]);
|
||||
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.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
|
||||
// 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 marker = Color32::from_rgb(235, 45, 45);
|
||||
let marker = palette::CLIP;
|
||||
for k in 0..len {
|
||||
let idx = (write + PLOT_N - len + k) % PLOT_N;
|
||||
if state.history.hit[idx] {
|
||||
|
||||
+33
-18
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
use nih_plug::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -11,7 +11,7 @@ use dsp::compressor::{Compressor, MAX_LOOKAHEAD_MS};
|
||||
use dsp::crossover::Crossover;
|
||||
use dsp::limiter::Limiter;
|
||||
use meters::Meters;
|
||||
use params::{build_settings, Codename206Params};
|
||||
use params::{build_settings, Bari206Params};
|
||||
|
||||
/// Peak-meter fall: after this long of silence the bars decay by 12 dB. (Matches nih-plug's
|
||||
/// gain-gui example feel.)
|
||||
@@ -23,13 +23,28 @@ const MID: usize = 1;
|
||||
const HIGH: usize = 2;
|
||||
const ALL: usize = 3;
|
||||
|
||||
/// Codename 206 — Stage 3: 3-band crossover + per-band compressors summed into an 'All' channel.
|
||||
/// RMS detector time constants, one per channel, indexed by the consts above.
|
||||
///
|
||||
/// Squaring the detector signal puts a component at twice the fundamental, so a one-pole that
|
||||
/// does not span a cycle leaves the detector rippling and the gain intermodulating with the
|
||||
/// bass. Measured peak-to-peak detector ripple at a 5 ms window was 4.4 dB at 30 Hz and 2.7 dB
|
||||
/// at 50 Hz - squarely in the low band, whose crossover reaches down to 30 Hz. The cost of a
|
||||
/// longer window is response time (95% of a step: ~12 ms at 5 ms, ~117 ms at 50 ms), so each
|
||||
/// band gets only as much as its frequency range actually needs:
|
||||
///
|
||||
/// * LOW - 50 ms: 30 Hz ripple drops to 0.46 dB, and slow is the wanted behaviour down here.
|
||||
/// * MID - 20 ms: enough for body without dulling the band that carries most transients.
|
||||
/// * HIGH - 10 ms: nothing up here ripples, so keep it responsive.
|
||||
/// * ALL - 35 ms: full-band, so it must cover the low content too.
|
||||
const RMS_WINDOW_MS: [f32; 4] = [50.0, 20.0, 10.0, 35.0];
|
||||
|
||||
/// Bari 206 — Stage 3: 3-band crossover + per-band compressors summed into an 'All' channel.
|
||||
///
|
||||
/// Signal: input → LR4 crossover → {low, mid, high} each through their own compressor → sum →
|
||||
/// 'All' compressor → output. Bypassing low+mid+high collapses it to a plain full-band comp
|
||||
/// driven by the 'All' channel (the crossover sums flat).
|
||||
struct Codename206 {
|
||||
params: Arc<Codename206Params>,
|
||||
struct Bari206 {
|
||||
params: Arc<Bari206Params>,
|
||||
sample_rate: f32,
|
||||
crossover: Crossover,
|
||||
/// Compressors indexed by [`LOW`], [`MID`], [`HIGH`], [`ALL`].
|
||||
@@ -57,10 +72,10 @@ struct Codename206 {
|
||||
/// Limiter gain reduction (dB) above which a plot bucket is flagged as hitting the ceiling.
|
||||
const CEILING_HIT_GR_DB: f32 = 0.1;
|
||||
|
||||
impl Default for Codename206 {
|
||||
impl Default for Bari206 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
params: Arc::new(Codename206Params::default()),
|
||||
params: Arc::new(Bari206Params::default()),
|
||||
sample_rate: 48_000.0,
|
||||
crossover: Crossover::new(),
|
||||
comps: [Compressor::new(), Compressor::new(), Compressor::new(), Compressor::new()],
|
||||
@@ -77,14 +92,14 @@ impl Default for Codename206 {
|
||||
}
|
||||
}
|
||||
|
||||
impl Codename206 {
|
||||
impl Bari206 {
|
||||
fn lookahead_samples(&self) -> usize {
|
||||
(self.params.look_ahead_ms.value() * 0.001 * self.sample_rate).round() as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for Codename206 {
|
||||
const NAME: &'static str = "206 prototype";
|
||||
impl Plugin for Bari206 {
|
||||
const NAME: &'static str = "Bari 206";
|
||||
const VENDOR: &'static str = "Novoyuuparosk";
|
||||
const URL: &'static str = env!("CARGO_PKG_HOMEPAGE");
|
||||
const EMAIL: &'static str = "mikkeli@novoyuuparosk.org";
|
||||
@@ -140,8 +155,8 @@ impl Plugin for Codename206 {
|
||||
self.scope_bucket_len =
|
||||
((self.sample_rate / meters::BUCKET_HZ as f32).round() as usize).max(1);
|
||||
|
||||
for comp in &mut self.comps {
|
||||
comp.prepare(self.sample_rate, channels, MAX_LOOKAHEAD_MS);
|
||||
for (i, comp) in self.comps.iter_mut().enumerate() {
|
||||
comp.prepare(self.sample_rate, channels, MAX_LOOKAHEAD_MS, RMS_WINDOW_MS[i]);
|
||||
}
|
||||
self.crossover.prepare(channels);
|
||||
self.crossover.update(
|
||||
@@ -388,8 +403,8 @@ impl Plugin for Codename206 {
|
||||
}
|
||||
}
|
||||
|
||||
impl ClapPlugin for Codename206 {
|
||||
const CLAP_ID: &'static str = "com.mikkeli.codename-206";
|
||||
impl ClapPlugin for Bari206 {
|
||||
const CLAP_ID: &'static str = "org.novoyuuparosk.bari-206";
|
||||
const CLAP_DESCRIPTION: Option<&'static str> =
|
||||
Some("Multiband compressor/limiter (stage 3: 3-band + 'All' channel)");
|
||||
const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL);
|
||||
@@ -403,11 +418,11 @@ impl ClapPlugin for Codename206 {
|
||||
];
|
||||
}
|
||||
|
||||
impl Vst3Plugin for Codename206 {
|
||||
const VST3_CLASS_ID: [u8; 16] = *b"Codename206Maxi!";
|
||||
impl Vst3Plugin for Bari206 {
|
||||
const VST3_CLASS_ID: [u8; 16] = *b"Bari206Multiband";
|
||||
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] =
|
||||
&[Vst3SubCategory::Fx, Vst3SubCategory::Dynamics];
|
||||
}
|
||||
|
||||
nih_export_clap!(Codename206);
|
||||
nih_export_vst3!(Codename206);
|
||||
nih_export_clap!(Bari206);
|
||||
nih_export_vst3!(Bari206);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//! Lock-free meter state shared from the audio thread to the editor.
|
||||
//!
|
||||
//! Two feeds, both written by `process()` (single producer) and read by the editor (single
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//! Plugin parameters and their layout.
|
||||
//!
|
||||
//! Holds the global controls plus four `CompressorParams` blocks (low/mid/high + the 'All'
|
||||
@@ -23,7 +23,7 @@ pub enum DetectionMode {
|
||||
}
|
||||
|
||||
#[derive(Params)]
|
||||
pub struct Codename206Params {
|
||||
pub struct Bari206Params {
|
||||
#[persist = "editor-state"]
|
||||
pub editor_state: Arc<EguiState>,
|
||||
|
||||
@@ -87,7 +87,7 @@ pub struct CompressorParams {
|
||||
pub mix: FloatParam,
|
||||
}
|
||||
|
||||
impl Default for Codename206Params {
|
||||
impl Default for Bari206Params {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
editor_state: EguiState::from_size(760, 520),
|
||||
|
||||
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
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
fn main() -> nih_plug_xtask::Result<()> {
|
||||
nih_plug_xtask::main()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user