# Audio

These twelve files are the game's soundtrack. They are **generated**, not
hand-placed: `tools/prepare_audio.sh` reads the raw recordings and writes
everything in this folder.

```bash
./tools/prepare_audio.sh          # reads game-sfx/, writes here
npm run prepare-audio             # same thing
```

Editing a file here by hand works until the next run of the script overwrites
it. Change the source recording or the slice table instead.

---

## What plays when

| File | Key | Gain | Plays on |
| --- | --- | --- | --- |
| `music_menu.mp3` | `music.menu` | 1.0 | Menu, How to Play, High Scores, Settings, Results |
| `music_game.mp3` | `music.game` | 1.0 | During a run |
| `sfx_whack.mp3` | `sfx.whack` | 1.0 | Hitting a normal or rocket mole |
| `sfx_gold.mp3` | `sfx.gold` | 0.85 | Hitting the golden mole |
| `sfx_bomb.mp3` | `sfx.bomb` | 0.8 | Hitting the grumpy mole (costs a life) |
| `sfx_pop.mp3` | `sfx.pop` | 0.35 | A mole emerging |
| `sfx_miss.mp3` | `sfx.miss` | 0.6 | A swing that hits nothing |
| `sfx_power.mp3` | `sfx.power` | 0.9 | Power hammer activating |
| `sfx_click.mp3` | `sfx.click` | 0.75 | Any button |
| `sfx_countdown.mp3` | `sfx.countdown` | 0.65 | Each of 3 · 2 · 1 before a run |
| `sfx_go.mp3` | `sfx.go` | 0.85 | The moment play starts |
| `sfx_gameover.mp3` | `sfx.gameover` | 0.9 | A run ending |

Total: ~570 KB.

---

## Gain, and why it is not baked into the files

The pipeline peak-normalises every effect to the same ceiling (−1 dBFS). That
makes them **measurably** equal, which is not the same as **perceptually**
equal — a sharp transient like `sfx_whack` reads as much quieter than a
sustained one like `sfx_bomb` at an identical peak.

`gain` in `src/config/assets.js` is where that gets corrected, and it is the
only place the balance lives. Two consequences worth knowing:

- **Retuning the mix is editing one number**, with no re-encoding and no
  hunting for a stray `{ volume: 0.5 }` buried in a scene.
- **`sfx.pop` is mixed far down (0.35)** because it fires on every spawn —
  several times a second at high difficulty. At full gain it is a machine gun.

Three values multiply to produce what you actually hear:

```
  sfxVolume    the player's setting          (Settings screen)
x gain         the sound's place in the mix  (AUDIO_MANIFEST)
x volume       per-call variation            (rare — used for pitch-paired ducking)
```

---

## Two notes on the source material

**The countdown was one file, not two.** `countdown.wav` is a complete
"3 · 2 · 1 · GO" sequence, but the game plays a tick per number in time with
the on-screen counter. Using it whole would have stacked four overlapping
copies of the entire sequence. The slice table cuts it in two: the first beep
becomes `sfx_countdown`, the closing flourish becomes `sfx_go`.

**Silence was the other problem.** `button.mp3` was 1.04 s of which 0.85 s was
nothing, and `main-menu-wack.wav` carried 0.4 s of silence at the *head* —
which, on a looping track, punches an audible hole every 13 seconds. The
pipeline trims both, with a 5 ms fade at each cut so the trim does not click.

---

## Replacing a sound

1. Drop the new recording into `game-sfx/`.
2. Point that row of the slice table in `tools/prepare_audio.sh` at it, with a
   trim range if it needs one.
3. `npm run prepare-audio`.

The script prints the gain it applied to each file, which is a quick way to
spot a source that is far off the others.

If you want a sound the game does not have yet, add a row to `AUDIO_MANIFEST`
first — a key with no file is a silent no-op, never an error, so the code can
call it before the audio exists.
