Show a Saturation node as a device that glows with its drive (#396)
game-maker / gate (push) Successful in 4m5s
game-maker / suite (push) Successful in 16m2s

A Saturation in the graph was a stack of rows. The user wanted a device
of its own style and size, like the Delay and the Reverb, that shows
warmth and heat the harder the sound is driven.

- The node is a copper plate of 720x280 with a button per type, the
  auto-gain lever, a big drive knob and small knobs for tone, output
  and wet. Each change is one undo step, wired numbers lock their knob.
- The screen draws a sine at -12 dBFS, the level auto-gain is reckoned
  at, as a grey ghost. Over it is the wave that comes out: bent by the
  type, its DC taken out, auto-gain and output after it, blended by
  wet. Under it stand the auto-gain and the peak.
- The heat is the drive over 48 dB, as far as wet lets it be heard.
  The wave goes from cold steel through ember red to white, a glow
  rises from below and flickers, and embers fly up from about 8 dB on.
  A lamp beside the name glows with it.
- The node keeps the editor drawing only while there is heat. Without
  drive it draws nothing, unlike the Delay, which always animates.
- The Reverb's private type button is now PickButton with its own
  colours, and the Saturation's type buttons use it as well.
- No instrument preset uses a Saturation, so no preset needs more
  columns.

Tests:
- Unit saturation-view: 14 new tests.
- Counter-check: without taking out the DC, exactly the DC test fails.
- e2e 34 (atx): ALL PASSED, 137 checks, one new for type, lever and
  drive with undo.
- Gate (atx): CHECK EXIT 0, 289 files, 4431 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:05:11 +02:00
1 parent 8c53ce0cbe
commit 80b25488ce
11 files changed
+1092 -21

No files matched your search

+52
View File
@@ -1443,6 +1443,58 @@ 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-dl');`);
// #396: a Saturation is a device of its own size with the wave a sine comes out as on a screen,
// glowing hotter the more it is driven. A type button, the auto-gain lever and the drive
// knob are one undo step each.
await mutate(`
const graph = __gm.state.project.assets.find((a) => a.id === ${JSON.stringify(fxId)}).graph;
graph.nodes.push(${JSON.stringify(node('fx-st', 'SoundFxSaturation', 0, 400, { drive: 12 }))});`);
await evaluate('__gm.soundfx.fit()');
await sleep(100);
const stShown = await json("__gm.saturation.panel('fx-st')");
await sleep(150);
const stLater = (await json("__gm.saturation.panel('fx-st')")).scope.flare;
await click(stShown.types.hard.x, stShown.types.hard.y);
const stHard = { params: await knobParams('fx-st'), panel: await json("__gm.saturation.panel('fx-st')") };
await click(stShown.autoGain.at.x, stShown.autoGain.at.y);
const stOff = { params: await knobParams('fx-st'), panel: await json("__gm.saturation.panel('fx-st')") };
const stDrive = stShown.knobs.drive.dial;
await drag(stDrive.x, stDrive.y, stDrive.x, stDrive.y - 40);
const stHot = { params: await knobParams('fx-st'), panel: await json("__gm.saturation.panel('fx-st')") };
for (let i = 0; i < 3; i++) await press('z', { ctrl: true });
const stUndone = { params: await knobParams('fx-st'), panel: await json("__gm.saturation.panel('fx-st')") };
check(
'a Saturation node is a device whose screen glows hotter with the drive; a type, auto-gain and the drive knob are one undo step each',
stShown.size.w === 720 &&
stShown.size.h === 280 &&
Object.values(stShown.inputs).length === 5 &&
Object.values(stShown.inputs).every((p) => p.x < stShown.rect.x) &&
Object.values(stShown.outputs).every((p) => p.x > stShown.rect.x + stShown.rect.w) &&
stShown.type === 'soft' &&
stShown.knobs.drive.number === '12 dB' &&
stShown.heat.word === 'WARM' &&
stShown.heat.burning &&
stShown.autoGain.lit &&
stShown.scope.says.auto.startsWith('AUTO-GAIN ') &&
// The glow flickers by itself.
stLater !== stShown.scope.flare &&
stHard.params.curve === 'hard' &&
stHard.panel.type === 'hard' &&
stOff.params.autoGain === false &&
stOff.panel.scope.says.auto === 'AUTO-GAIN OFF' &&
stHot.params.drive > 12 &&
stHot.panel.heat.share > stShown.heat.share &&
stHot.panel.scope.embers > 0 &&
stUndone.params.drive === 12 &&
stUndone.params.curve !== 'hard' &&
stUndone.params.autoGain !== false &&
stUndone.panel.type === 'soft' &&
stUndone.panel.heat.word === 'WARM',
JSON.stringify({ stShown, stLater, stHard, stOff, stHot, stUndone }),
);
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');`);
await showTab('scene');
await mutate(`
const graph = __gm.state.project.assets.find((a) => a.id === 'sn-logic').graph;