Files
bass-foundry/docs/development.md
T
nvrl 9b3ddeaf56
Check and Windows preview / check (anvil) (push) Successful in 2m0s
Check and Windows preview / check (crucible) (push) Successful in 1m29s
Check and Windows preview / check (lathe) (push) Successful in 1m27s
Check and Windows preview / check (patina) (push) Successful in 1m53s
Check and Windows preview / check (rivet) (push) Successful in 1m29s
Check and Windows preview / check (temper) (push) Successful in 1m29s
Check and Windows preview / check (weld) (push) Successful in 2m38s
Check and Windows preview / previews (push) Successful in 1m28s
Independent plugin release / plan (push) Successful in 8s
Check and Windows preview / windows (push) Successful in 3m14s
Independent plugin release / build (push) Successful in 3m50s
Independent plugin release / publish (push) Successful in 9s
feat(weld): phase alignment for layered bass, release 0.1.0
Weld hears the other bass layer through the sidechain and fits polarity
plus up to four RBJ allpass sections to the measured cross-spectrum, so
the layers sum instead of cancelling. No latency; the layer on its own
keeps its sound.

- Worker thread: Welch cross-spectrum and coherence, Nelder-Mead fit with
  1/f weighting, a per-band loss penalty and model selection by share of
  the achievable gain; GCC-PHAT lag detection flags a late layer.
- Audio thread: allocation-free chain with a 40 ms warm-up and 40 ms
  crossfade per new fit; mono blocks go to the worker through a lock-free
  queue.
- Follow / Learn / Hold; the fit is persisted and restored without a
  crossfade.
- Editor: per-band loss and recovery against a perfect alignment, phase
  before and after, recovered dB, time offset and status.
- Tests for flat magnitude, crossfade, fit targets, Follow hysteresis,
  Learn, silence, realtime allocation and the persisted fit in the host.
2026-11-06 08:31:41 +01:00

154 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 and Temper, 4× oversampling, 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`.
`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.storyboard.motif` |
| Patina | Resonance Compressor | `io.storyboard.resonance-compressor` |
| Anvil | (new) | `io.storyboard.sub-anchor` |
| Crucible | (new) | `io.storyboard.resampler` |
| Lathe | (new) | `io.storyboard.harmonic-eq` |
| Temper | (new) | `io.storyboard.tracked-distortion` |
| Weld | (new) | `io.storyboard.layer-align` |
The `io.storyboard.` prefix is historical and stays, because changing a CLAP ID
breaks existing projects. Rivet keeps `story-session-v1` and its session
migration. Patina keeps all parameter IDs. The copyright notice keeps its
original holder name. Replace old plugin files before rescanning, so no CLAP ID
is installed twice, and check that existing projects still load in FL Studio.
See [releases](releases.md) for tagged publishing. Runs on the real Gitea runner
and checks in FL Studio are separate from the local checks.