Show an Envelope node as a device with its ADSR curve (#388)
game-maker / gate (push) Successful in 4m24s
game-maker / suite (push) Successful in 15m50s

The Compressor, the Gain and the Chip are devices in the graph. The
Envelope was still a list of rows, and its shape was left to the
imagination of whoever read the numbers.

- The node is a plate of 520x280 in pale sea green. Its inputs are
  on the left, out and value on the right.
- A dark screen draws the curve over a note in amber. A letter sits
  above each stage, its time below it, and the sustain in percent
  above its line.
- The time on the screen is squeezed. Each stage is as wide as the
  root of its seconds, at least 3 %, and the hold has a fixed fifth.
  So 5 ms of attack still shows beside a release of 3 s.
- The falls use SETTLE, as the player does. With sustain 0 there is
  no hold and no release, and the screen says NOTE ENDS.
- Attack, Decay, Sustain and Release are chrome knobs. The times use
  taper 4, so 5 ms sits an eighth of the way and 1 s not quite half.
- From and To have no range a knob could turn through. They are
  NumberFields on the plate, typed in or scrubbed.
- The decay at sustain 1 and From/To while nothing takes value are
  faded. While a link takes value, the bezel says what it moves
  between, as VALUE 400 TO 3500. The UI font has no arrow.
- Instrument presets give an Envelope 3 columns of 240.
- Test hook __gm.envelope.panel(nodeId).

Tests:
- Unit envelope-view: 6 new tests for the plate, the knob turn, the
  idle settings and the curve. A linear decay turns two of them red.
- e2e 34 (atx): 1 new check on the Laser preset (device between its
  ports, curve ends with the decay, decay knob and scrubbed To field
  each one undo step).
- Probe pictures on stein2: Synth Brass preset, pad, pluck, organ
  and an Envelope whose value moves a Chip.
- Gate (atx): CHECK EXIT 0, 286 files, 4383 tests. The first run was
  red on the font test (#214) for an arrow in the bezel text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-10 16:06:15 +02:00
1 parent ab37d3423a
commit c75e72d4ee
9 files changed
+887 -5

No files matched your search

+44
View File
@@ -2345,6 +2345,50 @@ export async function sound(t) {
}),
);
// #388: the Laser's Envelope is drawn as a device between its ports, its curve on a screen. Its
// sustain is 0, so the curve ends with the decay; nothing takes its value, so "from" and
// "to" are grey. A knob and a scrubbed field each change it as one undo step.
const envelopeId = (
await json(
`__gm.state.project.assets.find((a) => a.id === ${JSON.stringify(insId)}).graph.nodes.find((n) => n.type === 'InstrumentEnvelope')`,
)
).id;
const envelopePanel = () => json(`__gm.envelope.panel('${envelopeId}')`);
const envShown = await envelopePanel();
const envRect = envShown.rect;
const envBeside = (p) => p.y > envRect.y && p.y < envRect.y + envRect.h;
const decayDial = envShown.knobs.decay.dial;
await drag(decayDial.x, decayDial.y, decayDial.x, decayDial.y - 20);
const envTurned = { params: (await insGraph()).envelope, panel: await envelopePanel() };
const toField = envShown.fields.to.at;
await drag(toField.x, toField.y, toField.x + 30, toField.y);
const envScrubbed = { params: (await insGraph()).envelope, panel: await envelopePanel() };
for (let i = 0; i < 2; i++) await press('z', { ctrl: true });
const envUndone = (await insGraph()).envelope;
check(
'an Envelope node shows its device between its ports with its curve; a knob and a field each change it as one undo step',
envShown.shown &&
envShown.size.w === 520 &&
envShown.size.h === 280 &&
Object.values(envShown.inputs).length === 7 &&
Object.values(envShown.inputs).every((p) => p.x < envRect.x && envBeside(p)) &&
Object.values(envShown.outputs).every((p) => p.x > envRect.x + envRect.w && envBeside(p)) &&
envShown.trace.ends &&
envShown.trace.stages.map((s) => s.key).join() === 'attack,decay' &&
envShown.screen.ends === 'NOTE ENDS' &&
envShown.range === '' &&
envShown.idle.includes('from') &&
envShown.idle.includes('to') &&
envShown.knobs.decay.value === laser.envelope.decay &&
envTurned.params.decay > laser.envelope.decay &&
envTurned.panel.knobs.decay.value === envTurned.params.decay &&
envTurned.panel.trace.stages[1].seconds === envTurned.params.decay &&
envScrubbed.params.to > 1 &&
envScrubbed.panel.fields.to.text === String(envScrubbed.params.to) &&
JSON.stringify(envUndone) === JSON.stringify(laser.envelope),
JSON.stringify({ envShown, envTurned, envScrubbed, envUndone, laser: laser.envelope }),
);
const beforeRandom = await stats();
await clickHook('randomizeAt');
const randomized = await waitFor(stats, (s) => s.started > beforeRandom.started);