makepad/skills/makepad-audio-generator/references/audio-workflows.md
Arena Agent fe21c07d84 skills: add Makepad game skills pack ported from threejs-game-skills
Nine agent skills for building Makepad/Rust games, ported from
majidmanzarpour/threejs-game-skills. Same director-routed workflow and
premium bar; runtime rewritten for this fork's game crates.

- makepad-game-director        entrypoint, routing, continuity, asset probe
- makepad-gameplay-systems     loop, movers vs rigid bodies, input, camera, netplay
- makepad-aaa-graphics-builder lighting, shaders, budget, visual scorecard
- makepad-game-ui-designer     HUD, menus, touch and XR UI
- makepad-debug-profiler       defect bisection and profiling
- makepad-qa-release           verification ladder, evidence, packaging
- makepad-3d-generator         CC0 model search, casts, procedural geometry
- makepad-image-generator      texgen textures, palettes, sky, icons
- makepad-audio-generator      sample bank, mixer, material impacts, 3D audio

Written against the real APIs in libs/game/*, libs/sim and apps/arcade:
the game.* verb table, GameRenderer adaptive quality, the packed 6-float
GameMeshVertex layout, script_mod! splash styling, makepad-test driving,
and the BUDGETS.md numbers. No paid generation API is required - the CC0
library plus seeded makepad-game-gen replaces them.

Includes install.sh (Codex/Claude), validate-skills.sh and a repo-aware
probe_assets.sh; both scripts verified against this checkout.
2026-09-05 21:18:58 +00:00

102 lines
4.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Audio workflows
## Loading
```rust
let pcm = makepad_game_audio::decode(&bytes)?; // WAV or Ogg Vorbis -> Pcm
let id: SampleId = bank.add(pcm);
```
`SampleBank` owns loaded samples; `SampleId` is the handle you keep. Load at startup or during level load — decoding during play is a stutter source, and Vorbis decode is the most expensive audio operation in the stack.
`AudioError` distinguishes decode failures. Handle it: a missing or corrupt sound should log and continue, never panic mid-frame.
## Playback
```rust
let handle: VoiceHandle = mixer.play(VoiceSpec {
sample: id,
gain,
pitch,
priority: Priority::/* .. */,
// placement for positional voices
});
```
Keep `VoiceHandle`s for anything you must stop or modulate later — looping engine notes, ambience beds. Fire-and-forget for one-shots.
## Event-driven emission
```rust
for impact in dynamics.take_mover_impacts() {
let pair = MaterialPair::new(impact.a_material, impact.b_material);
director.emit(SoundEvent {
category: Category::Impact,
placement: Placement::At(impact.pos),
// gain shaped by ImpactCurve from impact energy
});
}
```
`take_mover_impacts()` is the single source of truth for collisions. Re-detecting collisions in gameplay code to trigger audio is how you get double sounds that drift out of sync with the visual effect.
## Material table
`Material`, `MaterialPair`, `ImpactCurve`.
Map **every** pair a scene can produce; an unmapped pair either falls back to a generic thud (annotated-sounding) or is silent (feels broken). Enumerate the materials in the scene and fill the matrix — it is small, and it is the difference between a world that sounds physical and one that sounds labelled.
`ImpactCurve` maps impact energy to gain and brightness: a light tap and a heavy crash should not be the same sample at the same level. Energy comes from the impact, not from a fixed constant per event type.
## Variation
```rust
let pitch = 1.0 + rng.range(-0.08, 0.08);
let gain = base * (1.0 + rng.range(-0.10, 0.10));
let sample = variants[rng.next_index(variants.len())];
```
`LocalRng`, seeded — never thread RNG, or networked clients hear different things and any replay diverges.
For sample choice, shuffle a bag rather than picking uniformly at random: pure random repeats the same variant back-to-back often enough to be audible, which defeats the purpose.
## Voice budget
| Category | Suggested cap |
| --- | --- |
| Player feedback | always plays, never evicted |
| UI | 4 |
| Nearby impacts | 8 |
| Footsteps | 6, coalesced |
| Ambience | 2–4 loops |
| Music | 1 |
Enforce with `Priority`. Cull by distance **before** mixing. Coalesce identical events within a frame into one louder voice. Debounce repeats of the same sample within ~30 ms — overlapping identical copies phase-cancel into a metallic artefact that sounds like a bug, because it is one.
## 3D
```rust
let listener = Listener { pos: camera.pos, forward: camera.forward, up: camera.up };
```
On the **camera**, not the player. Attenuate roughly inverse-square with a near clamp, cull beyond a max range, pan by the listener's right vector. Keep UI and player-feedback sounds non-positional so they never get quiet or pan away at the moment they matter.
## Ambience
One or two long loops, quiet, continuous, crossfaded on area change. Layer sparse one-shots (a bird, a distant creak) at randomised intervals — this is what stops a loop being recognisable as a loop, and it is much cheaper than a longer bed.
## Music and ducking
Music sits under everything. Duck it (and ambience) by a few dB with a fast attack and slow release when player feedback fires; this is what keeps important sounds audible without raising their level to the point of harshness.
The Kenney jingle pack covers win/lose/level-complete stings. Short stings beat looping tracks for most game-jam-scope games — cheaper, and they never outstay their welcome.
## Testing
```bash
cargo test -p makepad-game-audio
```
Cx-free, so it all tests headlessly. Assert: decode round-trips (rate, channels, length); the mixer honours its voice cap under a flood of events; priority evicts the lowest-priority voice, never the player feedback; every `MaterialPair` a scene can produce resolves; no emission path grows a `Vec` without bound.
Then listen for several minutes of real play. Numbers cannot tell you that a footstep is repeating audibly, that the mix collapses in a busy wave, or that a critical event has no sound at all.