Let a game hand values to the parameters of a sound FX #311
game-maker / gate (push) Successful in 3m10s
game-maker / suite (push) Failing after 14m15s

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:
adminandClaude Opus 5.5 committed 2026-10-05 17:26:20 +02:00
1 parent de14f0f9e2
commit 183f356c99
25 files changed
+785 -99

No files matched your search

+22 -2
View File
@@ -1,3 +1,5 @@
import { isStruct } from '../../../model/Properties';
import type { FxParams } from '../../../soundfx/nodes';
import type { NodeDef, PortDef } from '../../NodeRegistry';
import { asBoolean, asNumber, asString, type Value } from '../../values';
import { exec, num, soundFxPort, soundPort, withDefault } from '../shared';
@@ -56,8 +58,17 @@ export const PlaySound: NodeDef = {
"leaves the asset's own setting alone or overrules it, so the asset says what the sound is and " +
'the node only colors it. Each play is a voice of its own: firing this twice in a row is heard ' +
'twice, overlapping. The sound belongs to this object and stops when the object is destroyed, ' +
'unless "detached" is on. "fx" sends it through a sound FX asset; left empty, it plays as it is.',
inputs: [exec(), soundPort(), withDefault(num('volume'), 1), loopPort(), detachedPort(), soundFxPort()],
'unless "detached" is on. "fx" sends it through a sound FX asset; left empty, it plays as it is. ' +
'"fx params" takes values for its parameters from a Sound FX parameters node.',
inputs: [
exec(),
soundPort(),
withDefault(num('volume'), 1),
loopPort(),
detachedPort(),
soundFxPort(),
{ key: 'fxParams', label: 'fx params', type: 'fxparams' },
],
outputs: [exec()],
params: [],
run: (ctx, inputs) => {
@@ -69,7 +80,16 @@ export const PlaySound: NodeDef = {
loopChoice(inputs.loop),
asBoolean(inputs.detached),
asString(inputs.fx),
fxParamsOf(inputs.fxParams),
);
return { next: 'exec' };
},
};
/** The numbers of a parameter bundle; anything else hands in none. */
function fxParamsOf(value: Value | undefined): FxParams {
if (!isStruct(value)) return {};
const params: Record<string, number> = {};
for (const [key, item] of Object.entries(value)) if (typeof item === 'number') params[key] = item;
return params;
}
@@ -0,0 +1,37 @@
import type { StructValue } from '../../../model/Properties';
import { SOUND_FX_PARAMETERS, SOUND_FX_SETTING, soundFxSourceOf } from '../../nested';
import type { NodeDef } from '../../NodeRegistry';
import { asNumber } from '../../values';
/**
* Values for the parameters of one sound FX (#311), picked from those its Input declares the way a
* Variables node picks variables. Each picked one is a number input starting at the FX's default;
* the bundle goes into the `fx params` input of a `Play sound`.
*/
export const SoundFxParameters: NodeDef = {
type: SOUND_FX_PARAMETERS,
label: 'Sound FX parameters',
category: 'data',
description:
'Values for the parameters of a sound FX, for the "fx params" input of a Play sound. Pick the parameters to set; one not picked keeps the default its Input gives it.',
inputs: [],
outputs: [{ key: 'params', label: 'params', type: 'fxparams' }],
params: [SOUND_FX_SETTING],
declaresPorts: 'inputs',
portKinds: ['number'],
declaredPort: (port, node, _graph, lookup) => {
const declared = lookup.soundFx(soundFxSourceOf(node.params))?.params.find((p) => p.key === port.key);
const value = declared?.default ?? 0;
return {
key: port.key,
label: port.key,
type: 'number',
default: value,
field: { key: port.key, label: port.key, kind: 'number', default: value },
};
},
run: (_ctx, inputs, _params, def) => {
const values: StructValue = Object.fromEntries(def.inputs.map((p) => [p.key, asNumber(inputs[p.key])]));
return { outputs: { params: values } };
},
};
+2
View File
@@ -89,6 +89,7 @@ import { Or } from './data/Or';
import { Owner } from './data/Owner';
import { PackStruct } from './data/PackStruct';
import { Properties } from './data/Properties';
import { SoundFxParameters } from './data/SoundFxParameters';
import { Parent } from './data/Parent';
import { Random } from './data/Random';
import { RenderLayerOf } from './data/RenderLayerOf';
@@ -297,6 +298,7 @@ export const BUILT_IN_NODES: readonly NodeDef[] = [
StopAnimation,
PlaySound,
StopSound,
SoundFxParameters,
LoadScene,
Log,
StopGame,