Play a sound through a graph of gain, pitch and EQ effects #311
A sound could only be played as it was uploaded. A Sound FX asset is
a node graph like a shader: Input, effects, Output. Play sound has a
new input fx that sends the voice through it.
- New asset type soundfx ("Sound FX"), not placeable, with a graph
and its own tab. The heading in the asset panel is "Sound effects",
so it does not take the click meant for the + menu entry.
- Nodes in core/soundfx/nodes.ts, category sound, port type audio
that only fits audio: Input, Output, Gain (dB), Pitch (semitones),
EQ (80 Hz and 12 kHz shelves, peaks at 250 Hz, 1 kHz and 4 kHz).
- compileSoundFx walks back from the Output with the shared graph
emitter. An input takes one edge, so the result is one line: a
pitch and a list of gain and filter stages. A node without input
or a graph without Output reports a problem on the node.
- Pitch is the playback rate of the source. It applies to the whole
sound and also changes its length, the node description says so.
- The BehaviorSystem resolves fx and hands the line to the AudioSink.
An fx that is missing or does not compile plays the sound dry.
- SoundLibrary wires source, stages, volume, destination in that
order. stats().last now also names pitch and stage count.
- The tab header picks one of the project's sounds and plays it
through the graph as it stands. Stop there ends only its own voice.
Tests:
- soundfx.test.ts (new, 11), sound-library (9, +2 for the wiring),
node-catalog (+1 for fx), graph and asset-groups adjusted
- e2e 34: ALL PASSED - 26 checks (+5: + menu opens the tab, palette,
2 effects, tab Play and game Play through the FX at -12 st)
- Gate on melektaus-atx: typecheck, lint, prettier clean, 239 of 240
test files green. The red one was sound-library (its fake
AudioContext had no playbackRate), fixed afterwards, gate not
re-run in full.
- Counter-check: without the audio rule in portsCompatible the port
test fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
93c0a80b7d
commit
de14f0f9e2
34 files changed
+899
-43
No files matched your search
+12
-1
@@ -310,6 +310,15 @@ export interface SoundAsset extends AssetBase {
|
||||
file: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* What a sound is played through (#311): the graph runs from its `Input` over effect nodes to its
|
||||
* `Output`, and a `Play sound` naming it sends the voice along that line (see `core/soundfx`).
|
||||
*/
|
||||
export interface SoundFxAsset extends AssetBase {
|
||||
type: 'soundfx';
|
||||
graph: Graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* A tile a tilemap paints with: `pictures` names one block per animation step (a texture id for a
|
||||
* sheet that holds nothing else, a frame id for one block cut out of a sheet), and `props` say
|
||||
@@ -562,6 +571,7 @@ export type Asset =
|
||||
| ShaderFunctionAsset
|
||||
| FontAsset
|
||||
| SoundAsset
|
||||
| SoundFxAsset
|
||||
| ParticlesAsset;
|
||||
|
||||
/** The assets whose `variables` are declared by the user – see `requireVariableOwner`. */
|
||||
@@ -573,7 +583,7 @@ export function isVariableOwner(asset: Asset | undefined): asset is VariableOwne
|
||||
|
||||
/** The assets edited as a node graph – what the graph canvas and the graph commands work on. */
|
||||
export type GraphAsset =
|
||||
BehaviorAsset | StateMachineAsset | ShaderAsset | ShaderFunctionAsset | ParticlesAsset;
|
||||
BehaviorAsset | StateMachineAsset | ShaderAsset | ShaderFunctionAsset | ParticlesAsset | SoundFxAsset;
|
||||
|
||||
const GRAPH_TYPES: ReadonlySet<AssetType> = new Set([
|
||||
'behavior',
|
||||
@@ -581,6 +591,7 @@ const GRAPH_TYPES: ReadonlySet<AssetType> = new Set([
|
||||
'shader',
|
||||
'shaderfunction',
|
||||
'particles',
|
||||
'soundfx',
|
||||
]);
|
||||
|
||||
export function isGraphAsset(asset: Asset): asset is GraphAsset {
|
||||
|
||||
@@ -24,6 +24,7 @@ export const ASSET_TYPES = [
|
||||
'shaderfunction',
|
||||
'font',
|
||||
'sound',
|
||||
'soundfx',
|
||||
'particles',
|
||||
] as const;
|
||||
|
||||
@@ -55,6 +56,7 @@ export const ASSET_TYPE_LABELS: Record<AssetType, string> = {
|
||||
shaderfunction: 'Shader function',
|
||||
font: 'Font',
|
||||
sound: 'Sound',
|
||||
soundfx: 'Sound FX',
|
||||
particles: 'Particles',
|
||||
};
|
||||
|
||||
@@ -66,7 +68,8 @@ export const ASSET_TYPE_LABELS: Record<AssetType, string> = {
|
||||
* what a tilemap draws with, so it is placed no more directly than a texture is – what is placed
|
||||
* is the tilemap. A curve is the way a key or a `Lerp` blends, picked by id like a state machine.
|
||||
* A struct is a shape that variables and fields take on (#253), nothing that exists on its own.
|
||||
* A shader function is part of other shaders (#303), worn by nothing itself.
|
||||
* A shader function is part of other shaders (#303), worn by nothing itself. A sound FX is what a
|
||||
* sound is played through (#311), heard but never seen.
|
||||
*/
|
||||
export type PlaceableAssetType = Exclude<
|
||||
AssetType,
|
||||
@@ -83,6 +86,7 @@ export type PlaceableAssetType = Exclude<
|
||||
| 'shaderfunction'
|
||||
| 'font'
|
||||
| 'sound'
|
||||
| 'soundfx'
|
||||
| 'variables'
|
||||
| 'struct'
|
||||
>;
|
||||
@@ -101,6 +105,7 @@ const NOT_PLACEABLE: ReadonlySet<AssetType> = new Set([
|
||||
'shaderfunction',
|
||||
'font',
|
||||
'sound',
|
||||
'soundfx',
|
||||
'variables',
|
||||
'struct',
|
||||
]);
|
||||
|
||||
Reference in new issue
Block a user