3 Commits

Author SHA1 Message Date
Cameron Clark ad2be09ee9 Fix: Normalize sound effect loudness (#3738)
## Summary
- Normalises all 20 SFX files to -22 LUFS with true peak ≤ -2 dBFS
- Collapses a ~20dB loudness spread (hydrogen-hit at -14.94 LUFS vs
sam-hit at -34.56 LUFS) into ~2dB
- Previously jarring hits (hydrogen, atom, MIRV) pulled down 6–8dB;
previously-quiet hits (sam-hit, message) brought up to match
- Short clips (`click.mp3`, `ka-ching.mp3`) too short for EBU R128 — got
a flat -6dB trim

## Why
Addresses #3728, specifically the "*WAY too loud*" feedback. Loudest
clips were peaking within ~1.5dB of clipping.

Does **not** change overlap/stacking — per discussion in #3728 and
#3394, the 8-concurrent-sound channel limit is retained intentionally.
At -22 LUFS, multiple overlapping events remain comfortable.

## Before / after (key files)

| Effect | Before (LUFS / TP dBFS) | After (LUFS / TP dBFS) | Δ |
|---|---|---|---|
| hydrogen-hit | -14.94 / -1.62 | -22.52 / -9.10 | −7.6 dB |
| atom-launch | -17.16 / -1.53 | -23.84 / -8.14 | −6.7 dB |
| hydrogen-launch | -16.15 / -1.92 | -23.65 / -9.37 | −7.5 dB |
| mirv-launch | -19.08 / -3.17 | -24.80 / -9.80 | −5.7 dB |
| sam-hit | -34.56 / -10.77 | -23.97 / -1.98 | +10.6 dB (raised up) |
| message | -29.47 / -24.46 | -21.97 / -17.00 | +7.5 dB (raised up) |

All durations unchanged (verified with `ffprobe`). All true peaks stay
below -1 dBFS — no clipping.

## How
Applied with ffmpeg's EBU R128 `loudnorm` filter:
```
ffmpeg -i in.mp3 -af "loudnorm=I=-22:TP=-2:LRA=7" -codec:a libmp3lame -q:a 2 out.mp3
```

Files shorter than the 1-second EBU R128 window (`click.mp3` 153ms,
`ka-ching.mp3` 586ms) used \`-af volume=-6dB\` instead.

## Test plan
- [x] Singleplayer: launch atom / hydrogen / MIRV nukes, verify
detonations are satisfying but not startling
- [x] Build city / port / warship / SAM, verify construction sounds sit
naturally in the mix
- [x] Trigger alliance suggest/break, verify they match loudness of
build sounds
- [x] Trigger many simultaneous events, verify the mix is comfortable
- [x] Verify no audio truncation / glitching / clipping

Addresses #3728
2026-04-22 14:35:35 -07:00
Cameron Clark 18da7134c8 Implement FX sound effects (#3394)
## Description:
Adds sound effects for approved events from the [sound asset
pack](https://drive.google.com/drive/folders/1KpGYJkmLxipy8XmTeyHf40XDC4P--Ck8?usp=sharing).
15 new sound effects triggered from `FxLayer`, `EventsDisplay`, and
`RadialMenu`. Sounds play even when visual FX are off, so disabling
explosions doesn't kill audio. Unapproved sounds are included as assets
but not wired up yet.

### SoundManager architecture

Reworked `SoundManager` per [maintainer
feedback](https://github.com/openfrontio/OpenFrontIO/issues/1893#issuecomment-4184649434)
and [follow-up
review](https://github.com/openfrontio/OpenFrontIO/pull/3394):

- No more singleton. `SoundManager` is instantiated in
`createClientGame()` with `EventBus` and `UserSettings`
- Layers emit events (`PlaySoundEffectEvent`,
`SetBackgroundMusicVolumeEvent`, `SetSoundEffectsVolumeEvent`) via
EventBus instead of holding a `SoundManager` reference
- `SoundManager` subscribes to these events in its constructor
- `SoundEffect` is a type union (not an enum), per project convention
- All sound configuration (type, URL mapping, events) lives in
`Sounds.ts`
- Sound effects are lazy-loaded on first play
- Channel limit of 8 concurrent sounds. New sounds always play; when at
the limit, the oldest active sound gets stopped
- `SoundManager` bootstraps volume from `UserSettings` in its
constructor
- All Howler calls are wrapped in try/catch with error logging, so sound
failures never crash the game
- `dispose()` method unsubscribes from EventBus and unloads all Howl
instances on game shutdown
- Sound code stays entirely in `src/client/`, nothing in `core/` touches
it

## Sound approval status (per
[spreadsheet](https://drive.google.com/drive/folders/1KpGYJkmLxipy8XmTeyHf40XDC4P--Ck8?usp=sharing))

### Approved, wired up in this PR

| Event | Sound file | Trigger location |
|-------|-----------|-----------------|
| Message sent/received | `message.mp3` | EventsDisplay |
| Menu open/select | `click.mp3` | RadialMenu |
| Atom bomb launch | `atom-launch.mp3` | FxLayer (unit created) |
| Atom bomb / MIRV hit | `atom-hit.mp3` | FxLayer (reached target) |
| Hydrogen launch | `hydrogen-launch.mp3` | FxLayer (unit created) |
| Hydrogen hit | `hydrogen-hit.mp3` | FxLayer (reached target) |
| MIRV launch | `mirv-launch.mp3` | FxLayer (unit created) |
| Alliance suggested | `alliance-suggested.mp3` | EventsDisplay |
| Alliance broken | `alliance-broken.mp3` | EventsDisplay |
| Port built | `build-port.mp3` | FxLayer (construction complete) |
| City built | `build-city.mp3` | FxLayer (construction complete) |
| Defense post built | `build-defense-post.mp3` | FxLayer (construction
complete) |
| Warship built | `build-warship.mp3` | FxLayer (unit created) |
| SAM built | `sam-built.mp3` | FxLayer (construction complete) |

### Waiting for approval, sound files included but NOT wired up

| Event | Sound file | Notes |
|-------|-----------|-------|
| Missile Silo built | `silo-built.mp3` | Waiting for Approval |
| SAM shoot | `sam-shoot.mp3` | Waiting for Approval |
| SAM hit | - | Waiting for Approval, no sound file assigned |
| Warship sunk | - | Waiting for Approval, no sound file assigned |
| Warship shoot | - | Waiting for Approval, no sound file assigned |

### Not done, no sound files exist yet

| Event | Notes |
|-------|-------|
| Looted player | "Not sure if needed" |
| Invaded | - |
| Ship invasion incoming | - |
| Ship sent | - |
| Menu theme song | - |
| Ambience | "Not sure if needed" |

## Test plan
- [x] Start a private game and launch atom/hydrogen/MIRV nukes, verify
launch and detonation sounds
- [x] Build structures (city, port, defense post, SAM), verify build
completion sounds
- [x] Build a warship, verify warship built sound
- [x] Receive an alliance request, verify alliance suggested sound
- [x] Break an alliance, verify alliance broken sound
- [ ] Receive a chat message, verify message sound
- [x] Open the radial menu and click items, verify click sound
- [x] Disable visual FX in settings, verify sounds still play
- [x] Adjust SFX volume slider, verify it affects all new sounds
- [x] Verify no audio issues with rapid/overlapping events
- [x] Verify SoundManager responds to EventBus events and unsubscribes
cleanly on dispose
- [x] Verify SoundManager swallows Howler errors without crashing the
game
- [x] Verify channel limit of 8, oldest sound stopped when at cap

## Checklist
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

Resolves #1893

## Please put your Discord username so you can be contacted if a bug or
regression is found:
cool_clarky
2026-04-06 21:01:23 -07:00
Gabe Kauffman 5b36c02ff0 Implement a "ka-ching" sound effect on kill (#2097)
## Description:
Building off of [this
PR](https://github.com/openfrontio/OpenFrontIO/pull/2090) which
implemented music, I extend this functionality to add sound effects.
Diff will be reduced if and when that PR gets merged!

I think the game would benefit from more sound effects, and adding a
"ka-ching" sound effect on kill seems like an easy place to start.

The ka-ching sound effect was found
[here](https://freesound.org/people/AKkingStudio/sounds/684165/) and is
licensed under Creative Commons.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

Demo video with sound:

https://github.com/user-attachments/assets/18c857a4-a741-492a-bbc1-68d4f3ba38da


## Please put your Discord username so you can be contacted if a bug or
regression is found:
basedgob

---------

Co-authored-by: icslucas <carolinacarazolli@gmail.com>
2025-10-02 16:27:06 -07:00