Add sound effects, background music and a sound menu

Generate retro sound effects and music in the browser with the Web Audio
API, so no audio files are needed. Any sound can still be replaced by a
file.

Audio (src/audio/):
- 16 effects (move, rotate, hold, hard drop, lock, line clears, garbage,
  items, countdown, knockout, win/lose), triggered by comparing match
  snapshots, so the server is unchanged
- Background music as a shuffled playlist of any number of tracks,
  either synth songs or audio files; 3 original songs included, tempo
  rises with the level
- SoundPlayer interface, so the audio engine can be replaced
- Audio files and music tracks are registered in theme/assets.ts
  (soundFiles, musicTracks)
- All volume levels for developers are in config/sound.ts (master, sfx,
  music, per effect, per track, default slider positions)

UI:
- Speaker button fixed in the top right corner of every screen; opens an
  inline strip with a mute button and volume slider for SFX and music
  that doesn't cover the boards or pause the game
- Leave button below it in the lobby and the game; in a running match
  it asks for a second click because leaving gives up the match
- Lobby Music panel to switch single tracks on or off
- Keys: M all sound on/off, B music on/off, N next track
- Settings are saved in localStorage

Docs: audio section in frontend.md, sound and music guide in
extending.md, new keys in game-rules.md and the README.
This commit is contained in:
2026-10-24 04:08:09 +02:00
parent 94052573b3
commit 1996b9c83e
34 changed files with 1586 additions and 15 deletions
@@ -21,6 +21,11 @@ export const keyBindings: Record<BoundAction, string[]> = {
/** Number row keys select the item target: 1 = yourself, 2..9 = opponents in display order. */
export const targetKeys = ['Digit1', 'Digit2', 'Digit3', 'Digit4', 'Digit5', 'Digit6', 'Digit7', 'Digit8', 'Digit9'];
/** Global sound keys, active on every screen. */
export const muteKey = 'KeyM';
export const musicKey = 'KeyB';
export const nextTrackKey = 'KeyN';
/** Delayed auto shift and auto repeat rate in milliseconds. */
export const DAS_MS = 167;
export const ARR_MS = 33;
@@ -35,6 +40,9 @@ export const controlsHelp: { keys: string; action: string }[] = [
{ keys: 'C / SHIFT', action: 'Hold' },
{ keys: '1 - 9', action: 'Select target (1 = you)' },
{ keys: 'F', action: 'Use item on target' },
{ keys: 'M', action: 'All sound on/off' },
{ keys: 'B', action: 'Music on/off' },
{ keys: 'N', action: 'Next music track' },
];
export const simpleActions: Partial<Record<BoundAction, InputAction>> = {