TASK 8 meter scaling: +10 dB input amplification in AudioLevelMeter.ToDisplay so the meters use the full bar — speech peaks (~0.2 RMS, -14 dBFS) read ~0.93 (red zone) and normal speech (~0.05, -26 dBFS) ~0.73 (yellow) at maxed volume, instead of hovering at the red edge; ≤0.001 linear still reads zero so the meter never idles on background noise; one knob shared by the mic bar and the game bar (× MicVolume gain-on-noise untouched); tests updated + new ToDisplay_Pushes_Speech_Peaks_Into_Red_At_Maxed_Volume — 171 tests passing, 0 warnings

This commit is contained in:
2026-08-14 08:43:06 -07:00
parent d90b5ded0d
commit 0b71b030e4
7 changed files with 53 additions and 12 deletions
+7 -4
View File
@@ -15,15 +15,18 @@ public sealed class AudioLevelMeter
/// <summary>
/// Maps a linear level (0..1) onto the meter's display scale: -60 dBFS..0 dBFS
/// spread linearly across 0..1. Linear RMS of real speech or game audio is
/// ~0.01..0.1 (-40..-20 dBFS), which leaves a flat (linear) meter looking
/// dead; the log scale makes typical levels occupy the bar.
/// spread linearly across 0..1, with +10 dB of input amplification. Linear RMS
/// of real speech or game audio is ~0.01..0.1 (-40..-20 dBFS), which leaves a
/// flat (linear) meter looking dead; the log scale makes typical levels occupy
/// the bar and the amplification pushes real speech peaks into the red zone
/// (0.8+) at maxed volume instead of hovering at its edge. Inputs at or below
/// -60 dBFS (0.001 linear) read as zero — the meter never idles on background noise.
/// </summary>
public static float ToDisplay(float linear)
{
if (linear <= 0.001f)
return 0f;
var db = 20f * MathF.Log10(linear);
var db = 20f * MathF.Log10(linear) + 10f;
return Math.Clamp(1f + db / 60f, 0f, 1f);
}
+1 -1
View File
@@ -49,7 +49,7 @@ External-facing logic: YouTube API, persistence. See
| `Audio/WasapiLoopbackAudioSource.cs` | Desktop/game capture: NAudio `WasapiLoopbackCapture` on the default render device — automatic at unity; the game bar's meter consumes it |
| `Audio/WasapiMicAudioSource.cs` | Mic capture: NAudio `WasapiCapture` with the device resolved by `FriendlyName` matching `MicSourceName` (DisplayName only), default capture endpoint fallback; name provider `Func<string?>` re-read at each `Start` so a device picked mid-session takes effect immediately (the mixer restarts the mic on pick) |
| `Audio/AudioMixer.cs` | Owns both sources; capture starts once at startup (`MainViewModel.StartMicCaptureAsync`) and stops on `Shutdown` — NOT go-live (preview monitoring). Mic samples → `AudioLevelMeter``MicLevelChanged`; loopback samples → the game bar's meter via `LoopbackLevelChanged`. Surfaces mic connection state: `MicConnected`/`MicFailed` (drives the status dot) + `RestartMic()` (device swap mid-session, keeps loopback). Failures log via `AppLog`; mic failure zeroes the meter, loopback failure doesn't kill the mic. Note: the meter `Push` is unconditional (the `?.` on the event would otherwise skip the argument when nothing is subscribed) |
| `Audio/AudioLevelMeter.cs` | Pure smoothed RMS level (0..1): `Push(AudioSample)` + `Reset` — the unit-tested math behind `AudioLevel` and `GameAudioLevel`. `ToDisplay(float)` maps the raw linear RMS onto the meter's display scale (60..0 dBFS spread across 0..1) — real speech/game RMS (~0.01..0.1) would otherwise leave a flat scale looking dead |
| `Audio/AudioLevelMeter.cs` | Pure smoothed RMS level (0..1): `Push(AudioSample)` + `Reset` — the unit-tested math behind `AudioLevel` and `GameAudioLevel`. `ToDisplay(float)` maps the raw linear RMS onto the meter's display scale (60..0 dBFS spread across 0..1, with **+10 dB input amplification** so real speech peaks hit the red zone at maxed volume) — real speech/game RMS (~0.01..0.1) would otherwise leave a flat scale looking dead; ≤0.001 linear reads as zero (never idles on background noise) |
| `Audio/WaveToFloat.cs` | Pure WASAPI buffer → float conversion: IEEE float 32-bit direct, PCM 16-bit normalized, `WaveFormatExtensible` IEEE-float subformat GUID, trailing partial samples ignored |
| `IGameAudioDetector.cs` | **TASK 4 game audio bar seam**: `IsGameAudioActive` + `IsGameAudioActiveChanged` + `Poll()` — detects "a working game with sound is up in the preview" |
| `GameAudioHysteresis.cs` | Pure show/hide state machine for the game bar: SHOW = full-screen app holds sound ~500ms; HIDE = the app leaves fullscreen ~1s. **Silence never hides an active bar** — only the game leaving the preview does (creator's rule). No timers; `Update(isFullScreen, hasSound, now)` |