A game that stays silent is only half a game, so a sound is an asset like any other (#45)
game-maker / suite (push) Successful in 12m1s
game-maker / suite (push) Successful in 12m1s
Sounds follow the road fonts already paved: the file goes to the server content-addressed under its sha256, the project keeps a named asset that points at it, and nothing is ever re-encoded. The format is read from the file's own signature, not from what the browser claims - the same .m4a arrives as audio/mp4, audio/x-m4a or as nothing at all. Playing is two nodes. "Play sound" takes the asset and may raise or lower its volume; every call is its own voice, so two jumps in a row are two jumps heard. Its loop input is a choice of three - from the asset, on, off - because a checkbox has to spend one of its two positions on "not set" and would lose a case: an asset that loops could then never be played once. Below the graph core stays browser-free: NodeContext hands out an asset id, BehaviorSystem resolves it against the project and folds the asset's own volume and loop into the call, and only viewport/SoundLibrary knows about Web Audio. A run without a sink plays nothing instead of throwing. The CI browser is muted, so the ear is no witness: the debug hook counts started and stopped voices, and step 34 reads that counter.
This commit is contained in:
1 parent
7babf88636
commit
5671a38044
47 files changed
+2066
-42
No files matched your search
@@ -129,6 +129,16 @@ export interface FontAsset extends AssetBase {
|
||||
file: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* `file` names the sound in `data/sounds/` the way a font names its typeface: content-addressed
|
||||
* `{sha256}.{ext}`, empty when the upload failed. Nothing wears a sound – a graph plays it with
|
||||
* `Play sound`, and `props` says how loud and whether it repeats (see `properties/sounds.ts`).
|
||||
*/
|
||||
export interface SoundAsset extends AssetBase {
|
||||
type: 'sound';
|
||||
file: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The graph is the motion of one particle: every output of its `Output` node is a function of the
|
||||
* particle's age and its own random number (see `core/particles/compile.ts`). A placed object is
|
||||
@@ -153,6 +163,7 @@ export type Asset =
|
||||
| StateMachineAsset
|
||||
| ShaderAsset
|
||||
| FontAsset
|
||||
| SoundAsset
|
||||
| ParticlesAsset;
|
||||
|
||||
/** The assets edited as a node graph – what the graph canvas and the graph commands work on. */
|
||||
|
||||
@@ -12,6 +12,7 @@ export const ASSET_TYPES = [
|
||||
'statemachine',
|
||||
'shader',
|
||||
'font',
|
||||
'sound',
|
||||
'particles',
|
||||
] as const;
|
||||
|
||||
@@ -31,17 +32,19 @@ export const ASSET_TYPE_LABELS: Record<AssetType, string> = {
|
||||
statemachine: 'State machine',
|
||||
shader: 'Shader',
|
||||
font: 'Font',
|
||||
sound: 'Sound',
|
||||
particles: 'Particles',
|
||||
};
|
||||
|
||||
/**
|
||||
* Textures and animations are not placeable: they are pictures a sprite wears through `texture`,
|
||||
* as a shader is worn through `shader` and a font through `font`. A state machine is not either:
|
||||
* a behavior refers to it by id, and a Variables asset is declarations a graph reads by id.
|
||||
* a behavior refers to it by id, and a Variables asset is declarations a graph reads by id. A
|
||||
* sound is nowhere on screen at all – a graph plays it, so there is nothing to place.
|
||||
*/
|
||||
export type PlaceableAssetType = Exclude<
|
||||
AssetType,
|
||||
'scene' | 'texture' | 'animation' | 'statemachine' | 'shader' | 'font' | 'variables'
|
||||
'scene' | 'texture' | 'animation' | 'statemachine' | 'shader' | 'font' | 'sound' | 'variables'
|
||||
>;
|
||||
|
||||
const NOT_PLACEABLE: ReadonlySet<AssetType> = new Set([
|
||||
@@ -51,6 +54,7 @@ const NOT_PLACEABLE: ReadonlySet<AssetType> = new Set([
|
||||
'statemachine',
|
||||
'shader',
|
||||
'font',
|
||||
'sound',
|
||||
'variables',
|
||||
]);
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { SoundAsset } from './Asset';
|
||||
import { findAsset, type Project } from './Project';
|
||||
|
||||
/** Mirrors `SoundStore::isValidName` on the server, where the name is derived from the bytes. */
|
||||
const FILE_PATTERN = /^[a-f0-9]{64}\.(mp3|ogg|wav|m4a)$/;
|
||||
|
||||
export function isSoundFile(name: string): boolean {
|
||||
return FILE_PATTERN.test(name);
|
||||
}
|
||||
|
||||
/** The file a `sound` value points at, empty when the id is unset or no longer resolves. */
|
||||
export function soundFileOf(project: Project, id: string): string {
|
||||
if (id === '') return '';
|
||||
const asset = findAsset(project, id);
|
||||
return asset?.type === 'sound' ? asset.file : '';
|
||||
}
|
||||
|
||||
export function isSoundAsset(asset: { type: string }): asset is SoundAsset {
|
||||
return asset.type === 'sound';
|
||||
}
|
||||
Reference in new issue
Block a user