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
@@ -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 } };
|
||||
},
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user