Let a game hand values to the parameters of a sound FX #311
A sound FX played the same every time: its numbers were settings of the nodes. Now the Input declares parameters with a default, every number of Gain, Pitch and EQ is an input they can feed, and a Play sound hands in values for them. - Input declares number parameters in the inspector, each with a Default (DeclaredPort.default, SetPortDefaultCommand). They are outputs of the Input. Input and Output count as terminals now, one of each per graph. - Gain, Pitch and EQ take their numbers at inputs under the keys the settings had, so saved graphs sound as before without a migration. Each value is held to its range however it arrives. - The compiler gives an FxPlan with the parameters still open. fxChainOf fills them in when a voice starts. The player still only sees an FxChain. - New behavior node Sound FX parameters: names an FX and picks its parameters like a Variables node picks variables. Each picked one is a number input that starts at the FX's default. Its output has the new port type fxparams, which only fits Play sound's fx params. - Play sound warns when the parameters name another FX than the one it plays. Names the FX does not know are left out. - Renaming a parameter at the Input renames it in every Sound FX parameters node naming that FX, edges and values included. - The Sound FX tab has a number field per parameter in its header. Play uses those values, they are never written into the asset. - A value counts from the start of a voice. Changing a playing voice is #315, a Mix node is #316. Tests: - soundfx.test.ts 22 (+11): parameters in the plan, held values, a 0-by-default stage, EQ band, save with defaults, wrong port types, default command with undo, Sound FX parameters into Play sound, defaults of picked ones, node problems, mismatch, rename with undo - node-catalog 368 (+3): Play sound fx params, Sound FX parameters - e2e 34: ALL PASSED - 28 checks (+2: header field plays depth 5, a Sound FX parameters node gives the game's voice depth 7) - Gate on melektaus-atx: typecheck, lint, prettier clean, 240 test files, 3636 tests green - Counter-check: with fxChainOf ignoring the values and the rename not followed, 5 of the new tests fail. - Browser probe: header fields, inspector Default and the picked parameter looked at in two screenshots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
de14f0f9e2
commit
183f356c99
25 files changed
+785
-99
No files matched your search
+40
-6
@@ -272,7 +272,9 @@ export async function sound(t) {
|
||||
|
||||
// 34g-3. A sound FX asset (#311): "+ → Sound FX" opens its tab, which says what keeps the graph
|
||||
// from compiling; built into Input → Pitch → Gain → Output, the header plays the sound
|
||||
// through it, and a `Play sound` naming it sends the game's voice the same way.
|
||||
// through it, and a `Play sound` naming it sends the game's voice the same way. The pitch
|
||||
// comes from a parameter `depth` of the Input: the header has a field for it, and a
|
||||
// `Sound FX parameters` node hands the game's value in.
|
||||
await showTab('scene');
|
||||
await clickText('+');
|
||||
await pickAssetType(t, 'Sound FX');
|
||||
@@ -309,8 +311,8 @@ export async function sound(t) {
|
||||
fx.name = 'Low';
|
||||
fx.graph = ${JSON.stringify({
|
||||
nodes: [
|
||||
node('fx-in', 'SoundFxInput', 0, 0),
|
||||
node('fx-pitch', 'SoundFxPitch', 220, 0, { semitones: -12 }),
|
||||
node('fx-in', 'SoundFxInput', 0, 0, {}, [{ key: 'depth', kind: 'number', default: -12 }]),
|
||||
node('fx-pitch', 'SoundFxPitch', 220, 0),
|
||||
node('fx-gain', 'SoundFxGain', 440, 0, { gain: -6 }),
|
||||
node('fx-out', 'SoundFxOutput', 660, 0),
|
||||
],
|
||||
@@ -318,12 +320,15 @@ export async function sound(t) {
|
||||
edge('fx-e1', 'fx-in', 'out', 'fx-pitch', 'in'),
|
||||
edge('fx-e2', 'fx-pitch', 'out', 'fx-gain', 'in'),
|
||||
edge('fx-e3', 'fx-gain', 'out', 'fx-out', 'in'),
|
||||
edge('fx-e4', 'fx-in', 'depth', 'fx-pitch', 'semitones'),
|
||||
],
|
||||
})};`);
|
||||
const fxBuilt = await json('{ info: __gm.soundfx.info(), preview: __gm.soundfx.preview() }');
|
||||
const fxBuilt = await json(
|
||||
'{ info: __gm.soundfx.info(), preview: __gm.soundfx.preview(), params: __gm.soundfx.params() }',
|
||||
);
|
||||
check(
|
||||
'Input → Pitch → Gain → Output compiles to two effects, and Play is offered',
|
||||
fxBuilt.info === '2 effects' && fxBuilt.preview.play === true,
|
||||
'Input → Pitch → Gain → Output compiles to two effects, Play is offered, depth starts at its default',
|
||||
fxBuilt.info === '2 effects' && fxBuilt.preview.play === true && fxBuilt.params.depth === -12,
|
||||
JSON.stringify(fxBuilt),
|
||||
);
|
||||
await evaluate('__gm.sound.stopAll()');
|
||||
@@ -337,6 +342,16 @@ export async function sound(t) {
|
||||
JSON.stringify(heardFx),
|
||||
);
|
||||
await evaluate('__gm.sound.stopAll()');
|
||||
await evaluate('__gm.soundfx.setParam("depth", 5)');
|
||||
const beforeParam = await stats();
|
||||
await click(fxPlayAt.x, fxPlayAt.y);
|
||||
const heardParam = await waitFor(stats, (s) => s.started > beforeParam.started);
|
||||
check(
|
||||
'the header plays with the value its depth field is set to',
|
||||
heardParam.last?.pitch === 5 && heardParam.last.stages === 1,
|
||||
JSON.stringify(heardParam),
|
||||
);
|
||||
await evaluate('__gm.sound.stopAll()');
|
||||
await showTab('scene');
|
||||
await mutate(`
|
||||
const graph = __gm.state.project.assets.find((a) => a.id === 'sn-logic').graph;
|
||||
@@ -352,6 +367,25 @@ export async function sound(t) {
|
||||
await stop();
|
||||
await mutate(`
|
||||
const graph = __gm.state.project.assets.find((a) => a.id === 'sn-logic').graph;
|
||||
graph.nodes.push(${JSON.stringify(
|
||||
node('sn-fxp', 'SoundFxParameters', 0, 120, { soundFxAsset: fxId, depth: 7 }, [
|
||||
{ key: 'depth', kind: 'number' },
|
||||
]),
|
||||
)});
|
||||
graph.edges.push(${JSON.stringify(edge('sn-e-fxp', 'sn-fxp', 'params', 'sn-play', 'fxParams'))});`);
|
||||
const beforeGameParam = await stats();
|
||||
await play();
|
||||
const gameParam = await waitFor(stats, (s) => s.started > beforeGameParam.started);
|
||||
check(
|
||||
'a Sound FX parameters node hands depth 7 to the game’s voice',
|
||||
gameParam.last?.pitch === 7 && gameParam.last.stages === 1,
|
||||
JSON.stringify(gameParam),
|
||||
);
|
||||
await stop();
|
||||
await mutate(`
|
||||
const graph = __gm.state.project.assets.find((a) => a.id === 'sn-logic').graph;
|
||||
graph.nodes = graph.nodes.filter((n) => n.id !== 'sn-fxp');
|
||||
graph.edges = graph.edges.filter((e) => e.id !== 'sn-e-fxp');
|
||||
Object.assign(graph.nodes.find((n) => n.id === 'sn-play').params, { loop: '', fx: '' });
|
||||
const project = __gm.state.project;
|
||||
project.assets = project.assets.filter((a) => a.id !== ${JSON.stringify(fxId)});`);
|
||||
|
||||
Reference in new issue
Block a user