Show a Limiter node as a device that holds what it took away (#397)
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:
1 parent
80b25488ce
commit
28909f0adf
13 files changed
+1106
-21
No files matched your search
@@ -8,6 +8,7 @@ import { EnvelopePanel } from '../soundfx/EnvelopePanel';
|
||||
import { CompressorPanel } from '../soundfx/CompressorPanel';
|
||||
import { KnobPanel } from '../soundfx/KnobPanel';
|
||||
import { DelayPanel } from '../soundfx/DelayPanel';
|
||||
import { LimiterPanel } from '../soundfx/LimiterPanel';
|
||||
import { SaturationPanel } from '../soundfx/SaturationPanel';
|
||||
import { ReverbPanel } from '../soundfx/ReverbPanel';
|
||||
import { CurvePanel } from '../soundfx/CurvePanel';
|
||||
@@ -522,6 +523,59 @@ export function graphHooks({ state, editor }: HookContext) {
|
||||
};
|
||||
},
|
||||
},
|
||||
/**
|
||||
* The panel a Limiter node shows (#397), in the sound FX graph or the instrument graph: whether it
|
||||
* is shown and where, its knobs and lever, which settings are idle, the lamp, what the screen has
|
||||
* caught and says, and the node's ports.
|
||||
*/
|
||||
limiter: {
|
||||
panel: (nodeId: string) => {
|
||||
const canvas = [editor.soundFxEditor.canvas, editor.instrumentEditor.canvas].find(
|
||||
(c) => c.faceOf(nodeId)?.widget instanceof LimiterPanel,
|
||||
);
|
||||
const panel = canvas?.faceOf(nodeId)?.widget;
|
||||
if (!canvas || !(panel instanceof LimiterPanel)) return null;
|
||||
const node = canvas.nodeScreen(nodeId);
|
||||
const ports = (keys: string[], isOutput: boolean) =>
|
||||
Object.fromEntries(keys.map((key) => [key, canvas.portGlobal(nodeId, key, isOutput)]));
|
||||
const capture = panel.scope.capture;
|
||||
return {
|
||||
shown: shownOnScreen(panel),
|
||||
rect: screenRect(panel),
|
||||
size: { w: panel.w, h: panel.h },
|
||||
node: node?.rect ?? null,
|
||||
inputs: ports(node?.inputs ?? [], false),
|
||||
outputs: ports(node?.outputs ?? [], true),
|
||||
knobs: Object.fromEntries(
|
||||
Object.entries(panel.knobs).map(([key, knob]) => [
|
||||
key,
|
||||
{ value: knob.value, number: knob.shown, enabled: knob.enabled, dial: knob.dialGlobal() },
|
||||
]),
|
||||
),
|
||||
idle: [...panel.idle],
|
||||
autoGain: {
|
||||
lit: panel.autoGain.lit,
|
||||
says: panel.autoGain.says.text,
|
||||
at: screenCentre(panel.autoGain),
|
||||
},
|
||||
lamp: panel.lamp,
|
||||
status: panel.status.text,
|
||||
scope: {
|
||||
state: capture.state,
|
||||
says: panel.scope.texts,
|
||||
points: capture.points.length,
|
||||
span: capture.span,
|
||||
/** Seconds after the sound started of the last point. */
|
||||
to: capture.to,
|
||||
/** The most taken away and when, null while nothing was. */
|
||||
most: capture.most && { db: capture.most.db, t: capture.most.t },
|
||||
/** How many points took something away, and the loudest that came in, in dB. */
|
||||
limited: capture.points.filter((p) => p.db > 0).length,
|
||||
loudest: capture.points.reduce((most, p) => Math.max(most, p.in), -Infinity),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
instrument: {
|
||||
current: () => editor.instrumentEditor.currentAssetId,
|
||||
info: () => editor.instrumentEditor.shownInfo,
|
||||
|
||||
Reference in new issue
Block a user