Show a Limiter node as a device that holds what it took away (#397)
game-maker / gate (push) Successful in 4m23s
game-maker / suite (push) Successful in 16m6s

A Limiter only said what it does in rows of numbers. How much it took
away, and when, could not be seen at all, and a short sound is over
before any needle could show it. Now the node is a device of its own
size, and its screen catches the reduction from the moment a sound
starts and holds it once the sound ends.

- LimiterPanel draws a 720x280 plate of brushed brick. The threshold
  knob is large, ceiling and release are small beside it, auto-gain is
  a lever. A LIMIT lamp lights while it takes something away.
- The screen hangs the reduction down from the top, 0 to 24 dB by the
  square root like the Compressor's meter. The level coming in runs
  along the bottom, orange above the limit. Time runs from the start
  of the sound over 1, 2, 4 or 8 s, and a longer sound rolls.
- ReductionCapture in limiterView holds the capture after 0.4 s of
  silence, without the silence, until the next sound. It writes the
  most taken away and when, and NOW or LASTED on the right.
- The Limiter is tapped now, like the Compressor. The face reads the
  loudest sample since the last frame on both sides.
- SpectrumTaps.levels takes a lateBy. The side in front is read 5 ms
  earlier, so a peak going in is met by what it became.
- limiterReduction leaves the auto-gain out and solves Wet's blend for
  the limited part. Less than 0.2 dB counts as none.
- A capture is also held when the node leaves the picture. Changing a
  knob or the lever leaves it as it is.
- Hook __gm.limiter.panel(id) for the sound FX and instrument graphs.

Decided myself:

- The capture stays held until the next sound, not for a fixed time.
- The screen has no wet knob, as the Compressor's has none.
- The Limiter's two taps cost two gains of 1 per voice. The analysers
  only exist while the node is in the picture.

Measured on stein2: the seeded blip at -4.4 dBFS against -12 dB showed
7.56 dB. Three hits at 0, -8 and -2 dBFS showed 11.9 dB at the first.
In e2e 34 on melektaus-atx, -10.44 dBFS in against -20 dB showed
9.56 dB, the difference to the hundredth.

Tests:
- Unit: limiter-view 13 tests (new), spectrum 6 (1 new), soundfx 67
  with the Limiter's taps.
- Counter-test: without Wet's share in the formula the blend test
  fails.
- e2e 34: ALL PASSED, 138 checks (1 new).
- Gate: CHECK EXIT 0, 290 files, 4445 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-10 20:30:59 +02:00
1 parent 80b25488ce
commit 28909f0adf
13 files changed
+1106 -21

No files matched your search

+5 -4
View File
@@ -447,7 +447,7 @@ export function withTaps(before: FxPlan, after: FxPlan, id: string): FxPlan {
}
const BYPASS = { key: 'bypass', label: 'Bypass', kind: 'boolean' as const, default: false };
const WET = { ...numberRow('wet', 'Wet', 0, 1, 0.01), default: 1 };
export const WET = { ...numberRow('wet', 'Wet', 0, 1, 0.01), default: 1 };
/** Whether a node is bypassed (#354): the graph is compiled as if it were not there. */
const isBypassed = (settings: PropertyBag): boolean => settings[BYPASS.key] === true;
@@ -1060,8 +1060,8 @@ export const SoundFxCompressor: SoundFxNodeDef = effect(
const LIMIT_THRESHOLD = { ...numberRow('threshold', 'Threshold (dB)', -60, 0, 0.5), default: -6 };
const CEILING = { ...numberRow('ceiling', 'Ceiling (dB)', -30, 0, 0.1), default: -1 };
const LIMIT_RELEASE = { ...numberRow('release', 'Release (s)', 0.001, 2, 0.001), default: 0.1 };
const LIMIT_AUTO_GAIN = { ...COMPRESS_AUTO_GAIN, default: true };
const LIMIT_ROWS = {
export const LIMIT_AUTO_GAIN = { ...COMPRESS_AUTO_GAIN, default: true };
export const LIMIT_ROWS = {
threshold: LIMIT_THRESHOLD,
ceiling: CEILING,
release: LIMIT_RELEASE,
@@ -1072,8 +1072,9 @@ export const SoundFxLimiter: SoundFxNodeDef = effect(
type: 'SoundFxLimiter',
label: 'Limiter',
category: 'sound',
tapped: true,
description:
'Keeps every peak of the sound below "threshold" (dB), however loud it comes in: it looks 5 ms ahead and has the volume down by the time the peak is heard, then lets it come back up in "release" seconds. "Auto-gain" on raises the limited sound to "ceiling" (dB), so its peaks sit right at the ceiling; off they sit at the threshold. Nothing ever goes above the ceiling, and a threshold above the ceiling limits at the ceiling. It is 5 ms late, which a voice, a Mix and a bus make up for. All of it can change while the sound plays.' +
'Keeps every peak of the sound below "threshold" (dB), however loud it comes in: it looks 5 ms ahead and has the volume down by the time the peak is heard, then lets it come back up in "release" seconds. "Auto-gain" on raises the limited sound to "ceiling" (dB), so its peaks sit right at the ceiling; off they sit at the threshold. Nothing ever goes above the ceiling, and a threshold above the ceiling limits at the ceiling. It is 5 ms late, which a voice, a Mix and a bus make up for. All of it can change while the sound plays. The node is drawn as a device: a knob for each number, and a screen of how much it takes away and when, from the moment a sound starts, held once the sound ends.' +
BLENDS,
inputs: [audioIn, ...Object.values(LIMIT_ROWS).map(numberIn)],
outputs: [audioOut],