feat: true RMS dBFS level measurement for LevelMeterFab via getFloatTimeDomainData
This commit is contained in:
@@ -11,12 +11,12 @@ public partial class LevelMeterFab : ComponentBase, IAsyncDisposable
|
||||
|
||||
[Parameter] public EventCallback OnClick { get; set; }
|
||||
|
||||
// Calibration window (PLAN-level-meter-fill.md §2). These four constants are the
|
||||
// spec contract: floor/ceiling define the dB window, and the linear map places
|
||||
// -12 dB at 60% fill (green/yellow) and -4.5 dB at 85% (yellow/orange) to match
|
||||
// the SVG gradient stops. The attack/release coefficients are by-ear tuning values.
|
||||
private const double FloorDb = -70.0; // fill = 0%; lowered to match FFT peak values (AnalyserNode.getFloatFrequencyData returns peaks, not RMS — typical loud dance tracks cluster ~-40 to -60 dB)
|
||||
private const double CeilingDb = -10.0; // fill = 100%; lowered to match FFT peak ceiling (loud dance FFT peaks land around -10 dB, far below 0 dB RMS full-scale)
|
||||
// Calibration window for true RMS dBFS (AnalyserNode.getFloatTimeDomainData).
|
||||
// Floor/ceiling define the dB window; the linear map across this 57 dB range places
|
||||
// a steady dance track (~-14 dBFS) at ~81% fill (upper yellow) and a hot drop
|
||||
// (~-6 dBFS) at ~95% (deep orange). Attack/release coefficients are by-ear tuning values.
|
||||
private const double FloorDb = -60.0; // fill = 0%; below this is near-silence (true RMS dBFS)
|
||||
private const double CeilingDb = -3.0; // fill = 100%; hot peaks on commercial masters (true RMS dBFS)
|
||||
private const double SilenceFloorDb = -80.0; // matches the analyzer's normalization window
|
||||
|
||||
private const double AttackCoefficient = 0.5; // fast rise toward a louder reading
|
||||
@@ -91,7 +91,7 @@ public partial class LevelMeterFab : ComponentBase, IAsyncDisposable
|
||||
_isAnimating = true;
|
||||
_smoothedDb = SilenceFloorDb;
|
||||
_fillPercent = 0;
|
||||
await AudioInterop.StartSpectrumAnimationAsync(_playerId, _instanceId, OnLevelData);
|
||||
await AudioInterop.StartLevelAnimationAsync(_playerId, _instanceId, OnLevelData);
|
||||
}
|
||||
|
||||
private async Task StopAnimation()
|
||||
@@ -99,7 +99,7 @@ public partial class LevelMeterFab : ComponentBase, IAsyncDisposable
|
||||
if (!_isAnimating || string.IsNullOrEmpty(_playerId)) return;
|
||||
|
||||
_isAnimating = false;
|
||||
await AudioInterop.StopSpectrumAnimationAsync(_playerId, _instanceId);
|
||||
await AudioInterop.StopLevelAnimationAsync(_playerId, _instanceId);
|
||||
|
||||
// Drop the column to empty so only the dim silhouette remains.
|
||||
if (_fillPercent != 0)
|
||||
@@ -110,23 +110,16 @@ public partial class LevelMeterFab : ComponentBase, IAsyncDisposable
|
||||
_smoothedDb = SilenceFloorDb;
|
||||
}
|
||||
|
||||
private Task OnLevelData(double[] buckets)
|
||||
private Task OnLevelData(double db)
|
||||
{
|
||||
if (buckets.Length == 0) return Task.CompletedTask;
|
||||
|
||||
// Peak reads more responsively than mean for a "hot signal" indicator.
|
||||
var peak = 0.0;
|
||||
foreach (var bucket in buckets)
|
||||
{
|
||||
if (bucket > peak) peak = bucket;
|
||||
}
|
||||
|
||||
// Inverse of SpectrumAnalyzer's normalization: value = (clampedDb + 80) / 80.
|
||||
var instantDb = peak * 80.0 - 80.0;
|
||||
// db is true RMS dBFS from getFloatTimeDomainData; -Infinity on silence.
|
||||
var instantDb = double.IsNegativeInfinity(db) || double.IsNaN(db)
|
||||
? SilenceFloorDb
|
||||
: Math.Max(db, SilenceFloorDb);
|
||||
|
||||
// Attack-fast / release-slow envelope so the column doesn't strobe at 30fps.
|
||||
var coefficient = instantDb > _smoothedDb ? AttackCoefficient : ReleaseCoefficient;
|
||||
_smoothedDb += (instantDb - _smoothedDb) * coefficient;
|
||||
var coeff = instantDb > _smoothedDb ? AttackCoefficient : ReleaseCoefficient;
|
||||
_smoothedDb += (instantDb - _smoothedDb) * coeff;
|
||||
|
||||
// Linear map of smoothed dB onto a 0-100 fill across the [floor, ceiling] window.
|
||||
var next = Math.Clamp((_smoothedDb - FloorDb) / (CeilingDb - FloorDb) * 100.0, 0.0, 100.0);
|
||||
|
||||
Reference in New Issue
Block a user