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
@@ -1495,6 +1495,65 @@ export async function sound(t) {
|
||||
await mutate(`
|
||||
const graph = __gm.state.project.assets.find((a) => a.id === ${JSON.stringify(fxId)}).graph;
|
||||
graph.nodes = graph.nodes.filter((n) => n.id !== 'fx-st');`);
|
||||
// #397: a Limiter is a device of its own size whose screen catches what it takes away from the
|
||||
// moment a sound starts and holds it once the sound ends. Wired in before the output, the
|
||||
// most it caught is the loudest that came in over its limit. The lever and the threshold
|
||||
// knob are one undo step each and leave the capture held.
|
||||
await mutate(`
|
||||
const graph = __gm.state.project.assets.find((a) => a.id === ${JSON.stringify(fxId)}).graph;
|
||||
graph.nodes.push(${JSON.stringify(node('fx-lm', 'SoundFxLimiter', 0, 400, { threshold: -20 }))});
|
||||
const last = graph.edges.find((e) => e.to.node === 'fx-out' && e.to.port === 'in');
|
||||
graph.edges.push({ id: 'fx-e-lm', from: { node: 'fx-lm', port: 'out' }, to: { node: 'fx-out', port: 'in' } });
|
||||
last.to = { node: 'fx-lm', port: 'in' };`);
|
||||
await evaluate('__gm.soundfx.fit()');
|
||||
await sleep(100);
|
||||
const lmPanel = () => json("__gm.limiter.panel('fx-lm')");
|
||||
const lmShown = await lmPanel();
|
||||
await evaluate('__gm.sound.stopAll()');
|
||||
await evaluate('__gm.soundfx.play()');
|
||||
const lmHeld = await waitFor(lmPanel, (p) => p.scope.state === 'held', 40);
|
||||
await click(lmShown.autoGain.at.x, lmShown.autoGain.at.y);
|
||||
const lmOff = { params: await knobParams('fx-lm'), panel: await lmPanel() };
|
||||
const lmDial = lmShown.knobs.threshold.dial;
|
||||
await drag(lmDial.x, lmDial.y, lmDial.x, lmDial.y - 20);
|
||||
const lmTurned = (await knobParams('fx-lm')).threshold;
|
||||
for (let i = 0; i < 2; i++) await press('z', { ctrl: true });
|
||||
const lmUndone = { params: await knobParams('fx-lm'), panel: await lmPanel() };
|
||||
check(
|
||||
'a Limiter node holds what it took away once the sound ends, the most of it the loudest over its limit; its lever and threshold are one undo step each',
|
||||
lmShown.size.w === 720 &&
|
||||
lmShown.size.h === 280 &&
|
||||
Object.values(lmShown.inputs).length === 5 &&
|
||||
Object.values(lmShown.inputs).every((p) => p.x < lmShown.rect.x) &&
|
||||
Object.values(lmShown.outputs).every((p) => p.x > lmShown.rect.x + lmShown.rect.w) &&
|
||||
lmShown.knobs.threshold.number === '-20 dB' &&
|
||||
lmShown.autoGain.lit &&
|
||||
lmShown.autoGain.says === '+19 dB' &&
|
||||
lmShown.status === 'WAITING FOR SOUND' &&
|
||||
lmHeld.status === 'HELD' &&
|
||||
lmHeld.scope.limited > 0 &&
|
||||
lmHeld.scope.most !== null &&
|
||||
lmHeld.scope.loudest > -20 &&
|
||||
Math.abs(lmHeld.scope.most.db - (lmHeld.scope.loudest + 20)) < 1 &&
|
||||
/^MOST -\d+(\.\d)? dB AT \d+ ms$/.test(lmHeld.scope.says.most) &&
|
||||
lmHeld.scope.says.side.startsWith('LASTED ') &&
|
||||
!lmHeld.lamp &&
|
||||
lmOff.params.autoGain === false &&
|
||||
lmOff.panel.autoGain.says === 'off' &&
|
||||
lmOff.panel.scope.state === 'held' &&
|
||||
lmTurned > -20 &&
|
||||
lmUndone.params.threshold === -20 &&
|
||||
lmUndone.params.autoGain !== false &&
|
||||
lmUndone.panel.scope.state === 'held' &&
|
||||
lmUndone.panel.scope.most?.db === lmHeld.scope.most.db,
|
||||
JSON.stringify({ lmShown, lmHeld, lmOff, lmTurned, lmUndone }),
|
||||
);
|
||||
await mutate(`
|
||||
const graph = __gm.state.project.assets.find((a) => a.id === ${JSON.stringify(fxId)}).graph;
|
||||
const into = graph.edges.find((e) => e.to.node === 'fx-lm' && e.to.port === 'in');
|
||||
into.to = { node: 'fx-out', port: 'in' };
|
||||
graph.edges = graph.edges.filter((e) => e.id !== 'fx-e-lm');
|
||||
graph.nodes = graph.nodes.filter((n) => n.id !== 'fx-lm');`);
|
||||
await showTab('scene');
|
||||
await mutate(`
|
||||
const graph = __gm.state.project.assets.find((a) => a.id === 'sn-logic').graph;
|
||||
|
||||
Reference in new issue
Block a user