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 {
|
||||
BEHAVIOR_PARAM,
|
||||
declaresPropertyPorts,
|
||||
declaresSoundFxPorts,
|
||||
declaresVariablePorts,
|
||||
type DeclaredPort,
|
||||
findNode,
|
||||
@@ -17,6 +18,8 @@ import {
|
||||
OBJECT_BEHAVIOR_PARAM,
|
||||
propertyTargetOf,
|
||||
SHADER_CALL,
|
||||
SOUND_FX_INPUT,
|
||||
soundFxSourceOf,
|
||||
variablesSourceOf,
|
||||
} from '../graph';
|
||||
import { isGraphAsset } from '../model/Asset';
|
||||
@@ -342,6 +345,54 @@ export class RenamePortCommand extends RenameCommand {
|
||||
if (ports.some((p) => p.key === to)) throw new CommandError(`"${to}" already exists`);
|
||||
node.ports = ports.map((p) => (p.key === from ? { ...p, key: to } : p));
|
||||
for (const site of sites) carryPort(site, from, to);
|
||||
if (node.type === SOUND_FX_INPUT) renameSoundFxParamPorts(project, this.assetId, from, to);
|
||||
}
|
||||
}
|
||||
|
||||
/** The default of a sound FX parameter (#311): what it is wherever no `Play sound` hands a value in. */
|
||||
export class SetPortDefaultCommand implements Command {
|
||||
readonly label: string;
|
||||
private was: number | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly assetId: string,
|
||||
private readonly nodeId: string,
|
||||
private readonly key: string,
|
||||
private readonly value: number,
|
||||
) {
|
||||
this.label = `Set default of ${key}`;
|
||||
}
|
||||
|
||||
execute(project: Project): void {
|
||||
const node = requireNode(project, this.assetId, this.nodeId);
|
||||
if (node.type !== SOUND_FX_INPUT) throw new CommandError('Only a sound FX Input has defaults');
|
||||
const port = (node.ports ?? []).find((p) => p.key === this.key);
|
||||
if (!port) throw new CommandError(`Port "${this.key}" not found`);
|
||||
if (!Number.isFinite(this.value)) throw new CommandError('A default is a number');
|
||||
this.was = port.default;
|
||||
this.mark(node, this.value);
|
||||
}
|
||||
|
||||
undo(project: Project): void {
|
||||
this.mark(requireNode(project, this.assetId, this.nodeId), this.was);
|
||||
}
|
||||
|
||||
private mark(node: GraphNode, value: number | undefined): void {
|
||||
node.ports = (node.ports ?? []).map((port) => {
|
||||
if (port.key !== this.key) return port;
|
||||
const { default: _, ...plain } = port;
|
||||
return value === undefined ? plain : { ...plain, default: value };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** A renamed parameter of a sound FX, followed into every node that sets it. */
|
||||
function renameSoundFxParamPorts(project: Project, fxId: string, from: string, to: string): void {
|
||||
for (const asset of project.assets) {
|
||||
if (!isGraphAsset(asset)) continue;
|
||||
for (const node of asset.graph.nodes)
|
||||
if (declaresSoundFxPorts(node.type) && soundFxSourceOf(node.params) === fxId)
|
||||
renamePortOn(asset.graph, node, from, to);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user