Show a Delay node as a device with its echoes over time (#393)
After the Chip, the Envelope, the Reverb and the one-knob devices, the Delay is drawn as a device of its own size too. Its screen shows what time, feedback, damping, wet and ping-pong make of a sound, and a light runs over the echoes at the beat they come at. - DelayPanel: a 680x280 plate in the Compressor's brushed aluminium, now exported as ALUMINIUM. A screen, levers for Sync and Ping-pong, knobs for Time, Feedback, Damping and Wet, and an Echo lamp. - The screen draws the sound as a white bar and each echo as a bar one step later. A bar is as tall as the echo is loud, evenly in dB to -60. It turns from warm white to rust as the damping takes the highs. With ping-pong the bars alternate above and below, the first left. - At most 24 bars are drawn, and the screen says how many more are heard. Under it stand the step and the end of the tail, above it where the highs of the first echo end. - The running light: DelayFace.tick returns true while the node is in the picture, so the editor keeps drawing, as for the Particles tab. The light goes round the span in real time, a bar glows as it is passed, and the lamp blinks with every echo but not with the sound. - On beats the screen counts at 120 BPM, as the tempo of the voice is not known in the graph. The time knob then names the note, dotted and triplet ones included. In seconds, 2 s and up reads "2 s max". - delayView holds the rules apart from the drawing: layout, tapers, echoTrain, echoCutoff for k rounds of the Butterworth low-pass, sweepAt and the texts. At wet 0 the rest is drawn faint. - delayEchoes in the core counts the echoes heard. The tail of a voice and the screen use it both. DELAY_ROWS, DELAY_SYNC and PING_PONG are exported. - Knob.reformat writes the number anew when its format changes, here when Sync switches between seconds and beats. - The test hook __gm.delay.panel returns knobs, levers, idle settings, the echoes drawn, the texts and where the light is. - Instrument presets give the Delay 4 columns. Echo Lead and Bell use one. In the picture of Echo Lead nothing overlaps. Found on the way: a node with a face draws no setting rows. The e2e check of a bypassed Delay (#354) read "Bypass: true" from them. It now checks the faded node and the stages heard, as before. Tests: - Unit delay-view: 17 tests, new. Counter-check: the first ping-pong echo on the right turns the side test red. - e2e 34 (melektaus-atx): 136 checks, 1 new. - Gate (melektaus-atx): 288 files, 4416 tests. - Pictures looked at: default, ping-pong, beats with damping, 83 echoes at 50 ms, wet 0, and the preset Echo Lead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
389d9555fa
commit
d4a45c127b
12 files changed
+1082
-17
No files matched your search
+62
-6
@@ -500,7 +500,8 @@ export async function sound(t) {
|
||||
cut.stopped === beforeLetRing.stopped + 2,
|
||||
JSON.stringify({ before: beforeLetRing.stopped, letRing, cut }),
|
||||
);
|
||||
// #354: the Delay bypassed is drawn faded and says so, and the voice plays as if it were not there.
|
||||
// #354: the Delay bypassed is drawn faded, and the voice plays as if it were not there. Since
|
||||
// #393 the Delay is a device, which writes no setting rows, so "Bypass: true" is not read.
|
||||
await mutate(`
|
||||
const graph = __gm.state.project.assets.find((a) => a.id === ${JSON.stringify(fxId)}).graph;
|
||||
graph.nodes.find((n) => n.id === 'fx-delay').params.bypass = true;`);
|
||||
@@ -512,11 +513,8 @@ export async function sound(t) {
|
||||
const heardBypass = await waitFor(stats, (s) => s.started > beforeBypass.started);
|
||||
await evaluate('__gm.sound.stopAll()');
|
||||
check(
|
||||
'a bypassed Delay is drawn faded with "Bypass: true", and the voice passes only the gain',
|
||||
bypassed.alpha < 0.5 &&
|
||||
bypassed.gain === 1 &&
|
||||
bypassed.settings?.includes('Bypass: true') &&
|
||||
heardBypass.last?.stages === 1,
|
||||
'a bypassed Delay is drawn faded, and the voice passes only the gain',
|
||||
bypassed.alpha < 0.5 && bypassed.gain === 1 && heardBypass.last?.stages === 1,
|
||||
JSON.stringify({ bypassed, last: heardBypass.last }),
|
||||
);
|
||||
await mutate(`
|
||||
@@ -1387,6 +1385,64 @@ 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-pn');`);
|
||||
// #393: a Delay is a device of its own size with its echoes on a screen and a light running over
|
||||
// them at their beat. Ping-pong puts them left and right by turns, Sync names the time as a
|
||||
// note, the time knob turns it; each is one undo step.
|
||||
await mutate(`
|
||||
const graph = __gm.state.project.assets.find((a) => a.id === ${JSON.stringify(fxId)}).graph;
|
||||
graph.nodes.push(${JSON.stringify(node('fx-dl', 'SoundFxDelay', 0, 400, { time: 0.5, feedback: 0.5, mix: 0.5 }))});`);
|
||||
await evaluate('__gm.soundfx.fit()');
|
||||
await sleep(100);
|
||||
const dlShown = await json("__gm.delay.panel('fx-dl')");
|
||||
await sleep(150);
|
||||
const dlLater = (await json("__gm.delay.panel('fx-dl')")).scope.at;
|
||||
await click(dlShown.pingPong.at.x, dlShown.pingPong.at.y);
|
||||
const dlPingPong = { params: await knobParams('fx-dl'), panel: await json("__gm.delay.panel('fx-dl')") };
|
||||
await click(dlShown.sync.at.x, dlShown.sync.at.y);
|
||||
const dlBeats = { params: await knobParams('fx-dl'), panel: await json("__gm.delay.panel('fx-dl')") };
|
||||
const dlTime = dlShown.knobs.time.dial;
|
||||
await drag(dlTime.x, dlTime.y, dlTime.x, dlTime.y - 20);
|
||||
const dlTurned = (await knobParams('fx-dl')).time;
|
||||
for (let i = 0; i < 3; i++) await press('z', { ctrl: true });
|
||||
const dlUndone = { params: await knobParams('fx-dl'), panel: await json("__gm.delay.panel('fx-dl')") };
|
||||
const dlSides = (panel) =>
|
||||
panel.scope.echoes
|
||||
.slice(0, 3)
|
||||
.map((e) => e.side)
|
||||
.join(' ');
|
||||
check(
|
||||
'a Delay node is a device with its echoes on a screen and a running light; ping-pong, sync and the time knob are one undo step each',
|
||||
dlShown.size.w === 680 &&
|
||||
dlShown.size.h === 280 &&
|
||||
Object.values(dlShown.inputs).length === 5 &&
|
||||
Object.values(dlShown.inputs).every((p) => p.x < dlShown.rect.x) &&
|
||||
Object.values(dlShown.outputs).every((p) => p.x > dlShown.rect.x + dlShown.rect.w) &&
|
||||
dlShown.knobs.time.number === '500 ms' &&
|
||||
dlShown.scope.heard === 10 &&
|
||||
dlShown.scope.echoes.length === 10 &&
|
||||
dlShown.scope.says.every === 'EVERY 500 ms' &&
|
||||
dlShown.scope.says.ends === 'ENDS 5 s' &&
|
||||
dlSides(dlShown) === 'both both both' &&
|
||||
// The light runs on by itself.
|
||||
dlLater !== dlShown.scope.at &&
|
||||
dlPingPong.params.pingPong === true &&
|
||||
dlPingPong.panel.pingPong.lit &&
|
||||
dlSides(dlPingPong.panel) === 'left right left' &&
|
||||
dlBeats.params.sync === 'beats' &&
|
||||
dlBeats.panel.sync.says === 'beats' &&
|
||||
dlBeats.panel.knobs.time.number === '1/8' &&
|
||||
dlBeats.panel.scope.says.every === 'EVERY 1/8 · 250 ms AT 120 BPM' &&
|
||||
dlTurned > 0.5 &&
|
||||
dlUndone.params.time === 0.5 &&
|
||||
dlUndone.params.sync !== 'beats' &&
|
||||
dlUndone.params.pingPong !== true &&
|
||||
dlUndone.panel.knobs.time.number === '500 ms' &&
|
||||
dlSides(dlUndone.panel) === 'both both both',
|
||||
JSON.stringify({ dlShown, dlLater, dlPingPong, dlBeats, dlTurned, dlUndone }),
|
||||
);
|
||||
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');`);
|
||||
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