Check and Windows preview / check (anvil) (push) Successful in 2m1s
Check and Windows preview / check (crucible) (push) Successful in 1m31s
Check and Windows preview / check (knurl) (push) Successful in 2m4s
Check and Windows preview / check (lathe) (push) Successful in 1m27s
Check and Windows preview / check (patina) (push) Successful in 1m54s
Check and Windows preview / check (rivet) (push) Successful in 1m30s
Check and Windows preview / check (temper) (push) Successful in 1m31s
Check and Windows preview / check (weld) (push) Successful in 2m31s
Check and Windows preview / previews (push) Successful in 1m26s
Independent plugin release / plan (push) Successful in 7s
Check and Windows preview / windows (push) Successful in 2m26s
Independent plugin release / build (push) Successful in 3m0s
Independent plugin release / publish (push) Successful in 8s
Knurl follows the played note and treats every cycle of the wave on its own: a repeating level pattern over 1-16 cycles makes subharmonics in tune with every note, random level and timing per cycle add rattle, and a noise burst per cycle, highpassed at a multiple of f0, adds grit. Amounts are counted in cycles, so the texture is the same on F1 and on C2. - Cycle clock from the shared note tracker, restarted at each note; the audio is delayed by the lookahead plus 4.2 ms of timing-jitter headroom. - Per-cycle randomness from a hash of the cycle number: Same every note or Free. - Patterns Off, Double, Triple, Fry (default), Gallop, Stutter or drawn; grit switchable per step. Pattern persisted as knurl-pattern. - Editor: last eight cycles overlaid, pattern bars with the playing step. - Tests match the prototype: Double gives f0/2 at -8.19 dB, Triple f0/3 at -10.68 dB, equal on F1 and C2, no added clicks, bit-exact when off, no allocation on the audio thread.
156 lines
7.2 KiB
Markdown
156 lines
7.2 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 and Knurl, 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`,
|
||
`knurl`.
|
||
|
||
`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` |
|
||
| Knurl | (new) | `io.storyboard.cycle-texture` |
|
||
|
||
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.
|