Quench splits the signal with a Linkwitz-Riley crossover and lets the sub pass unclipped. The top is clipped symmetrically at the ceiling minus the sub's held peak, so it does not pump at the sub's rate and the sum stays under the ceiling. What the clip removes is high-passed at the split, so its products stay out of the low band. Two correction passes and an oversampled hard clip hold the ceiling; a safety clip catches the downsampler's ringing. - Controls: Drive, Ceiling, Split, Hold, Soft, Bypass. - Listen to Quench, a plain clipper at the same settings, or the delta. - Editor meter compares how much the low band changed for Quench and for a plain clip, with a scope of the output, the sub and the top's room. - Measured: sub level +0.04 dB against -2.2 dB for a plain clip, low-band products -35 dB against -16 dB, at 0.5-1.2 dB less RMS. - Registered in the catalog, workflows, editor-check, README, chain and development docs, and the website copy.
162 lines
7.6 KiB
Markdown
162 lines
7.6 KiB
Markdown
# Development
|
||
|
||
Run commands from the repository root. You need Rust stable and Python **3.11 or
|
||
newer**. Lockfiles are committed for the root workspace, every CLAP wrapper and
|
||
the headless editor tool. Use `--locked` so a build never changes them silently.
|
||
|
||
## Product boundaries
|
||
|
||
[plugins/catalog.json](../plugins/catalog.json) is the single list of products.
|
||
For each product it defines the display name, the wrapper manifest, the library
|
||
name, the documentation folder, the CLAP ID, the internal packages and the
|
||
licenses to ship. The build, check, package and release scripts and the script
|
||
tests read it. The version in each wrapper's `Cargo.toml` is the release version.
|
||
|
||
Every product has the same split:
|
||
|
||
| Folder | Content | Workspace |
|
||
| --- | --- | --- |
|
||
| `plugins/<product>/core/` | Engine. Plain Rust, no plugin SDK, tested on its own | root |
|
||
| `plugins/<product>/ui/` | egui editor, built on `common-ui` | root |
|
||
| `plugins/<product>/plugin/` | CLAP wrapper on a pinned NIH-plug revision | own |
|
||
|
||
Rivet has more crates in the root workspace (`midi`, `app`, `preview`), and
|
||
Patina has `render` for offline WAV evaluation. The shared crates are:
|
||
|
||
- `common-ui`: palette, embedded fonts, header, panels, knobs, meters.
|
||
- `common-dsp`: filters, YIN pitch tracking, the note tracker used by Anvil,
|
||
Lathe, Temper, Knurl, Planish and Braze, 4× oversampling (also Quench's), meters and WAV files.
|
||
|
||
The wrappers stay separate workspaces, each with its own lockfile, so the
|
||
NIH-plug Git dependency stays out of the root workspace. Host identities and
|
||
saved state are separate for every product.
|
||
|
||
The engines run allocation-free on the audio thread. Analysis that is too heavy
|
||
for the audio thread (Rivet's generation jobs, Weld's cross-spectrum and fit) runs on a worker
|
||
thread. Data goes to the editor through lock-free queues.
|
||
|
||
## Checks
|
||
|
||
```sh
|
||
cargo test --workspace --locked
|
||
cargo clippy --workspace --all-targets --locked -- -D warnings
|
||
python3 scripts/check-plugin.py --plugin <product>
|
||
python3 -m unittest discover -s scripts/tests -v
|
||
```
|
||
|
||
Products: `rivet`, `patina`, `anvil`, `crucible`, `lathe`, `temper`, `weld`,
|
||
`knurl`, `planish`, `braze`, `quench`.
|
||
|
||
`check-plugin.py` runs formatting, tests and clippy for one product's packages
|
||
and its wrapper, without compiling the other products. Run it for every product
|
||
when you change a shared crate or the root workspace. Offline builds work once
|
||
the dependencies are cached; add `--offline` to manual Cargo commands.
|
||
|
||
The test types are:
|
||
|
||
- **Engine tests** (`core/tests/dsp.rs` and similar) measure signal behaviour
|
||
and latency against fixed targets.
|
||
- **Realtime tests** (`core/tests/realtime.rs`) install a counting allocator
|
||
and fail if the audio-thread path allocates.
|
||
- **Host tests** (`plugin/tests/host.rs`) load the wrapper through the exported
|
||
CLAP ABI without a DAW. They check the ports, latency, state save and load,
|
||
and processing at 44.1, 48 and 96 kHz with odd block sizes.
|
||
- **Script tests** (`scripts/tests/`) cover the catalog, release planning,
|
||
packaging and the publish client with fake transports. They also fail when a
|
||
catalog product is missing from a CI workflow.
|
||
|
||
The native wrapper tests on Linux need pkg-config and the X11/XCB and OpenGL
|
||
development headers. Rivet's optional standalone also needs ALSA/JACK headers.
|
||
The Gitea check jobs install these headers.
|
||
|
||
## Windows builds
|
||
|
||
On Windows, install Rust and the MSVC build tools. To cross-compile on Linux,
|
||
install the Rust `x86_64-pc-windows-gnu` target and MinGW
|
||
(`x86_64-w64-mingw32-gcc`).
|
||
|
||
```sh
|
||
python3 scripts/build-plugin.py --plugin <product> --target x86_64-pc-windows-gnu
|
||
python3 scripts/package-windows.py # combined preview ZIP
|
||
python3 scripts/package-windows.py --plugin <product> # one product's release ZIP
|
||
```
|
||
|
||
Builds go to `dist/<target>/<Name>.clap`, with the docs and licenses in the same
|
||
folder structure. `CARGO_TARGET_DIR` is supported for sharing one build
|
||
directory between wrappers. MSVC packaging accepts
|
||
`--target x86_64-pc-windows-msvc`.
|
||
|
||
- The combined ZIP is `dist/bass-foundry-windows-x86_64.zip`. It holds every
|
||
product that has been built.
|
||
- A product ZIP is `<product>-windows-x86_64-<version>.zip` and holds only that
|
||
product.
|
||
|
||
Every ZIP has a `SHA256SUMS` file inside and a `.zip.sha256` file next to it.
|
||
ZIP metadata is deterministic; the binaries still depend on the toolchain and
|
||
the build environment. Packaging takes only the selected catalog entries, never
|
||
old binaries left in `dist`.
|
||
|
||
Linux builds are for local development; CI produces only Windows binaries. No
|
||
script installs a plugin into a DAW.
|
||
|
||
## Editor previews
|
||
|
||
```sh
|
||
mkdir -p target/editor
|
||
cargo run --manifest-path tools/editor-check/Cargo.toml -- target/editor/<product>.png --plugin <product>
|
||
cargo run --manifest-path tools/editor-check/Cargo.toml -- target/editor/rivet-creative.png --plugin rivet --creative
|
||
cargo run --manifest-path tools/editor-check/Cargo.toml -- target/editor/patina-compact.png --plugin patina --compact
|
||
```
|
||
|
||
Patina also accepts `--silent`, `--delta` and `--bypass`. The tool compiles the
|
||
real editor code, feeds it synthetic analysis data and renders it in software
|
||
without a display. Copy a checked render to `docs/<product>/assets/editor.png`
|
||
to update the guide. These renders do not prove native host embedding, keyboard
|
||
routing or how the plugin sounds; use the FL Studio checks in each product guide.
|
||
|
||
## Add a product
|
||
|
||
1. Create `plugins/<product>/{core,ui,plugin}`. Copy the wrapper `Cargo.toml`
|
||
and `Cargo.lock` of a similar product so the NIH-plug revision matches.
|
||
2. Add `core` and `ui` to the root `members`, the wrapper to `exclude`, and a
|
||
`[profile.dev.package.<product>-core] opt-level = 2` entry if the tests run
|
||
heavy DSP.
|
||
3. Add the product to `plugins/catalog.json` with a new, unique CLAP ID.
|
||
4. Add it to `.gitea/workflows/check.yml` (check matrix, editor preview,
|
||
Windows build) and to the tag list in `release.yml`. The script tests fail
|
||
until you do.
|
||
5. Add a `--plugin <product>` fixture to `tools/editor-check`.
|
||
6. Write `docs/<product>/README.md`, `CHANGELOG.md` and `assets/editor.png`, and
|
||
add the product to the root README.
|
||
|
||
## Compatibility and naming
|
||
|
||
The suite is called **Bass Foundry**; this is the vendor name hosts show. Hosts
|
||
identify plugins by CLAP ID, so the vendor name does not affect saved projects.
|
||
|
||
| Product | Former display name | CLAP ID |
|
||
| --- | --- | --- |
|
||
| Rivet | Motif Storyboard | `io.narl.bass-foundry.rivet` |
|
||
| Patina | Resonance Compressor | `io.narl.bass-foundry.patina` |
|
||
| Anvil | (new) | `io.narl.bass-foundry.anvil` |
|
||
| Crucible | (new) | `io.narl.bass-foundry.crucible` |
|
||
| Lathe | (new) | `io.narl.bass-foundry.lathe` |
|
||
| Temper | (new) | `io.narl.bass-foundry.temper` |
|
||
| Weld | (new) | `io.narl.bass-foundry.weld` |
|
||
| Knurl | (new) | `io.narl.bass-foundry.knurl` |
|
||
| Planish | (new) | `io.narl.bass-foundry.planish` |
|
||
| Braze | (new) | `io.narl.bass-foundry.braze` |
|
||
| Quench | (new) | `io.narl.bass-foundry.quench` |
|
||
|
||
CLAP IDs use the form `io.narl.bass-foundry.<product>`. Builds tagged before
|
||
this scheme used `io.storyboard.*` IDs; hosts treat the new IDs as different
|
||
plugins, so projects saved with those builds need the plugin added again. From now on an ID is frozen: if a product is renamed, its CLAP ID
|
||
stays. A new plugin gets `io.narl.bass-foundry.<slug>` with its product slug.
|
||
|
||
Rivet keeps `story-session-v1` and its session migration. Patina keeps all
|
||
parameter IDs. The copyright notice keeps its original holder name. Delete old
|
||
plugin files before rescanning, so FL Studio does not list both builds.
|
||
|
||
See [releases](releases.md) for tagged publishing. Runs on the real Gitea runner
|
||
and checks in FL Studio are separate from the local checks.
|