mirror of
https://github.com/DarthAffe/KeyboardAudioVisualizer.git
synced 2025-12-12 15:18:30 +00:00
262 lines
12 KiB
C#
262 lines
12 KiB
C#
using System;
|
|
using System.Linq;
|
|
using KeyboardAudioVisualizer.AudioProcessing.Spectrum;
|
|
using KeyboardAudioVisualizer.Configuration;
|
|
using KeyboardAudioVisualizer.Helper;
|
|
using RGB.NET.Core;
|
|
|
|
namespace KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider
|
|
{
|
|
#region Configuration
|
|
|
|
public class BeatVisualizationProviderConfiguration : AbstractConfiguration
|
|
{
|
|
//TODO DarthAffe 12.08.2017: Check if there is something usefull to configure here
|
|
}
|
|
|
|
#endregion
|
|
|
|
// Port of https://github.com/kctess5/Processing-Beat-Detection
|
|
public class BeatVisualizationProvider : AbstractAudioProcessor, IVisualizationProvider
|
|
{
|
|
#region Properties & Fields
|
|
|
|
private readonly BeatVisualizationProviderConfiguration _configuration;
|
|
private readonly ISpectrumProvider _specturProvider;
|
|
|
|
private int _beatBands = 30; //Number of bands to montiter, higher for more accuracy, lower for speed
|
|
private int _longTermAverageSamples = 60; //gets average volume over a period of time
|
|
private int _shortTermAverageSamples = 1; //average volume over a shorter "instantanious" time
|
|
private int _deltaArraySamples = 300; //number of energy deltas between long & short average to sum together
|
|
private int _beatAverageSamples = 100;
|
|
private int _beatCounterArraySamples = 400;
|
|
private int _maxTime = 200;
|
|
private float _predictiveInfluenceConstant = 0.1f;
|
|
private float _predictiveInfluence;
|
|
private int _cyclePerBeatIntensity;
|
|
private float[][] _deltaArray;
|
|
private float[][] _shortAverageArray;
|
|
private float[][] _longAverageArray;
|
|
private float[] _globalAverageArray;
|
|
private int[] _beatCounterArray;
|
|
private int[] _beatSpread;
|
|
private int _beatCounterPosition;
|
|
private int _cyclesPerBeat;
|
|
private int _longPosition;
|
|
private int _shortPosition;
|
|
private int _deltaPosition;
|
|
private int[] _count;
|
|
private float[] _totalLong;
|
|
private float[] _totalShort;
|
|
private float[] _delta;
|
|
private float[] _c;
|
|
private int _beat;
|
|
private int _beatCounter;
|
|
private float[] _beatAverage;
|
|
private float _totalBeat;
|
|
private int _beatPosition;
|
|
private float _totalGlobal;
|
|
private float _threshold;
|
|
private float _standardDeviation;
|
|
|
|
public IConfiguration Configuration => _configuration;
|
|
public float[] VisualizationData { get; } = new float[1];
|
|
|
|
public string DisplayName => "Beat";
|
|
public RGBDeviceType VisualizerFor => (RGBDeviceType)0xFF;
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
public BeatVisualizationProvider(BeatVisualizationProviderConfiguration configuration, ISpectrumProvider specturProvider)
|
|
{
|
|
this._configuration = configuration;
|
|
this._specturProvider = specturProvider;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
public override void Initialize()
|
|
{
|
|
_deltaArray = new float[_deltaArraySamples][];
|
|
for (int i = 0; i < _deltaArray.Length; i++)
|
|
_deltaArray[i] = new float[_beatBands];
|
|
|
|
_shortAverageArray = new float[_shortTermAverageSamples][];
|
|
for (int i = 0; i < _shortAverageArray.Length; i++)
|
|
_shortAverageArray[i] = new float[_beatBands];
|
|
|
|
_longAverageArray = new float[_longTermAverageSamples / _shortTermAverageSamples][];
|
|
for (int i = 0; i < _longAverageArray.Length; i++)
|
|
_longAverageArray[i] = new float[_beatBands];
|
|
|
|
_globalAverageArray = new float[_longTermAverageSamples];
|
|
_beatCounterArray = new int[_beatCounterArraySamples];
|
|
_beatSpread = new int[_maxTime];
|
|
_count = new int[_beatBands];
|
|
_totalLong = new float[_beatBands];
|
|
_totalShort = new float[_beatBands];
|
|
_delta = new float[_beatBands];
|
|
_c = new float[_beatBands]; //multiplier used to determain threshold
|
|
_beatAverage = new float[_beatAverageSamples];
|
|
}
|
|
|
|
public override void Update()
|
|
{
|
|
ISpectrum spectrum = _specturProvider.GetLogarithmicSpectrum(60, minFrequency: 60);
|
|
|
|
if (_shortPosition >= _shortTermAverageSamples) _shortPosition = 0; //Resets incremental variables
|
|
if (_longPosition >= (_longTermAverageSamples / _shortTermAverageSamples)) _longPosition = 0;
|
|
if (_deltaPosition >= _deltaArraySamples) _deltaPosition = 0;
|
|
if (_beatPosition >= _beatAverageSamples) _beatPosition = 0;
|
|
|
|
/////////////////////////////////////Calculate short and long term array averages///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
for (int i = 0; i < _beatBands; i++)
|
|
{
|
|
_shortAverageArray[_shortPosition][i] = spectrum[i].Average; //stores the average intensity between the freq. bounds to the short term array
|
|
_totalLong[i] = 0;
|
|
_totalShort[i] = 0;
|
|
for (int j = 0; j < (_longTermAverageSamples / _shortTermAverageSamples); j++)
|
|
_totalLong[i] += _longAverageArray[j][i]; //adds up all the values in both of these arrays, for averaging
|
|
for (int j = 0; j < _shortTermAverageSamples; j++)
|
|
_totalShort[i] += _shortAverageArray[j][i];
|
|
}
|
|
|
|
///////////////////////////////////////////Find wideband frequency average intensity/////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
_totalGlobal = 0;
|
|
_globalAverageArray[_longPosition] = spectrum[0, 2000].Average(x => x.Average);
|
|
for (int j = 0; j < _longTermAverageSamples; j++)
|
|
_totalGlobal += _globalAverageArray[j];
|
|
_totalGlobal = _totalGlobal / _longTermAverageSamples;
|
|
|
|
//////////////////////////////////Populate long term average array//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
if ((_shortPosition % _shortTermAverageSamples) == 0)
|
|
{ //every time the short array is completely new it is added to long array
|
|
for (int i = 0; i < _beatBands; i++)
|
|
_longAverageArray[_longPosition][i] = _totalShort[i]; //increases speed of program, but is the same as if each individual value was stored in long array
|
|
_longPosition++;
|
|
}
|
|
|
|
/////////////////////////////////////////Find index of variation for each band///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
for (int i = 0; i < _beatBands; i++)
|
|
{
|
|
_totalLong[i] = _totalLong[i] / (float)_longTermAverageSamples / (float)_shortTermAverageSamples;
|
|
_delta[i] = 0;
|
|
_deltaArray[_deltaPosition][i] = (float)Math.Pow(Math.Abs(_totalLong[i] - _totalShort[i]), 2);
|
|
|
|
for (int j = 0; j < _deltaArraySamples; j++)
|
|
_delta[i] += _deltaArray[j][i];
|
|
_delta[i] /= _deltaArraySamples;
|
|
|
|
///////////////////////////////////////////Find local beats/////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
_c[i] = (float)((1.3 + MathHelper.Clamp(Map(_delta[i], 0, 3000, 0, 0.4), 0, 0.4) //delta is usually bellow 2000
|
|
+ Map(MathHelper.Clamp(Math.Pow(_totalLong[i], 0.5), 0, 6), 0, 20, 0.3, 0) //possibly comment this out, adds weight to the lower end
|
|
+ Map(MathHelper.Clamp(_count[i], 0, 15), 0, 15, 1, 0))
|
|
- Map(MathHelper.Clamp(_count[i], 30, 200), 30, 200, 0, 0.75));
|
|
|
|
if ((_cyclePerBeatIntensity / _standardDeviation) > 3.5)
|
|
{
|
|
_predictiveInfluence = (float)(_predictiveInfluenceConstant * (1 - (Math.Cos(_beatCounter * (Math.PI * Math.PI)) / _cyclesPerBeat)));
|
|
_predictiveInfluence *= (float)Map(MathHelper.Clamp(_cyclePerBeatIntensity / _standardDeviation, 3.5, 20), 3.5, 15, 1, 6);
|
|
if (_cyclesPerBeat > 10)
|
|
_c[i] += _predictiveInfluence;
|
|
}
|
|
}
|
|
_beat = 0;
|
|
for (int i = 0; i < _beatBands; i++)
|
|
{
|
|
if ((_totalShort[i] > (_totalLong[i] * _c[i])) & (_count[i] > 7))
|
|
{ //If beat is detected
|
|
if ((_count[i] > 12) & (_count[i] < 200))
|
|
{
|
|
_beatCounterArray[_beatCounterPosition % _beatCounterArraySamples] = _count[i];
|
|
_beatCounterPosition++;
|
|
}
|
|
_count[i] = 0; //resets counter
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////Figure out # of beats, and average///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
for (int i = 0; i < _beatBands; i++)
|
|
if (_count[i] < 2)
|
|
_beat++; //If there has been a recent beat in a band add to the global beat value
|
|
_beatAverage[_beatPosition] = _beat;
|
|
|
|
for (int j = 0; j < _beatAverageSamples; j++)
|
|
_totalBeat += _beatAverage[j];
|
|
_totalBeat = _totalBeat / _beatAverageSamples;
|
|
|
|
/////////////////////////////////////////////////find global beat///////////////////////////////////////////////////////////////////////////////////////////////
|
|
_c[0] = (float)(3.25 + Map(MathHelper.Clamp(_beatCounter, 0, 5), 0, 5, 5, 0));
|
|
if (_cyclesPerBeat > 10)
|
|
_c[0] = (float)(_c[0] + (0.75 * (1 - (Math.Cos(_beatCounter * (Math.PI * Math.PI)) / _cyclesPerBeat))));
|
|
|
|
_threshold = (float)MathHelper.Clamp((_c[0] * _totalBeat) + Map(MathHelper.Clamp(_totalGlobal, 0, 2), 0, 2, 4, 0), 5, 1000);
|
|
|
|
if ((_beat > _threshold) & (_beatCounter > 5))
|
|
{
|
|
VisualizationData[0] = 1;
|
|
_beatCounter = 0;
|
|
}
|
|
else
|
|
VisualizationData[0] = 0;
|
|
|
|
/////////////////////////////////////////////////////Calculate beat spreads///////////////////////////////////////////////////////////////////////////////////////////
|
|
//average = beatCounterArraySamples/200 !!!
|
|
for (int i = 0; i < _maxTime; i++)
|
|
_beatSpread[i] = 0;
|
|
|
|
for (int i = 0; i < _beatCounterArraySamples; i++)
|
|
_beatSpread[_beatCounterArray[i]]++;
|
|
|
|
_cyclesPerBeat = Mode(_beatCounterArray);
|
|
if (_cyclesPerBeat < 20)
|
|
_cyclesPerBeat *= 2;
|
|
|
|
_cyclePerBeatIntensity = _beatSpread.Max();
|
|
_standardDeviation = 0;
|
|
|
|
for (int i = 0; i < _maxTime; i++)
|
|
_standardDeviation += (float)Math.Pow((_beatCounterArraySamples / _maxTime) - _beatSpread[i], 2);
|
|
|
|
_standardDeviation = (float)Math.Pow(_standardDeviation / _maxTime, 0.5);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
_shortPosition++;
|
|
_deltaPosition++;
|
|
for (int i = 0; i < _beatBands; i++) _count[i]++;
|
|
_beatCounter++;
|
|
_beatPosition++;
|
|
}
|
|
|
|
private int Mode(int[] array)
|
|
{
|
|
int[] modeMap = new int[array.Length];
|
|
int maxEl = array[0];
|
|
int maxCount = 1;
|
|
for (int i = 0; i < array.Length; i++)
|
|
{
|
|
int el = array[i];
|
|
if (modeMap[el] == 0)
|
|
modeMap[el] = 1;
|
|
else
|
|
modeMap[el]++;
|
|
|
|
if (modeMap[el] > maxCount)
|
|
{
|
|
maxEl = el;
|
|
maxCount = modeMap[el];
|
|
}
|
|
}
|
|
return maxEl;
|
|
}
|
|
|
|
private static double Map(double value, double oldMin, double oldMax, double newMin, double newMax) => newMin + ((newMax - newMin) * ((value - oldMin) / (oldMax - oldMin)));
|
|
|
|
#endregion
|
|
}
|
|
}
|