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,6 +1,7 @@
|
||||
import {
|
||||
callProblem,
|
||||
declaresPropertyPorts,
|
||||
declaresSoundFxPorts,
|
||||
declaresVariablePorts,
|
||||
edgesInto,
|
||||
edgesOfNode,
|
||||
@@ -16,6 +17,8 @@ import {
|
||||
OBJECT_TYPE_PARAM,
|
||||
portSignature,
|
||||
resolveDef,
|
||||
soundFxParamsMismatch,
|
||||
soundFxSourceOf,
|
||||
STATE_TRANSITION,
|
||||
stateName,
|
||||
STRUCT_TYPE_PARAM,
|
||||
@@ -42,6 +45,7 @@ import { addNote, addTitle, type InspectorHost } from './host';
|
||||
import {
|
||||
buildPortSection,
|
||||
buildPropertyPortSection,
|
||||
buildSoundFxPortSection,
|
||||
buildVariablePortSection,
|
||||
variableChoices,
|
||||
} from './portSection';
|
||||
@@ -67,7 +71,9 @@ export function nodeSignature(project: Project, assetId: string, nodeId: string)
|
||||
const declared = (node.ports ?? [])
|
||||
.map(
|
||||
(p) =>
|
||||
`${p.key}:${p.kind}${p.write ? ':set' : ''}${p.machine ? `:${p.machine}:${machineOf(p.machine)}` : ''}`,
|
||||
`${p.key}:${p.kind}${p.write ? ':set' : ''}${p.machine ? `:${p.machine}:${machineOf(p.machine)}` : ''}${
|
||||
p.default !== undefined ? `=${p.default}` : ''
|
||||
}`,
|
||||
)
|
||||
.join(',');
|
||||
// They decide which rows the inspector builds, and two behaviors may share a name in the title.
|
||||
@@ -82,13 +88,13 @@ export function nodeSignature(project: Project, assetId: string, nodeId: string)
|
||||
// The source names which declarations the port menu offers, so switching it has to rebuild.
|
||||
const typeId = nodeStructType(node) || listNodeItemType(node);
|
||||
const typeName = structAssetOf(project, typeId)?.name ?? '';
|
||||
const source = `${variablesSourceOf(node.params)}:${typeId}:${typeName}`;
|
||||
const source = `${variablesSourceOf(node.params)}:${soundFxSourceOf(node.params)}:${typeId}:${typeName}`;
|
||||
const choices = variableChoices(project, assetId, node)
|
||||
.map((v) => v.key)
|
||||
.join(',');
|
||||
return `${portSignature(node, asset.graph, lookup, assetId)}|${declared}|${connected.join(
|
||||
',',
|
||||
)}|${source}:${choices}|${target}|${transitions}`;
|
||||
)}|${source}:${choices}|${target}|${transitions}|${soundFxParamsMismatch(node, asset.graph) ?? ''}`;
|
||||
}
|
||||
|
||||
/** The name of the state at the other end of an edge – what a transition row is labelled with. */
|
||||
@@ -111,7 +117,8 @@ export function buildNodeSection(host: InspectorHost, assetId: string, nodeId: s
|
||||
addTitle(host, `${def.category.toUpperCase()} NODE`);
|
||||
host.body.add(new Label(def.label, { style: 'bodyBold' }));
|
||||
addNote(host, def.description);
|
||||
const problem = callProblem(node, graphLookup(host.state.project), assetId);
|
||||
const problem =
|
||||
callProblem(node, graphLookup(host.state.project), assetId) ?? soundFxParamsMismatch(node, asset.graph);
|
||||
if (problem) addNote(host, problem, 'warning');
|
||||
const addSetting = (param: PropertyDef, handlers = paramHandlers(host, assetId, nodeId, param)) => {
|
||||
const row = createFieldRow(host, param, node.params[param.key] ?? param.default, handlers);
|
||||
@@ -119,7 +126,8 @@ export function buildNodeSection(host: InspectorHost, assetId: string, nodeId: s
|
||||
host.body.add(row.row);
|
||||
};
|
||||
// Both draw their settings inside the port section: a setting decides what the menu offers.
|
||||
const ownSettings = declaresPropertyPorts(node.type) || declaresVariablePorts(node.type);
|
||||
const ownSettings =
|
||||
declaresPropertyPorts(node.type) || declaresVariablePorts(node.type) || declaresSoundFxPorts(node.type);
|
||||
// The type of a Pack/Expand decides its fields (#253), so it stands above them – and picking one
|
||||
// rewrites the ports, which a plain param edit would not.
|
||||
const typed = isTypedNode(node);
|
||||
@@ -134,6 +142,7 @@ export function buildNodeSection(host: InspectorHost, assetId: string, nodeId: s
|
||||
const pickItemType = (value: PropertyValue) =>
|
||||
host.state.execute(new SetListNodeTypeCommand(assetId, nodeId, textOf(value)));
|
||||
if (declaresVariablePorts(node.type)) buildVariablePortSection(host, assetId, node, addSetting);
|
||||
else if (declaresSoundFxPorts(node.type)) buildSoundFxPortSection(host, assetId, node, addSetting);
|
||||
else if (ownSettings) buildPropertyPortSection(host, assetId, node, addSetting);
|
||||
else if (def.declaresPorts) {
|
||||
const typeName = structAssetOf(host.state.project, nodeStructType(node))?.name ?? null;
|
||||
|
||||
Reference in new issue
Block a user