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:
@@ -10,6 +10,7 @@ Step-by-step guides for the most common changes.
|
||||
- [Add a new match event](#add-a-new-match-event)
|
||||
- [Change graphics and colors](#change-graphics-and-colors)
|
||||
- [Change key bindings](#change-key-bindings)
|
||||
- [Add or replace sounds and music](#add-or-replace-sounds-and-music)
|
||||
|
||||
---
|
||||
|
||||
@@ -179,3 +180,81 @@ Edit `src/config/keybindings.ts`:
|
||||
- `controlsHelp` holds the texts shown in the lobby's Controls panel. Keep them ASCII.
|
||||
|
||||
To add a new **action**, add it to `BoundAction` and `keyBindings`. If it maps 1:1 to a server action, add it to `simpleActions`. A new server action also needs an `InputAction` value on the server, handled in `PlayerGame.HandleInput` or `Match.HandleInput`, plus the matching string in `src/net/types.ts`.
|
||||
|
||||
---
|
||||
|
||||
## Add or replace sounds and music
|
||||
|
||||
The audio system is described in [Frontend > Audio](frontend.md#audio-srcaudio). Everything you can swap is registered in `src/theme/assets.ts`, next to the graphics.
|
||||
|
||||
### Balance the volumes
|
||||
|
||||
All loudness settings for developers are in **`src/config/sound.ts`**:
|
||||
|
||||
| Setting | Affects |
|
||||
|---|---|
|
||||
| `soundMix.master` | Everything |
|
||||
| `soundMix.sfx` / `soundMix.music` | All effects / all music |
|
||||
| `soundMix.effects.<id>` | One effect, e.g. `move: 0.5` makes moving half as loud |
|
||||
| `soundMix.tracks.<track id>` | One music track. Tracks not listed play at 1 |
|
||||
| `defaultSfxVolume` / `defaultMusicVolume` | Where the player's sliders start (0..1) when no settings are saved yet |
|
||||
|
||||
Every value multiplies the level the sound was designed with: `1` is unchanged, `0` is silent (an effect at `0` is not played at all), and values above `1` make it louder. The levels apply to synthesized sounds and to registered audio files alike. The player's volume sliders apply on top.
|
||||
|
||||
The `volume` numbers inside `synthSounds.ts` and `songs.ts` shape a sound (balance between its tones, or between a song's voices). Change those only when you redesign a sound itself.
|
||||
|
||||
### Replace an effect with an audio file
|
||||
|
||||
Put the file in `public/assets/sounds/` and register it under its `SoundId`:
|
||||
|
||||
```ts
|
||||
export const soundFiles: Partial<Record<SoundId, string>> = {
|
||||
lineClear: asset('assets/sounds/line_clear.wav'),
|
||||
};
|
||||
```
|
||||
|
||||
Any format the browser can decode works (wav, mp3, ogg). If the file fails to load, the synth sound is used and a warning is logged.
|
||||
|
||||
### Add music tracks
|
||||
|
||||
`musicTracks` is a playlist, and you can register as many tracks as you like. Tracks play in shuffled order, and players can switch single tracks off in the lobby.
|
||||
|
||||
```ts
|
||||
export const musicTracks: MusicTrack[] = [
|
||||
{ id: 'pixel_rush', title: 'Pixel Rush', kind: 'synth', song: songs.pixelRush },
|
||||
{ id: 'boss_fight', title: 'Boss Fight', kind: 'file', src: asset('assets/sounds/music/boss_fight.mp3') },
|
||||
];
|
||||
```
|
||||
|
||||
- **File track:** put the file in `public/assets/sounds/music/`. The next track starts when the file ends.
|
||||
- **Synth track:** add a `SynthSong` to `src/audio/songs.ts`. Each channel is a waveform, a volume and a text pattern: a note name (`C4`, `F#3`, `A#5`) starts a note, `-` holds it, `.` is a rest. Noise channels use `x` (short hit) and `o` (long hit). Shorter channels repeat until the longest one ends. After `loops` repetitions the playlist moves on.
|
||||
|
||||
```ts
|
||||
const myTune: SynthSong = {
|
||||
bpm: 140,
|
||||
stepsPerBeat: 2, // eighth notes
|
||||
loops: 6,
|
||||
channels: [
|
||||
{ wave: 'triangle', volume: 0.3, pattern: 'C3 C4 C3 C4 G2 G3 G2 G3' },
|
||||
{ wave: 'square', volume: 0.06, pattern: 'E5 - G5 . C6 - - . B5 - G5 . D5 - - .' },
|
||||
{ wave: 'noise', volume: 0.05, pattern: 'x . o .' },
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
The `id` must be unique and stay stable, because disabled tracks are saved by id. To change a track's level, add its id to `soundMix.tracks` in `src/config/sound.ts`.
|
||||
|
||||
### Add a new sound effect
|
||||
|
||||
1. Add the id to `SoundId` in `src/audio/types.ts`.
|
||||
2. Add a recipe to `synthSounds` in `src/audio/synthSounds.ts` (usually a few `tone()` / `noise()` calls) and a volume to `soundMix.effects` in `src/config/sound.ts`. TypeScript reports an error until both exist for every id.
|
||||
3. Trigger it with `player.play('myId')`: from `useGameSounds` for something visible in the snapshots, or anywhere through `useSound().player`.
|
||||
4. If the server should announce something the client can't detect, add a [match event](#add-a-new-match-event) and react to it in `useGameSounds`.
|
||||
|
||||
### Replace the whole engine
|
||||
|
||||
The UI only talks to the `SoundPlayer` interface (`src/audio/types.ts`). To use a different audio library, implement that interface and pass it in `main.tsx`:
|
||||
|
||||
```tsx
|
||||
<SoundProvider createPlayer={() => new MyHowlerPlayer()}>
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user