feat(ballast): add Ballast switch-up leveller

Ballast gives every part of a bassline one gain that brings it to the target
loudness, with no compression inside a part. Parts start at note starts,
detected from the audio or taken from MIDI, or on the host's beat grid, so a
patch change on the same note counts too.

- Loudness is K-weighted over each part's first 100 ms, in 10 ms blocks with
  a relative gate, and every part is weighted from rest so the last part does
  not ring into it.
- The audio path is delayed by the tracker's lookahead, the window and a
  5 ms glide, so each gain is in place on its part's first sample.
- Learn takes the next part's loudness as the target. Amount, Range, Measure
  from (leave the sub out), Gate, Output, Bypass.
- Editor shows the last 32 parts in and out, the target, and the spread of
  the last 16 parts.
- Registered in the catalog, workflows, editor-check, README, chain and
  development docs, and the website copy.
This commit is contained in:
nvrl committed 2026-11-06 16:05:19 +01:00
1 parent d77b88c7a9
commit d93dd942a8
28 files changed
+4716 -21

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
# Ballast changelog
## Unreleased (0.1.0)
- Introduce Ballast, a switch-up leveller: every part of a bassline gets one
gain that brings it to the Target loudness. No compression inside a part.
- Parts start at note starts (detected from the audio, or from MIDI) or on the
host's beat grid, from 1 bar to 1/16.
- Loudness is K-weighted (ITU-R BS.1770) over each part's first 100 ms, in
10 ms blocks with a relative gate, and each part is weighted from rest.
- The gain glides over 5 ms and is in place on the part's first sample.
- **Learn** takes the next part's loudness as the target. **Amount**,
**Range**, **Measure from** (leave the sub out of the measurement), **Gate**,
**Output**, **Bypass**.
- Editor: the last 32 parts, their loudness in and out, the target, and the
spread of the last 16 parts.
- About 172 ms of latency: the tracker's lookahead, the window and the glide.
+80
View File
@@ -0,0 +1,80 @@
# Ballast
Ballast levels every note or switch-up of a bassline to one loudness, with a
single gain for each. Current version **0.1.0** (unreleased).
Riddim and dubstep basslines change patch every few beats. Patches at the same
peak level still differ in how loud they sound, so each switch-up lands at a
different loudness and gets hand-set gain or clipping. A compressor would even
them out but also change their envelope and punch. Ballast measures each part's
loudness and gives the whole part one gain: inside a part nothing moves.
![Ballast](assets/editor.png)
## Install on Windows
Copy `Ballast.clap` into `C:\Program Files\Common Files\CLAP\`. In FL Studio
2024.1 or newer, open Options → Manage plugins, enable Verify plugins, and Find
installed plugins. Load Ballast in the mixer slot of the bass, after its sound
design. Keep the font license with redistributed packages.
## Work with it
1. Choose where a part starts with **Parts from**:
- **Notes**: every note start, detected from the audio, or from MIDI notes
sent to the effect (**Notes from**: AUDIO or MIDI).
- **Grid**: every line of the host's beat grid, from 1 bar to 1/16. Use it
when a switch-up changes the patch on the same note: the note tracker does
not see a new note there.
2. Play the bassline and press **LEARN** just before the switch-up that sits
right in the mix. Its loudness becomes the **Target**. You can also set
Target by hand.
3. Watch the display: one bar per part with its loudness as it came in, and a
mark where Ballast puts it. The marks line up on the target line.
4. Lower **Amount** to keep some of the differences: 50 % halves them.
The panel shows the spread of the last 16 parts, in and out: the standard
deviation of their loudness in dB. In the editor preview four patches spread
by 7 dB come out within 0.4 dB; the rest is what Range left.
Other controls:
- **Range** is the most boost or cut a part gets.
- **Measure from**: the measurement hears only what is above this frequency;
the gain still applies to the whole signal. When Anvil makes the sub on the
bus, set it to Anvil's Split: Anvil replaces the sub anyway, so only the part
above it should decide the gain.
- **Gate** is the note-start threshold (Audio). A part quieter than the gate
keeps the last part's gain instead of being lifted, so rests and tails are
not pulled up.
- **Output** trims the level; **Bypass** crossfades to the delayed input.
### How it measures
A part's loudness is its K-weighted level (ITU-R BS.1770, the weighting behind
LUFS) over its first 100 ms, or over all of it when it is shorter. The level is
counted in 10 ms blocks, and blocks 20 dB under the part's loudest block are
left out. Each part is weighted from rest, as if it played alone, so the last
part's level does not ring into it.
The gain glides to the new value over 5 ms and is in place on the part's first
sample, so the attack already has the new level. Later parts of a long note keep
that gain: Ballast does not follow the note's envelope.
## Timing and limits
Latency is the note tracker's lookahead (about 67 ms), the 100 ms measuring
window and the 5 ms glide, about 172 ms in all, reported to the host for delay
compensation. That is too much for playing live through Ballast: add it for the
mix.
In Grid mode the grid follows the host's beat position while it plays and runs
on at the last tempo when it stops. A note start found by a change of pitch can
come about 20 ms early, inside the gap before the note;
the block gate keeps that gap out of the measurement. Two parts closer than
10 ms are merged.
Ballast decides from the first 100 ms of a part. A patch that swells after
that, or a note whose loudness depends on what plays with it, is levelled by
its start only. It levels loudness, not brightness: two patches at the same
loudness can still differ in tone.
Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

+10 -5
View File
@@ -1,13 +1,13 @@
# Signal chain
The order of the Bass Foundry plugins in a chain matters. Anvil, Lathe, Temper,
Knurl, Planish and Braze each detect the pitch from the audio they receive. If a plugin earlier
Knurl, Planish, Braze and Ballast each detect the pitch from the audio they receive. If a plugin earlier
in the chain changes the audio, the pitch detection after it can go wrong.
## Recommended order
```text
bass track: synth → Lathe → Temper → Planish → Braze → Knurl → Patina → Weld
bass track: synth → Lathe → Temper → Planish → Braze → Knurl → Patina → Ballast → Weld
bass bus: Anvil → (bus compression) → Quench → Crucible
master: … → Quench (last, or before a true-peak limiter)
```
@@ -23,7 +23,12 @@ master: … → Quench (last, or before a true-peak limiter)
it reads Loose, move Braze to the bus after Anvil, with Low cut at or above
Anvil's Split. There it reads the pitch from Anvil's clean sub, and the sub
stays out of the beating.
- **Knurl last among the tracked plugins.** See below.
- **Ballast after the sound design.** It levels what the patch finally sounds
like, so it comes after the distortion, OTT and Patina. It finds note starts
like the other tracked plugins: after Knurl, use MIDI or Grid. Weld and Anvil
come after it. On the bus with Anvil, set Measure from to Anvil's Split.
- **Knurl last among the pitch-tracked plugins.** See below. Ballast only
needs note starts; after Knurl, run it from MIDI or the grid.
- **Patina after the tracked plugins.** It does not detect pitch, so the order
does not change its result.
- **Weld after every plugin with latency on its track.** Weld can delay its own
@@ -58,8 +63,8 @@ and route the bass's MIDI notes to it.
## Latency
Each tracked plugin adds its own lookahead: Anvil 80 ms, Lathe, Temper,
Planish and Braze about 67 ms, Knurl about 71 ms, Patina about 45 ms, Quench
under 1 ms. A full chain is
Planish and Braze about 67 ms, Knurl about 71 ms, Patina about 45 ms, Ballast
about 172 ms, Quench under 1 ms. A full chain is
about 400 ms. FL
Studio compensates this for playback and rendering, but playing a MIDI keyboard
through the full chain is not practical. Write with a shorter chain and add the
+4 -2
View File
@@ -25,7 +25,8 @@ 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.
Lathe, Temper, Knurl, Planish, Braze and Ballast, 4× oversampling (also Quench's),
BS.1770 K-weighting (Ballast), 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
@@ -45,7 +46,7 @@ python3 -m unittest discover -s scripts/tests -v
```
Products: `rivet`, `patina`, `anvil`, `crucible`, `lathe`, `temper`, `weld`,
`knurl`, `planish`, `braze`, `quench`.
`knurl`, `planish`, `braze`, `quench`, `ballast`.
`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
@@ -147,6 +148,7 @@ identify plugins by CLAP ID, so the vendor name does not affect saved projects.
| Planish | (new) | `io.narl.bass-foundry.planish` |
| Braze | (new) | `io.narl.bass-foundry.braze` |
| Quench | (new) | `io.narl.bass-foundry.quench` |
| Ballast | (new) | `io.narl.bass-foundry.ballast` |
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
+17 -2
View File
@@ -263,8 +263,8 @@ one period of the note, so heavy settings do not ripple at f0. See [Planish](pla
Patina already cut fixed formants on every note, but it also cut a boosted
harmonic as hard (5.7 dB), at a position that moved with the note.
Open: MIDI as the pitch source.
3. Planish (done, released 0.1.0), then Quench (done, unreleased) and Ballast:
they fill the mix and master gap.
3. Planish (done, released 0.1.0), then Quench and Ballast (both done,
unreleased): they fill the mix and master gap.
- In parallel, from the research findings, in this order: Weld delay
spread (done, Weld 0.3.0); the fatness lane, as the first Assay piece
(prototype `scripts/fatness.py`); Anvil Ghost harmonics and Phone listen
@@ -376,6 +376,21 @@ the same loudness without compression changing its character. Earlier working
name: Hit Leveler. Pairs with Anvil: Anvil levels the sub, Ballast the whole
patch.
Built (unreleased 0.1.0, see [Ballast](ballast/README.md)): one gain per part,
parts from note starts or the host grid (a patch change on the same note is not
a new note to the tracker), K-weighted loudness of the first 100 ms, Learn for
the reference. Found while building it:
- A note start from a pitch change comes about 20 ms early, in the gap before
the note; a plain mean over the window then reads the part ~0.9 dB quiet.
Fixed with 10 ms blocks and a relative gate (−20 dB under the loudest block).
- The K-weighting filters ring the last part into the next one: a near-silent
part after a loud one read −18 LUFS instead of under −60. Fixed by weighting
every part from rest.
- Next ideas: a brightness match next to loudness (problem 2 names both; with
Lathe or Patina doing the EQ), a "follow" option for long notes that swell,
and switch-up detection from timbre change instead of the grid.
### Bellows — pitch-tracked resonator
A comb or Karplus-style resonator tuned to the detected note. It turns noise,