Skip to content

Sound: audio.play in @threert/gameplay, played by the engine on every platform - #77

Merged
doodlewind merged 2 commits into
mainfrom
audio
Nov 14, 2026
Merged

doodlewind merged 2 commits into
mainfrom
audio

Conversation

@doodlewind

Copy link
Copy Markdown
Collaborator

Games had no sound: the engine had no audio, and every app would have had to invent its own. This makes sound a runtime capability, the same everywhere.

Game side — packages/gameplay/src/audio.ts:

audio.preload(["assets/sounds/shot.ogg"]);                       // in setup: decoded ahead
audio.play("assets/sounds/shot.ogg", { at: muzzle, pitch: 1.03, falloff: 8 });
audio.play("assets/sounds/fire.ogg", { at, loop: true, key: "fire" }); audio.move("fire", at); audio.stop("fire", 0.5);

A sound is a file of the game's package; the listener is the frame's camera (nothing to send).

Engine side — renderer/src/audio (feature audio, on by default):

  • decoding by Symphonia (WAV, Ogg Vorbis, MP3, FLAC): the same samples on every platform;
  • placing (mix.rs): level to falloff, then falloff / distance, fade out 100–120 m; equal-power pan capped at 80 %; a low pass with distance and behind;
  • mixing: 48 voices, 8 copies of a file, ramps, fades, soft limiter, app volume, pause;
  • the device (out.rs, the only per-platform part): cpal on macOS / iOS / Windows; ALSA (Linux, Steam Deck) and AAudio (Android) loaded at run time, so nothing new is linked there.
  • threert-render play plays a game's sound.

Browser — the three.js view plays the same messages on Web Audio with the same numbers (tools/three/audio.ts; tools/three/test/audio.test.ts holds it to mix.rs).

Docs — docs/RUNTIME.md "Sound" is the contract.

Tested: unit tests (mixer, decoder, Audio, gameplay posts, page parity); threert-render play on macOS (Core Audio, 48 kHz); in the ThreeRT clients on an AYN Thor (AAudio stream, usage GAME, released in the background and reopened on return) and a Steam Deck (ALSA → PipeWire stream), and the three.js page in Chrome (both formats fetched and decoded, no errors).

🤖 Generated with Claude Code

doodlewind and others added 2 commits November 14, 2026 10:48
… platform

- packages/gameplay/src/audio.ts: `audio.play(sound, { at, gain, pitch, loop, key, falloff })`, `stop`, `move`,
  `preload`, posted on the host channel (`audio.*`). A sound is a file of the game's package.
- renderer/src/audio (the `audio` feature, on by default): decoding by Symphonia (WAV, Ogg Vorbis, MP3, FLAC), placing
  against the listener (the frame's camera: falloff / distance, equal-power pan to 80 %, a low pass far and behind),
  mixing (48 voices, 8 copies of a file, ramps, fades, a soft limiter) and the device: cpal on macOS, iOS and Windows,
  ALSA on Linux and AAudio on Android loaded at run time (no sound library linked). Apps hand it the posts and the
  camera; the device opens on the first sound, closes with the game and in the background.
- `threert-render play` plays a game's sound; the three.js view plays it on Web Audio by the same numbers
  (tools/three/audio.ts, held to mix.rs by a test).
- docs/RUNTIME.md "Sound": the contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r a path needs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@doodlewind
doodlewind merged commit 36d64ba into main Nov 14, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant