fix(vu): drive needle from RMS-dB loudness instead of peak-of-bins
- Backend computes time-domain RMS, maps -60..-6 dB to 0..1, sends as `level` alongside the per-frame-normalized frequency bins. - Frontend prefers `level` directly; drops the peak-of-bins fallback and the redundant volume-slider attenuation (loopback capture is already post-volume on Windows/macOS). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -284,7 +284,18 @@ class AudioAnalyzer:
|
||||
counts = bin_ends - bin_starts
|
||||
bins = (cumsum[bin_ends] - cumsum[bin_starts]) / counts
|
||||
|
||||
# Normalize to 0-1
|
||||
# True loudness from time-domain RMS, mapped via dB
|
||||
# so the VU needle reflects actual program level — not
|
||||
# the per-frame-normalized spectrum.
|
||||
rms = float(np.sqrt(np.mean(mono.astype(np.float64) ** 2)))
|
||||
if rms > 1e-6:
|
||||
db = 20.0 * np.log10(rms)
|
||||
# Map -60 dB..-6 dB to 0..1 (typical music range)
|
||||
level = max(0.0, min(1.0, (db + 60.0) / 54.0))
|
||||
else:
|
||||
level = 0.0
|
||||
|
||||
# Normalize bins to 0-1 for spectrum display
|
||||
max_val = bins.max()
|
||||
if max_val > 0:
|
||||
bins *= (1.0 / max_val)
|
||||
@@ -295,9 +306,10 @@ class AudioAnalyzer:
|
||||
# Round for compact JSON
|
||||
frequencies = np.round(bins, 3).tolist()
|
||||
bass = round(bass, 3)
|
||||
level = round(level, 3)
|
||||
|
||||
with self._lock:
|
||||
self._data = {"frequencies": frequencies, "bass": bass}
|
||||
self._data = {"frequencies": frequencies, "bass": bass, "level": level}
|
||||
|
||||
# Throttle to target FPS
|
||||
elapsed = time.monotonic() - t0
|
||||
|
||||
Reference in New Issue
Block a user