Add ReplayGain support
Reads REPLAYGAIN_TRACK_GAIN / REPLAYGAIN_ALBUM_GAIN / REPLAYGAIN_*_PEAK
tags off decoded tracks and applies a linear multiplier to samples on
their way to cpal. User picks Off / Track / Album via `g` in the TUI.
Data
- fin-config: new ReplayGainMode enum and ReplayGainSettings struct
(mode, preamp_db, prevent_clip). Config gains a `replaygain` field
with #[serde(default)] so old on-disk configs keep loading. Pure
data, no fin-player dep required.
- fin-player: new replaygain module owns ReplayGainInfo (per-track
tag values) and the linear_gain math — dB→linear, preamp additive
in dB domain, peak-based clip prevention, fallback to the other
scope when the requested one is missing. 11 unit tests cover the
numeric edge cases (clip prevention, preamp, fallback, junk parse).
Audio path
- SymphoniaPlayer stores current ReplayGainSettings in the worker,
extracts ReplayGainInfo from the first packet's metadata on each
track load, and precomputes a linear gain that the sample push
loop folds into the same multiply as user volume.
- New PlayerCommand::SetReplayGain updates the settings live and
recomputes the current track's linear gain in place.
- Renderer trait gains set_replaygain with a no-op default (Chromecast
and UPnP do their own device-side normalization).
TUI
- `g` cycles Off → Track → Album → Off, saves to config, statuses
"ReplayGain: <mode>".
- Player bar shows a compact "RG:track" / "RG:album" badge only when
active; muted when Off (row stays balanced).
- Settings screen has a new row: mode + preamp dB + clip-guard flag
+ "press g to cycle" hint. Panel grew from 7→8 rows to fit.
- Help hint updated.
Verified live: on a track without RG tags, linear=1.0 is logged and the
badge lights up when cycled; config.toml grows a [replaygain] section
that survives restart; 76 workspace tests pass.