Add real-time BPM detection and display to HUD and title
Implement real-time BPM (beats per minute) estimation in AudioAnalyzer by tracking beat intervals and smoothing the result. Display the current BPM in both the window title and the on-screen HUD. Update AudioFeatures to include a Bpm field. Adjust HUD layout to accommodate the new BPM information.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
@@ -32,6 +33,11 @@ public sealed class AudioAnalyzer : IDisposable
|
||||
private float _smoothedHigh;
|
||||
private float _smoothedBeat;
|
||||
private float _energyBaseline;
|
||||
private float _smoothedBpm;
|
||||
private float _lastBeatTimeSeconds = -10f;
|
||||
private bool _beatGateOpen = true;
|
||||
private readonly Queue<float> _beatIntervals = new();
|
||||
private const int MaxBeatIntervals = 8;
|
||||
|
||||
private Snapshot _latestSnapshot;
|
||||
private bool _disposed;
|
||||
@@ -217,6 +223,31 @@ public sealed class AudioAnalyzer : IDisposable
|
||||
_smoothedHigh = Smooth(_smoothedHigh, highEnergy, 0.30f, 0.08f);
|
||||
_smoothedBeat = MathF.Max(beat, _smoothedBeat * 0.85f);
|
||||
|
||||
float now = (float)_clock.Elapsed.TotalSeconds;
|
||||
bool beatHit = beat > 0.62f && _beatGateOpen;
|
||||
if (beatHit)
|
||||
{
|
||||
float delta = now - _lastBeatTimeSeconds;
|
||||
if (delta > 0.22f && delta < 2.0f)
|
||||
{
|
||||
_beatIntervals.Enqueue(delta);
|
||||
while (_beatIntervals.Count > MaxBeatIntervals)
|
||||
{
|
||||
_beatIntervals.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
_lastBeatTimeSeconds = now;
|
||||
_beatGateOpen = false;
|
||||
}
|
||||
else if (beat < 0.20f)
|
||||
{
|
||||
_beatGateOpen = true;
|
||||
}
|
||||
|
||||
float bpmInstant = CalculateBpm();
|
||||
_smoothedBpm = Smooth(_smoothedBpm, bpmInstant, 0.18f, 0.04f);
|
||||
|
||||
AudioFeatures features = new()
|
||||
{
|
||||
Level = _smoothedLevel,
|
||||
@@ -224,8 +255,9 @@ public sealed class AudioAnalyzer : IDisposable
|
||||
Mid = _smoothedMid,
|
||||
High = _smoothedHigh,
|
||||
Beat = _smoothedBeat,
|
||||
Bpm = _smoothedBpm,
|
||||
Spectrum = spectrum,
|
||||
TimeSeconds = (float)_clock.Elapsed.TotalSeconds
|
||||
TimeSeconds = now
|
||||
};
|
||||
|
||||
Volatile.Write(ref _latestSnapshot, new Snapshot(features));
|
||||
@@ -264,6 +296,29 @@ public sealed class AudioAnalyzer : IDisposable
|
||||
return a + ((b - a) * t);
|
||||
}
|
||||
|
||||
private float CalculateBpm()
|
||||
{
|
||||
if (_beatIntervals.Count == 0)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
float sum = 0f;
|
||||
foreach (float interval in _beatIntervals)
|
||||
{
|
||||
sum += interval;
|
||||
}
|
||||
|
||||
float avg = sum / _beatIntervals.Count;
|
||||
if (avg <= 1e-5f)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
float bpm = 60f / avg;
|
||||
return Math.Clamp(bpm, 40f, 220f);
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_disposed)
|
||||
|
||||
Reference in New Issue
Block a user