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)
|
||||
|
||||
@@ -166,7 +166,7 @@ public class Game1 : Game
|
||||
_fpsElapsed -= TimeSpan.FromSeconds(1);
|
||||
}
|
||||
|
||||
Window.Title = $"Fractal Visualizer | {GetPresetName(_fractalPresetIndex)} | {_renderQuality} | Mode: {_currentInputMode} | FPS:{_fps}";
|
||||
Window.Title = $"Fractal Visualizer | {GetPresetName(_fractalPresetIndex)} | {_renderQuality} | Mode: {_currentInputMode} | FPS:{_fps} | BPM:{_latestFeatures.Bpm:F1}";
|
||||
_previousKeyboardState = keyboard;
|
||||
base.Update(gameTime);
|
||||
}
|
||||
@@ -193,10 +193,11 @@ public class Game1 : Game
|
||||
_hudFont.DrawText(_spriteBatch, _pixel, "MID " + _latestFeatures.Mid.ToString("0.000"), 12, 90, 2, Color.White);
|
||||
_hudFont.DrawText(_spriteBatch, _pixel, "HIGH " + _latestFeatures.High.ToString("0.000"), 12, 110, 2, Color.White);
|
||||
_hudFont.DrawText(_spriteBatch, _pixel, "BEAT " + _latestFeatures.Beat.ToString("0.000"), 12, 130, 2, Color.White);
|
||||
_hudFont.DrawText(_spriteBatch, _pixel, "PAUSED " + (_isPaused ? "YES" : "NO"), 12, 150, 2, Color.White);
|
||||
_hudFont.DrawText(_spriteBatch, _pixel, "FPS " + _fps, 12, 170, 2, Color.White);
|
||||
_hudFont.DrawText(_spriteBatch, _pixel, "QUALITY " + _renderQuality + " 1 2 3", 12, 190, 2, Color.White);
|
||||
_hudFont.DrawText(_spriteBatch, _pixel, "F OR F11 FULLSCREEN R RESIZE", 12, 210, 2, Color.White);
|
||||
_hudFont.DrawText(_spriteBatch, _pixel, "BPM " + _latestFeatures.Bpm.ToString("0.0"), 12, 150, 2, Color.White);
|
||||
_hudFont.DrawText(_spriteBatch, _pixel, "PAUSED " + (_isPaused ? "YES" : "NO"), 12, 170, 2, Color.White);
|
||||
_hudFont.DrawText(_spriteBatch, _pixel, "FPS " + _fps, 12, 190, 2, Color.White);
|
||||
_hudFont.DrawText(_spriteBatch, _pixel, "QUALITY " + _renderQuality + " 1 2 3", 12, 210, 2, Color.White);
|
||||
_hudFont.DrawText(_spriteBatch, _pixel, "F OR F11 FULLSCREEN R RESIZE", 12, 230, 2, Color.White);
|
||||
_spriteBatch.End();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ public struct AudioFeatures
|
||||
public float Mid;
|
||||
public float High;
|
||||
public float Beat;
|
||||
public float Bpm;
|
||||
public float[] Spectrum;
|
||||
public float TimeSeconds;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user