# Game audio (audio.core) The sound of a game on every target: mixing buses, music by state, 2D and 3D effects, ambience areas and a table "game event to sound". Readable offline: `gripforge docs audio-core`, or the MCP tool `gripforge_docs` (topic `audio-core`). ## What it is for A new game has sound without any asset: every kit brings default correspondences from its events to sounds, and `audio.core` ships a set of procedural base sounds (interface clicks, footsteps, jumps, hits, pickups, a vehicle engine, three music loops). The kit decides nothing of the game: it plays what the events say. To change what is heard, change the table, not the code. It is installed by default in every new game, with `ui.menu`, `input.remap` and `settings.graphics`. The player's mix is the Audio tab of the Options screen (`ui.menu`). ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "audio.core" } gripforge_gamekit_configure { project_id: "gkp_…", id: "audio.core", config: { "volumes": { "music": 50 }, "sounds": { "footstep": { "src": ["lib:abc", "lib:def"] } } } } ``` It requires only `game.engine`. Only the keys that differ from the defaults need to be written. The full reference is `gripforge kit api audio.core`. ## Buses `master`, `music`, `sfx` (effects), `voice`, `ambience`, `ui` (interface). Volume goes from 0 to 100 (gain = (v/100)^2), each bus can be muted, and master applies over the others. While the game is paused, `sfx`, `voice` and `ambience` are silenced (music and interface go on; `voiceDuringPause: true` keeps dialogue audible). The player's choices are saved in the player settings, outside game saves. Loading a save stops the sounds that were playing (past events are not replayed) and restarts the current music track. ## Settings and defaults | Key | Default | Meaning | | --- | --- | --- | | `volumes` | master 100, music 70, sfx 100, voice 100, ambience 80, ui 80 | default mix, before the player changes it | | `muted` | none | buses muted by default | | `maxVoices` | 32 | sounds at once, music and ambience included (1 to 128) | | `seed` | 1 | seed of the variant and variation picks | | `sounds` | `{}` | the project sound bank (adds or replaces) | | `defaultCues` | `true` | use the default event to sound correspondences of the kits | | `cues` | `[]` | the project's correspondences, after the defaults | | `music` | states, playlists, `fade` 1.5, `initial` exploration | music by state | | `ambience` | `[]` | looping areas mixed by the listener's position | | `voiceDuringPause` | `false` | keep dialogue audible when the world is paused | | `acoustics` | disabled | Rust-only room, door and occlusion model (below) | | `tab` | every bus, mutes on | what the Audio tab shows | ## Sounds The bank maps a sound id (`footstep`, `ui.click`, `music.combat`, `my_door`) to a definition: `src` (several sources are variants, one is picked at random, never the same twice in a row), `bus`, `volume`, `pitch`, `volumeJitter`, `pitchJitter` (random plus or minus fraction per play), `loop`, `spatial` (3D) with `minDistance`, `maxDistance`, `rolloff` (`inverse` or `linear`), `priority` (0 to 100), `limit` (instances at once), `duration`, `tags`. A source is `base:` (procedural), `lib:` (an audio asset of the library), `slot:` (an asset slot), an engine path (`res://…` on Godot, a Resources path on Unity, `/Game/…` on Unreal) or a URL on the web. Base sounds: `ui.click`, `ui.hover`, `ui.confirm`, `ui.back`, `ui.error`, `ui.open`, `jump`, `land`, `footstep`, `hit`, `impact`, `swing`, `shot`, `reload`, `pickup`, `coin`, `checkpoint`, `success`, `fail`, `door`, `explosion`, `thunder`, `vehicle.engine` (loop), `ambience.wind`, `ambience.night` (loops), `music.menu`, `music.exploration`, `music.combat` (loops). A project sound with the same id replaces one. ## Correspondences (cues) A cue is `{ event, action, sound | state, when, unless, ifVar, volume, pitch, key, param, range, pitchRange, volumeRange }`: - `play` (default) plays `sound` when `event` fires. `when` and `unless` match payload fields, `ifVar` game variables. A 3D sound is placed at the payload's `x, y, z` (or `position`), else at the entity named by `key` (default `entity`). A looping sound plays once per `key` (one engine per vehicle). - `stop` stops the looping voice of `sound` for that `key`. - `param` drives a sound from a payload number (`range` to `pitchRange` or `volumeRange`): an engine pitch from the speed. - `music` switches the music `state`. - `event: "var:"` fires when a game variable changes (payload `{ value, previous }`). ```json { "cues": [ { "event": "door.opened", "sound": "my_door", "volume": 0.8 }, { "event": "surface.footstep", "sound": "" }, { "event": "boss.started", "action": "music", "state": "combat" } ] } ``` An empty `sound` mutes the defaults of that event. `defaultCues: false` turns all the kits' defaults off. ## Music and ambience `music.states` maps a state to a playlist (`menu`, `exploration`, `combat` by default), `music.playlists` holds `{ tracks, shuffle, loop }`, `music.fade` is the crossfade in seconds, `music.initial` the state when the game starts without a menu. By default the title screen plays `menu`, play plays `exploration`, encounters and bosses play `combat`, a cleared encounter returns to `exploration`. Ambience areas are `{ id, sound, x, y, z, radius, fade, volume }`: radius 0 means everywhere, the gain is full inside the radius and fades out over `fade`. ## Service, events, variables Service `audio.core`: `play(sound, { position, volume, pitch, key })` (a voice id, 0 when not played), `playAt(sound, x, y, z)`, `stop(voice)`, `stopSound(sound)`, `stopAll()`, `trigger(event, payload)`, `setMusicState(state)`, `musicState()`, `playPlaylist(id)`, `nextTrack()`, `stopMusic()`, `music()`, `setListener(x, y, z)`, `gainAt(sound, x, y, z)`, `ambience()`, `volume(bus)`, `setVolume(bus, v)`, `muted(bus)`, `setMuted(bus, on)`, `busGain(bus)`, `mixer()`, `voices()`, `stats()`, `bank()`, `sound(id)`, `search(tag)`, `cuesFor(event)`, `reset()`, `reload()`. The options tab is `settings.audio` (`optionFields(lang)`, `setOption(id, value)`). Events: `audio.mix`, `audio.reset`, `audio.music`. Variables: `audio.volume.`, `audio.muted.`, `audio.music`, `audio.voices`. The kit listens to every event of the bus: that is the cue table. ## Hooks and modding A service: it takes no decision, so it declares no hook. A game or a mod changes the table. A mod may override `volumes`, `muted`, `defaultCues`, `cues`, `music` and `ambience`, all naming sounds by their id in the bank, so it remaps sounds the game already has. Not moddable: `sounds` (a source may be a URL or a path, the bank is the game's), `maxVoices`, `seed`, `tab`. The player's mix is a player setting: a mod changes the defaults, never what the player chose. ```json { "schema": "gripforge.mod/1", "id": "quiet_music", "name": "Quiet music", "version": "1.0.0", "requires": { "audio.core": "^1" }, "config": { "audio.core": { "volumes": { "music": 40 } } } } ``` ## Acoustics (Rust only, off by default) `acoustics: { enabled, rooms, portals, obstacles }` turns on a shared propagation model: non-overlapping rooms in metres, door propagation, extra occluding boxes, reverb profiles and hearing for stealth. It is provided as the capability `audio.acoustics` (`acousticSample(query)`, `acoustics(command)`) which consumers feature-detect. Only the Rust host computes it; the web player consumes the parameters, and the older engine scripts do not implement it. A game save holds only the acoustic scene. ## Engines | Engine | Rendering | | --- | --- | | Web | Web Audio: a gain node per bus into the master, HRTF panner for 3D voices | | Godot | audio buses `GF Music`, `GF Effects`… under `Master`; stream players, 3D players for spatial sounds | | Unity | audio sources routed to a mixer (groups Master, Music, SFX, Voice, Ambience, UI) | | Unreal | a sound class per bus under a master class, a sound mix with class adjusters | ## When it does not do what you expect | Symptom | Cause and fix | | --- | --- | | A sound plays nothing | `play` returned 0: unknown id, bus muted, `maxVoices` reached or `limit` of the sound reached | | An event stays silent | a cue with an empty `sound` mutes it, or `defaultCues` is `false` | | Same footstep twice | a single source in `src`: give several variants | | Sounds stop after a load | by design: past events are not replayed; the game resynchronises its own loops | | The 3D sound has no place | the cue payload lacks `x, y, z` / `position` and the `key` entity is unknown |