mirror of
https://github.com/DarthAffe/KeyboardAudioVisualizer.git
synced 2025-12-12 15:18:30 +00:00
Refactored audio processing
This commit is contained in:
parent
fc3db046c3
commit
260be351ae
@ -44,9 +44,8 @@ namespace KeyboardAudioVisualizer
|
||||
public void InitializeDevices()
|
||||
{
|
||||
RGBSurface surface = RGBSurface.Instance;
|
||||
//surface.Exception += args =>;
|
||||
|
||||
surface.UpdateFrequency = 1 / 30.0; //TODO DarthAffe 03.08.2017: Settings
|
||||
surface.UpdateFrequency = 1 / 40.0; //TODO DarthAffe 03.08.2017: Settings
|
||||
surface.UpdateMode = UpdateMode.Continuous;
|
||||
|
||||
surface.LoadDevices(CorsairDeviceProvider.Instance);
|
||||
@ -54,7 +53,7 @@ namespace KeyboardAudioVisualizer
|
||||
//surface.LoadDevices(CoolerMasterDeviceProvider.Instance);
|
||||
|
||||
ILedGroup background = new ListLedGroup(surface.Leds);
|
||||
background.Brush = new SolidColorBrush(new Color(96, 0, 0, 0)); //TODO DarthAffe 06.08.2017: A-Channel gives some kind of blur - settings!
|
||||
background.Brush = new SolidColorBrush(new Color(64, 0, 0, 0)); //TODO DarthAffe 06.08.2017: A-Channel gives some kind of blur - settings!
|
||||
|
||||
//TODO DarthAffe 03.08.2017: Changeable, Settings etc.
|
||||
foreach (IRGBDevice device in surface.Devices)
|
||||
@ -89,6 +88,7 @@ namespace KeyboardAudioVisualizer
|
||||
}
|
||||
else
|
||||
new ListLedGroup(device).Brush = new FrequencyBarsBrush(AudioProcessor.Instance.PrimaryVisualizationProvider, new RainbowGradient(300, -14));
|
||||
//new ListLedGroup(device).Brush = new BeatBrush(AudioProcessor.Instance.PrimaryVisualizationProvider, new Color(255, 255, 255));
|
||||
|
||||
//{
|
||||
// ILedGroup left = new RectangleLedGroup(new Rectangle(device.Location.X, device.Location.Y, device.Size.Width / 2.0, device.Size.Height));
|
||||
|
||||
@ -31,7 +31,10 @@ namespace KeyboardAudioVisualizer.AudioCapture
|
||||
_capture = new WasapiLoopbackCapture();
|
||||
_capture.Initialize();
|
||||
_soundInSource = new SoundInSource(_capture) { FillWithZeros = false };
|
||||
_stream = new SingleBlockNotificationStream(_soundInSource.ToStereo().ToSampleSource());
|
||||
|
||||
_stream = _soundInSource.WaveFormat.SampleRate == 44100
|
||||
? new SingleBlockNotificationStream(_soundInSource.ToStereo().ToSampleSource())
|
||||
: new SingleBlockNotificationStream(_soundInSource.ChangeSampleRate(44100).ToStereo().ToSampleSource());
|
||||
|
||||
_soundInSource.DataAvailable += OnSoundDataAvailable;
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using KeyboardAudioVisualizer.AudioCapture;
|
||||
using KeyboardAudioVisualizer.AudioProcessing.BeatDetection;
|
||||
using KeyboardAudioVisualizer.AudioProcessing.Equalizer;
|
||||
using KeyboardAudioVisualizer.AudioProcessing.Spectrum;
|
||||
using KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider;
|
||||
@ -21,6 +22,8 @@ namespace KeyboardAudioVisualizer.AudioProcessing
|
||||
private AudioBuffer _audioBuffer;
|
||||
private IAudioInput _audioInput;
|
||||
private ISpectrumProvider _spectrumProvider;
|
||||
private OnsetDetector _onsetDetector;
|
||||
|
||||
public IVisualizationProvider PrimaryVisualizationProvider { get; private set; }
|
||||
public IVisualizationProvider SecondaryVisualizationProvider { get; private set; }
|
||||
|
||||
@ -37,6 +40,7 @@ namespace KeyboardAudioVisualizer.AudioProcessing
|
||||
public void Update()
|
||||
{
|
||||
_spectrumProvider.Update();
|
||||
_onsetDetector.Update();
|
||||
PrimaryVisualizationProvider.Update();
|
||||
SecondaryVisualizationProvider.Update();
|
||||
}
|
||||
@ -54,30 +58,25 @@ namespace KeyboardAudioVisualizer.AudioProcessing
|
||||
_audioInput = new CSCoreAudioInput();
|
||||
_audioInput.Initialize();
|
||||
|
||||
_audioBuffer = new AudioBuffer(CalculateSampleSize(_audioInput.SampleRate, MAXIMUM_UPDATE_RATE));
|
||||
_audioBuffer = new AudioBuffer(4096); // Working with ~93ms -
|
||||
_audioInput.DataAvailable += (data, offset, count) => _audioBuffer.Put(data, offset, count);
|
||||
|
||||
_spectrumProvider = new FourierSpectrumProvider(_audioBuffer, _audioInput.SampleRate);
|
||||
_spectrumProvider = new FourierSpectrumProvider(_audioBuffer);
|
||||
_spectrumProvider.Initialize();
|
||||
|
||||
_onsetDetector = new OnsetDetector(_audioBuffer);
|
||||
_onsetDetector.Initialize();
|
||||
|
||||
//TODO DarthAffe 03.08.2017: Initialize correctly; Settings
|
||||
MultiBandEqualizer equalizer = new MultiBandEqualizer { [0] = -3, [1] = -1, [2] = -1, [3] = 1, [4] = 3 };
|
||||
PrimaryVisualizationProvider = new FrequencyBarsVisualizationProvider(new FrequencyBarsVisualizationProviderConfiguration { Scale = 34 }, _spectrumProvider) { Equalizer = equalizer };
|
||||
MultiBandEqualizer equalizer = new MultiBandEqualizer { [0] = -3, [1] = -1, [2] = 1, [3] = 2, [4] = 3 };
|
||||
PrimaryVisualizationProvider = new FrequencyBarsVisualizationProvider(new FrequencyBarsVisualizationProviderConfiguration(), _spectrumProvider) { Equalizer = equalizer };
|
||||
//PrimaryVisualizationProvider = new BeatVisualizationProvider(new BeatVisualizationProviderConfiguration(), _spectrumProvider);
|
||||
PrimaryVisualizationProvider.Initialize();
|
||||
|
||||
SecondaryVisualizationProvider = new LevelVisualizationProvider(new LevelVisualizationProviderConfiguration(), _audioBuffer);
|
||||
SecondaryVisualizationProvider.Initialize();
|
||||
}
|
||||
|
||||
private int CalculateSampleSize(int sampleRate, int maximumUpdateRate)
|
||||
{
|
||||
int sampleSize = 2;
|
||||
while ((sampleSize * maximumUpdateRate) < sampleRate)
|
||||
sampleSize <<= 1;
|
||||
|
||||
return sampleSize;
|
||||
}
|
||||
|
||||
public void Dispose() => _audioInput.Dispose();
|
||||
|
||||
#endregion
|
||||
|
||||
@ -0,0 +1,453 @@
|
||||
using System;
|
||||
using MathNet.Numerics;
|
||||
using MathNet.Numerics.IntegralTransforms;
|
||||
|
||||
// Based on https://github.com/adamstark/BTrack
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.BeatDetection
|
||||
{
|
||||
public class BeatDetectorBtrack : IAudioProcessor
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly OnsetDetector _onsetDetector;
|
||||
|
||||
private double _tightness;
|
||||
private double _alpha;
|
||||
private double _beatPeriod;
|
||||
private double _tempo;
|
||||
private double _estimatedTempo;
|
||||
private double _latestCumulativeScoreValue;
|
||||
private double _tempoToLagFactor;
|
||||
private int _m0;
|
||||
private int _beatCounter;
|
||||
private int _hopSize;
|
||||
private int _onsetDFBufferSize;
|
||||
private bool _tempoFixed;
|
||||
private bool _beatDueInFrame;
|
||||
private int _fftLengthForAcfCalculation;
|
||||
|
||||
private CircularBuffer _onsetDf;
|
||||
private CircularBuffer _cumulativeScore;
|
||||
private readonly double[] _resampledOnsetDf = new double[512];
|
||||
private readonly double[] _acf = new double[512];
|
||||
private readonly double[] _weightingVector = new double[128];
|
||||
private readonly double[] _combFilterBankOutput = new double[128];
|
||||
private readonly double[] _tempoObservationVector = new double[41];
|
||||
private readonly double[] _delta = new double[41];
|
||||
private readonly double[] _prevDelta = new double[41];
|
||||
private readonly double[] _prevDeltaFixed = new double[41];
|
||||
private readonly double[][] _tempoTransitionMatrix = new double[41][];
|
||||
private Complex32[] _complexBuffer;
|
||||
|
||||
public bool IsBeat => _beatDueInFrame;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public BeatDetectorBtrack(OnsetDetector onsetDetector)
|
||||
{
|
||||
this._onsetDetector = onsetDetector;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_tightness = 5;
|
||||
_alpha = 0.9;
|
||||
_tempo = 120;
|
||||
_estimatedTempo = 120.0;
|
||||
_tempoToLagFactor = (60.0 * _onsetDetector.SampleRate) / 512.0;
|
||||
_m0 = 10;
|
||||
_beatCounter = -1;
|
||||
_beatDueInFrame = false;
|
||||
_fftLengthForAcfCalculation = 1024;
|
||||
|
||||
const double RAYPARAM = 43;
|
||||
for (int n = 0; n < 128; n++)
|
||||
_weightingVector[n] = ((double)n / Math.Pow(RAYPARAM, 2)) * Math.Exp((-1 * Math.Pow(-n, 2)) / (2 * Math.Pow(RAYPARAM, 2)));
|
||||
|
||||
for (int i = 0; i < 41; i++)
|
||||
_prevDelta[i] = 1;
|
||||
|
||||
const double M_SIG = (int)(41 / 8.0);
|
||||
for (int i = 0; i < 41; i++)
|
||||
{
|
||||
_tempoTransitionMatrix[i] = new double[41];
|
||||
for (int j = 0; j < 41; j++)
|
||||
{
|
||||
double x = j + 1;
|
||||
_tempoTransitionMatrix[i][j] = (1 / (M_SIG * Math.Sqrt(2 * Math.PI))) * Math.Exp((-1 * Math.Pow((x - (i + 1)), 2)) / (2 * Math.Pow(M_SIG, 2)));
|
||||
}
|
||||
}
|
||||
_tempoFixed = false;
|
||||
_latestCumulativeScoreValue = 0;
|
||||
|
||||
_complexBuffer = new Complex32[_fftLengthForAcfCalculation];
|
||||
|
||||
_onsetDf = new CircularBuffer();
|
||||
_cumulativeScore = new CircularBuffer();
|
||||
|
||||
SetHopSize(_onsetDetector.HopSize);
|
||||
}
|
||||
|
||||
private void SetHopSize(int hopSize)
|
||||
{
|
||||
_hopSize = hopSize;
|
||||
_onsetDFBufferSize = (512 * 512) / hopSize;
|
||||
_beatPeriod = Math.Round(60 / ((((double)hopSize) / _onsetDetector.SampleRate) * _tempo));
|
||||
_onsetDf.Resize(_onsetDFBufferSize);
|
||||
_cumulativeScore.Resize(_onsetDFBufferSize);
|
||||
for (int i = 0; i < _onsetDFBufferSize; i++)
|
||||
{
|
||||
_onsetDf[i] = 0;
|
||||
_cumulativeScore[i] = 0;
|
||||
if ((i % ((int)Math.Round(_beatPeriod))) == 0)
|
||||
_onsetDf[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
ProcessOnsetDetectionFunctionSample(_onsetDetector.Onset);
|
||||
}
|
||||
|
||||
private void ProcessOnsetDetectionFunctionSample(double newSample)
|
||||
{
|
||||
newSample = Math.Abs(newSample);
|
||||
newSample = newSample + 0.0001;
|
||||
|
||||
_m0--;
|
||||
_beatCounter--;
|
||||
_beatDueInFrame = false;
|
||||
|
||||
_onsetDf.Add(newSample);
|
||||
UpdateCumulativeScore(newSample);
|
||||
|
||||
if (_m0 == 0)
|
||||
PredictBeat();
|
||||
|
||||
if (_beatCounter == 0)
|
||||
{
|
||||
_beatDueInFrame = true;
|
||||
ResampleOnsetDetectionFunction();
|
||||
CalculateTempo();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResampleOnsetDetectionFunction()
|
||||
{
|
||||
for (int i = 0; i < _onsetDFBufferSize; i++)
|
||||
_resampledOnsetDf[i] = _onsetDf[i];
|
||||
|
||||
//float[] output = new float[512];
|
||||
//float[] input = new float[_onsetDFBufferSize];
|
||||
|
||||
//for (int i = 0; i < _onsetDFBufferSize; i++)
|
||||
//{
|
||||
// input[i] = (float)_onsetDf[i];
|
||||
//}
|
||||
//double src_ratio = 512.0 / ((double)_onsetDFBufferSize);
|
||||
//int BUFFER_LEN = _onsetDFBufferSize;
|
||||
//int output_len;
|
||||
//SRC_DATA src_data;
|
||||
////output_len = (int) floor (((double) BUFFER_LEN) * src_ratio) ;
|
||||
//output_len = 512;
|
||||
//src_data.data_in = input;
|
||||
//src_data.input_frames = BUFFER_LEN;
|
||||
//src_data.src_ratio = src_ratio;
|
||||
//src_data.data_out = output;
|
||||
//src_data.output_frames = output_len;
|
||||
//src_simple(&src_data, SRC_SINC_BEST_QUALITY, 1);
|
||||
|
||||
//for (int i = 0; i < output_len; i++)
|
||||
// _resampledOnsetDf[i] = (double)src_data.data_out[i];
|
||||
}
|
||||
|
||||
private void CalculateTempo()
|
||||
{
|
||||
AdaptiveThreshold(_resampledOnsetDf);
|
||||
CalculateBalancedAcf(_resampledOnsetDf);
|
||||
CalculateOutputOfCombFilterBank();
|
||||
AdaptiveThreshold(_combFilterBankOutput);
|
||||
|
||||
int t_index;
|
||||
int t_index2;
|
||||
// calculate tempo observation vector from beat period observation vector
|
||||
for (int i = 0; i < 41; i++)
|
||||
{
|
||||
t_index = (int)Math.Round(_tempoToLagFactor / ((double)((2 * i) + 80)));
|
||||
t_index2 = (int)Math.Round(_tempoToLagFactor / ((double)((4 * i) + 160)));
|
||||
_tempoObservationVector[i] = _combFilterBankOutput[t_index - 1] + _combFilterBankOutput[t_index2 - 1];
|
||||
}
|
||||
double maxval;
|
||||
double maxind;
|
||||
double curval;
|
||||
// if tempo is fixed then always use a fixed set of tempi as the previous observation probability function
|
||||
if (_tempoFixed)
|
||||
{
|
||||
for (int k = 0; k < 41; k++)
|
||||
{
|
||||
_prevDelta[k] = _prevDeltaFixed[k];
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < 41; j++)
|
||||
{
|
||||
maxval = -1;
|
||||
for (int i = 0; i < 41; i++)
|
||||
{
|
||||
curval = _prevDelta[i] * _tempoTransitionMatrix[i][j];
|
||||
if (curval > maxval)
|
||||
{
|
||||
maxval = curval;
|
||||
}
|
||||
}
|
||||
_delta[j] = maxval * _tempoObservationVector[j];
|
||||
}
|
||||
NormalizeArray(_delta, 41);
|
||||
maxind = -1;
|
||||
maxval = -1;
|
||||
for (int j = 0; j < 41; j++)
|
||||
{
|
||||
if (_delta[j] > maxval)
|
||||
{
|
||||
maxval = _delta[j];
|
||||
maxind = j;
|
||||
}
|
||||
_prevDelta[j] = _delta[j];
|
||||
}
|
||||
_beatPeriod = Math.Round((60.0 * 44100.0) / (((2 * maxind) + 80) * ((double)_hopSize)));
|
||||
if (_beatPeriod > 0)
|
||||
{
|
||||
_estimatedTempo = 60.0 / ((((double)_hopSize) / 44100.0) * _beatPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
private void AdaptiveThreshold(double[] x)
|
||||
{
|
||||
int n = x.Length;
|
||||
int i = 0;
|
||||
int k, t = 0;
|
||||
double[] xThresh = new double[n];
|
||||
int p_post = 7;
|
||||
int p_pre = 8;
|
||||
t = Math.Min(x.Length, p_post); // what is smaller, p_post of df size. This is to avoid accessing outside of arrays
|
||||
// find threshold for first 't' samples, where a full average cannot be computed yet
|
||||
for (i = 0; i <= t; i++)
|
||||
{
|
||||
k = Math.Min((i + p_pre), n);
|
||||
xThresh[i] = CalculateMeanOfArray(x, 1, k);
|
||||
}
|
||||
// find threshold for bulk of samples across a moving average from [i-p_pre,i+p_post]
|
||||
for (i = t + 1; i < n - p_post; i++)
|
||||
{
|
||||
xThresh[i] = CalculateMeanOfArray(x, i - p_pre, i + p_post);
|
||||
}
|
||||
// for last few samples calculate threshold, again, not enough samples to do as above
|
||||
for (i = n - p_post; i < n; i++)
|
||||
{
|
||||
k = Math.Max((i - p_post), 1);
|
||||
xThresh[i] = CalculateMeanOfArray(x, k, n);
|
||||
}
|
||||
// subtract the threshold from the detection function and check that it is not less than 0
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
x[i] = x[i] - xThresh[i];
|
||||
if (x[i] < 0)
|
||||
{
|
||||
x[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateOutputOfCombFilterBank()
|
||||
{
|
||||
int numelem;
|
||||
for (int i = 0; i < 128; i++)
|
||||
_combFilterBankOutput[i] = 0;
|
||||
|
||||
numelem = 4;
|
||||
for (int i = 2; i <= 127; i++) // max beat period
|
||||
{
|
||||
for (int a = 1; a <= numelem; a++) // number of comb elements
|
||||
{
|
||||
for (int b = 1 - a; b <= a - 1; b++) // general state using normalisation of comb elements
|
||||
{
|
||||
_combFilterBankOutput[i - 1] = _combFilterBankOutput[i - 1] + (_acf[(a * i + b) - 1] * _weightingVector[i - 1]) / (2 * a - 1); // calculate value for comb filter row
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateBalancedAcf(double[] onsetDetectionFunction)
|
||||
{
|
||||
int onsetDetectionFunctionLength = 512;
|
||||
for (int i = 0; i < _fftLengthForAcfCalculation; i++)
|
||||
{
|
||||
if (i < onsetDetectionFunctionLength)
|
||||
_complexBuffer[i] = new Complex32((float)onsetDetectionFunction[i], 0);
|
||||
else
|
||||
_complexBuffer[i] = new Complex32(0, 0);
|
||||
}
|
||||
|
||||
Fourier.Forward(_complexBuffer);
|
||||
|
||||
for (int i = 0; i < _fftLengthForAcfCalculation; i++)
|
||||
_complexBuffer[i] = new Complex32((_complexBuffer[i].Real * _complexBuffer[i].Real) + (_complexBuffer[i].Imaginary * _complexBuffer[i].Imaginary), 0);
|
||||
|
||||
Fourier.Inverse(_complexBuffer);
|
||||
|
||||
double lag = 512;
|
||||
for (int i = 0; i < 512; i++)
|
||||
{
|
||||
double absValue = Math.Sqrt((_complexBuffer[i].Real * _complexBuffer[i].Real) + (_complexBuffer[i].Imaginary * _complexBuffer[i].Imaginary));
|
||||
|
||||
_acf[i] = absValue / lag;
|
||||
_acf[i] /= 1024.0;
|
||||
lag = lag - 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
private double CalculateMeanOfArray(double[] array, int startIndex, int endIndex)
|
||||
{
|
||||
int i;
|
||||
double sum = 0;
|
||||
int length = endIndex - startIndex;
|
||||
// find sum
|
||||
for (i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
sum = sum + array[i];
|
||||
}
|
||||
if (length > 0)
|
||||
{
|
||||
return sum / length; // average and return
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void NormalizeArray(double[] array, int n)
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
if (array[i] > 0)
|
||||
{
|
||||
sum = sum + array[i];
|
||||
}
|
||||
}
|
||||
if (sum > 0)
|
||||
{
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
array[i] = array[i] / sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCumulativeScore(double odfSample)
|
||||
{
|
||||
int start, end, winsize;
|
||||
double max;
|
||||
start = _onsetDFBufferSize - (int)Math.Round(2 * _beatPeriod);
|
||||
end = _onsetDFBufferSize - (int)Math.Round(_beatPeriod / 2);
|
||||
winsize = end - start + 1;
|
||||
double[] w1 = new double[winsize];
|
||||
double v = -2 * _beatPeriod;
|
||||
double wcumscore;
|
||||
// create window
|
||||
for (int i = 0; i < winsize; i++)
|
||||
{
|
||||
w1[i] = Math.Exp((-1 * Math.Pow(_tightness * Math.Log(-v / _beatPeriod), 2)) / 2);
|
||||
v = v + 1;
|
||||
}
|
||||
// calculate new cumulative score value
|
||||
max = 0;
|
||||
int n = 0;
|
||||
for (int i = start; i <= end; i++)
|
||||
{
|
||||
wcumscore = _cumulativeScore[i] * w1[n];
|
||||
if (wcumscore > max)
|
||||
{
|
||||
max = wcumscore;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
_latestCumulativeScoreValue = ((1 - _alpha) * odfSample) + (_alpha * max);
|
||||
_cumulativeScore.Add(_latestCumulativeScoreValue);
|
||||
}
|
||||
|
||||
private void PredictBeat()
|
||||
{
|
||||
int windowSize = (int)_beatPeriod;
|
||||
double[] futureCumulativeScore = new double[_onsetDFBufferSize + windowSize];
|
||||
double[] w2 = new double[windowSize];
|
||||
// copy cumscore to first part of fcumscore
|
||||
for (int i = 0; i < _onsetDFBufferSize; i++)
|
||||
{
|
||||
futureCumulativeScore[i] = _cumulativeScore[i];
|
||||
}
|
||||
// create future window
|
||||
double v = 1;
|
||||
for (int i = 0; i < windowSize; i++)
|
||||
{
|
||||
w2[i] = Math.Exp((-1 * Math.Pow((v - (_beatPeriod / 2)), 2)) / (2 * Math.Pow((_beatPeriod / 2), 2)));
|
||||
v++;
|
||||
}
|
||||
// create past window
|
||||
v = -2 * _beatPeriod;
|
||||
int start = _onsetDFBufferSize - (int)Math.Round(2 * _beatPeriod);
|
||||
int end = _onsetDFBufferSize - (int)Math.Round(_beatPeriod / 2);
|
||||
int pastwinsize = end - start + 1;
|
||||
double[] w1 = new double[pastwinsize];
|
||||
for (int i = 0; i < pastwinsize; i++)
|
||||
{
|
||||
w1[i] = Math.Exp((-1 * Math.Pow(_tightness * Math.Log(-v / _beatPeriod), 2)) / 2);
|
||||
v = v + 1;
|
||||
}
|
||||
// calculate future cumulative score
|
||||
double max;
|
||||
int n;
|
||||
double wcumscore;
|
||||
for (int i = _onsetDFBufferSize; i < (_onsetDFBufferSize + windowSize); i++)
|
||||
{
|
||||
start = i - (int)Math.Round(2 * _beatPeriod);
|
||||
end = i - (int)Math.Round(_beatPeriod / 2);
|
||||
max = 0;
|
||||
n = 0;
|
||||
for (int k = start; k <= end; k++)
|
||||
{
|
||||
wcumscore = futureCumulativeScore[k] * w1[n];
|
||||
if (wcumscore > max)
|
||||
{
|
||||
max = wcumscore;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
futureCumulativeScore[i] = max;
|
||||
}
|
||||
// predict beat
|
||||
max = 0;
|
||||
n = 0;
|
||||
for (int i = _onsetDFBufferSize; i < (_onsetDFBufferSize + windowSize); i++)
|
||||
{
|
||||
wcumscore = futureCumulativeScore[i] * w2[n];
|
||||
if (wcumscore > max)
|
||||
{
|
||||
max = wcumscore;
|
||||
_beatCounter = n;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
// set next prediction time
|
||||
_m0 = _beatCounter + (int)Math.Round(_beatPeriod / 2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.BeatDetection
|
||||
{
|
||||
public class CircularBuffer
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private int _writeIndex = 0;
|
||||
private double[] _buffer = new double[1];
|
||||
|
||||
public double this[int index]
|
||||
{
|
||||
get => _buffer[(index + _writeIndex) % _buffer.Length];
|
||||
set => _buffer[(index + _writeIndex) % _buffer.Length] = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void Add(double value)
|
||||
{
|
||||
_buffer[_writeIndex] = value;
|
||||
_writeIndex = (_writeIndex + 1) % _buffer.Length;
|
||||
}
|
||||
|
||||
public void Resize(int size)
|
||||
{
|
||||
_buffer = new double[size];
|
||||
_writeIndex = 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using KeyboardAudioVisualizer.AudioCapture;
|
||||
using MathNet.Numerics;
|
||||
using MathNet.Numerics.IntegralTransforms;
|
||||
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.BeatDetection
|
||||
{
|
||||
public class OnsetDetector : IAudioProcessor
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly AudioBuffer _audioBuffer;
|
||||
public int SampleRate => _audioBuffer.Size;
|
||||
public int HopSize => _hopSize;
|
||||
public double Onset { get; private set; }
|
||||
|
||||
private int _frameSize;
|
||||
private int _hopSize;
|
||||
private double _prevEnergySum;
|
||||
|
||||
private float[] _dataBuffer;
|
||||
private double[] _frame;
|
||||
private double[] _window;
|
||||
private double[] _magSpec;
|
||||
private double[] _prevMagSpec;
|
||||
private double[] _phase;
|
||||
private double[] _prevPhase;
|
||||
private double[] _prevPhase2;
|
||||
private Complex32[] _complexBuffer;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public OnsetDetector(AudioBuffer audioBuffer)
|
||||
{
|
||||
this._audioBuffer = audioBuffer;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_hopSize = 512;
|
||||
_frameSize = SampleRate;
|
||||
|
||||
_dataBuffer = new float[_audioBuffer.Size];
|
||||
_frame = new double[_frameSize];
|
||||
_window = new double[_frameSize];
|
||||
_magSpec = new double[_frameSize];
|
||||
_prevMagSpec = new double[_frameSize];
|
||||
_phase = new double[_frameSize];
|
||||
_prevPhase = new double[_frameSize];
|
||||
_prevPhase2 = new double[_frameSize];
|
||||
_complexBuffer = new Complex32[_frameSize];
|
||||
|
||||
_window = Window.Hann(_frameSize);
|
||||
|
||||
for (int i = 0; i < _frameSize; i++)
|
||||
{
|
||||
_prevMagSpec[i] = 0.0;
|
||||
_prevPhase[i] = 0.0;
|
||||
_prevPhase2[i] = 0.0;
|
||||
_frame[i] = 0.0;
|
||||
}
|
||||
_prevEnergySum = 0.0;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
_audioBuffer.CopyMixInto(ref _dataBuffer, 0);
|
||||
Onset = CalculateOnsetDetectionFunctionSample(_dataBuffer);
|
||||
}
|
||||
|
||||
private double CalculateOnsetDetectionFunctionSample(float[] buffer)
|
||||
{
|
||||
for (int i = 0; i < (_frameSize - _hopSize); i++)
|
||||
{
|
||||
_frame[i] = _frame[i + _hopSize];
|
||||
}
|
||||
|
||||
int j = 0;
|
||||
for (int i = (_frameSize - _hopSize); i < _frameSize; i++)
|
||||
{
|
||||
_frame[i] = buffer[j];
|
||||
j++;
|
||||
}
|
||||
|
||||
return ComplexSpectralDifferenceHWR();
|
||||
}
|
||||
|
||||
private double ComplexSpectralDifferenceHWR()
|
||||
{
|
||||
double phaseDeviation;
|
||||
double sum;
|
||||
double magnitudeDifference;
|
||||
double csd;
|
||||
|
||||
PerformFFT();
|
||||
|
||||
sum = 0;
|
||||
for (int i = 0; i < _frameSize; i++)
|
||||
{
|
||||
// calculate phase value
|
||||
_phase[i] = Math.Atan2(_complexBuffer[i].Imaginary, _complexBuffer[i].Real);
|
||||
// calculate magnitude value
|
||||
_magSpec[i] = Math.Sqrt(Math.Pow(_complexBuffer[i].Real, 2) + Math.Pow(_complexBuffer[i].Imaginary, 2));
|
||||
// phase deviation
|
||||
phaseDeviation = _phase[i] - (2 * _prevPhase[i]) + _prevPhase2[i];
|
||||
// calculate magnitude difference (real part of Euclidean distance between complex frames)
|
||||
magnitudeDifference = _magSpec[i] - _prevMagSpec[i];
|
||||
// if we have a positive change in magnitude, then include in sum, otherwise ignore (half-wave rectification)
|
||||
if (magnitudeDifference > 0)
|
||||
{
|
||||
// calculate complex spectral difference for the current spectral bin
|
||||
csd = Math.Sqrt(Math.Pow(_magSpec[i], 2) + Math.Pow(_prevMagSpec[i], 2) - 2 * _magSpec[i] * _prevMagSpec[i] * Math.Cos(phaseDeviation));
|
||||
// add to sum
|
||||
sum = sum + csd;
|
||||
}
|
||||
// store values for next calculation
|
||||
_prevPhase2[i] = _prevPhase[i];
|
||||
_prevPhase[i] = _phase[i];
|
||||
_prevMagSpec[i] = _magSpec[i];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
private void PerformFFT()
|
||||
{
|
||||
int fsize2 = _frameSize / 2;
|
||||
|
||||
for (int i = 0; i < fsize2; i++)
|
||||
{
|
||||
_complexBuffer[i] = new Complex32((float)(_frame[i + fsize2] * _window[i + fsize2]), 0);
|
||||
_complexBuffer[i + fsize2] = new Complex32((float)(_frame[i] * _window[i]), 0);
|
||||
}
|
||||
|
||||
Fourier.Forward(_complexBuffer);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using KeyboardAudioVisualizer.Helper;
|
||||
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.Spectrum
|
||||
{
|
||||
public abstract class AbstractSpectrum : ISpectrum
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
protected Band[] Bands { get; set; }
|
||||
public int BandCount => Bands.Length;
|
||||
|
||||
//TODO DarthAffe 10.08.2017: Check Frequency-Indexers - they are most likely wrong
|
||||
public Band this[int index] => Bands[index];
|
||||
public Band this[float frequency] => Bands[FrequencyHelper.GetIndexOfFrequency(frequency, BandCount)];
|
||||
public Band[] this[float minFrequency, float maxFrequency]
|
||||
{
|
||||
get
|
||||
{
|
||||
int minIndex = FrequencyHelper.GetIndexOfFrequency(minFrequency, BandCount);
|
||||
int maxIndex = FrequencyHelper.GetIndexOfFrequency(maxFrequency, BandCount);
|
||||
|
||||
Band[] result = new Band[maxIndex - minIndex];
|
||||
Array.Copy(Bands, minIndex, result, 0, result.Length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public IEnumerator<Band> GetEnumerator() => Bands.AsEnumerable().GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
45
KeyboardAudioVisualizer/AudioProcessing/Spectrum/Band.cs
Normal file
45
KeyboardAudioVisualizer/AudioProcessing/Spectrum/Band.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.Spectrum
|
||||
{
|
||||
public class Band
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly float[] _data;
|
||||
private readonly float _resolution;
|
||||
|
||||
public float LowerFrequency { get; }
|
||||
public float UpperFrequency { get; }
|
||||
|
||||
private float? _average = null;
|
||||
public float Average => _average ?? (_average = _data.Average()).Value;
|
||||
|
||||
private float? _min = null;
|
||||
public float Min => _min ?? (_min = _data.Min()).Value;
|
||||
|
||||
private float? _max = null;
|
||||
public float Max => _max ?? (_max = _data.Max()).Value;
|
||||
|
||||
private float? _sum = null;
|
||||
public float Sum => _sum ?? (_sum = _data.Sum()).Value;
|
||||
|
||||
public float this[int index] => _data[index];
|
||||
public float this[float frequency] => _data[(int)((frequency - LowerFrequency) / _resolution)];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public Band(float lowerFrequency, float upperFrequency, float[] data)
|
||||
{
|
||||
this.LowerFrequency = lowerFrequency;
|
||||
this.UpperFrequency = upperFrequency;
|
||||
this._data = data;
|
||||
|
||||
_resolution = (UpperFrequency - LowerFrequency) / data.Length;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using KeyboardAudioVisualizer.AudioCapture;
|
||||
using System;
|
||||
using KeyboardAudioVisualizer.AudioCapture;
|
||||
using MathNet.Numerics;
|
||||
using MathNet.Numerics.IntegralTransforms;
|
||||
|
||||
@ -12,21 +13,18 @@ namespace KeyboardAudioVisualizer.AudioProcessing.Spectrum
|
||||
|
||||
private float[] _sampleData;
|
||||
private double[] _hamming;
|
||||
private Complex32[] _complexBuffer;
|
||||
|
||||
private float[] _spectrum;
|
||||
public float[] Spectrum => _spectrum;
|
||||
|
||||
public int SampleRate { get; private set; }
|
||||
public float Resolution { get; private set; }
|
||||
private int _usableDataLength;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public FourierSpectrumProvider(AudioBuffer audioBuffer, int sampleRate)
|
||||
public FourierSpectrumProvider(AudioBuffer audioBuffer)
|
||||
{
|
||||
this._audioBuffer = audioBuffer;
|
||||
this.SampleRate = sampleRate;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -37,13 +35,15 @@ namespace KeyboardAudioVisualizer.AudioProcessing.Spectrum
|
||||
{
|
||||
_hamming = Window.Hamming(_audioBuffer.Size);
|
||||
_sampleData = new float[_audioBuffer.Size];
|
||||
_spectrum = new float[_audioBuffer.Size / 2];
|
||||
Resolution = (float)SampleRate / (float)_audioBuffer.Size;
|
||||
_complexBuffer = new Complex32[_audioBuffer.Size];
|
||||
_usableDataLength = (_audioBuffer.Size / 2) + 1;
|
||||
_spectrum = new float[_usableDataLength];
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
_audioBuffer.CopyMixInto(ref _sampleData, 0);
|
||||
|
||||
ApplyHamming(ref _sampleData);
|
||||
CreateSpectrum(ref _sampleData);
|
||||
}
|
||||
@ -56,24 +56,26 @@ namespace KeyboardAudioVisualizer.AudioProcessing.Spectrum
|
||||
|
||||
private void CreateSpectrum(ref float[] data)
|
||||
{
|
||||
Complex32[] complexData = CreateComplexData(ref data);
|
||||
Fourier.Forward(complexData, FourierOptions.NoScaling);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
_complexBuffer[i] = new Complex32(data[i], 0);
|
||||
|
||||
Fourier.Forward(_complexBuffer, FourierOptions.NoScaling);
|
||||
|
||||
for (int i = 0; i < _spectrum.Length; i++)
|
||||
{
|
||||
Complex32 fourierData = complexData[i];
|
||||
_spectrum[i] = (fourierData.Real * fourierData.Real) + (fourierData.Imaginary * fourierData.Imaginary);
|
||||
Complex32 fourierData = _complexBuffer[i];
|
||||
_spectrum[i] = (float)Math.Sqrt(fourierData.Real * fourierData.Real) + (fourierData.Imaginary * fourierData.Imaginary);
|
||||
}
|
||||
}
|
||||
|
||||
private static Complex32[] CreateComplexData(ref float[] data)
|
||||
{
|
||||
Complex32[] complexData = new Complex32[data.Length];
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
complexData[i] = new Complex32(data[i], 0);
|
||||
public ISpectrum GetLinearSpectrum(int bands = 64, float minFrequency = -1, float maxFrequency = -1) => new LinearSpectrum(_spectrum, bands, minFrequency, maxFrequency);
|
||||
|
||||
return complexData;
|
||||
}
|
||||
public ISpectrum GetLogarithmicSpectrum(int bands = 12, float minFrequency = -1, float maxFrequency = -1) => new LogarithmicSpectrum(_spectrum, bands, minFrequency, maxFrequency);
|
||||
|
||||
#endregion
|
||||
public ISpectrum GetGammaSpectrum(int bands = 64, float gamma = 2, float minFrequency = -1, float maxFrequency = -1) => new GammaSpectrum(_spectrum, bands, gamma, minFrequency, maxFrequency);
|
||||
|
||||
public ISpectrum GetRawSpectrum() => new RawSpectrumProvider(_spectrum);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using KeyboardAudioVisualizer.Helper;
|
||||
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.Spectrum
|
||||
{
|
||||
public class GammaSpectrum : AbstractSpectrum
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
public GammaSpectrum(float[] data, int bands, float gamma = 2, float minFrequency = -1, float maxFrequency = -1)
|
||||
{
|
||||
int dataReferenceCount = (data.Length - 1) * 2;
|
||||
|
||||
int fromIndex = minFrequency < 0 ? 0 : MathHelper.Clamp(FrequencyHelper.GetIndexOfFrequency(minFrequency, dataReferenceCount), 0, data.Length - 1 - bands); // -bands since we need at least enough data to get our bands
|
||||
int toIndex = maxFrequency < 0 ? data.Length - 1 : MathHelper.Clamp(FrequencyHelper.GetIndexOfFrequency(maxFrequency, dataReferenceCount), fromIndex, data.Length - 1);
|
||||
|
||||
int usableSourceData = Math.Max(bands, (toIndex - fromIndex) + 1);
|
||||
|
||||
Bands = new Band[bands];
|
||||
|
||||
int index = fromIndex;
|
||||
for (int i = 0; i < Bands.Length; i++)
|
||||
{
|
||||
int count = Math.Max(1, (((int)(Math.Pow((i + 1f) / Bands.Length, gamma) * usableSourceData))) - index);
|
||||
|
||||
float[] bandData = new float[count];
|
||||
Array.Copy(data, index, bandData, 0, count);
|
||||
Bands[i] = new Band(FrequencyHelper.GetFrequencyOfIndex(index, dataReferenceCount),
|
||||
FrequencyHelper.GetFrequencyOfIndex(index + count, dataReferenceCount),
|
||||
bandData);
|
||||
|
||||
index += count;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.Spectrum
|
||||
{
|
||||
public interface ISpectrum : IEnumerable<Band>
|
||||
{
|
||||
int BandCount { get; }
|
||||
|
||||
Band this[int index] { get; }
|
||||
Band this[float frequency] { get; }
|
||||
Band[] this[float minFrequency, float maxFrequency] { get; }
|
||||
}
|
||||
}
|
||||
@ -2,8 +2,9 @@
|
||||
{
|
||||
public interface ISpectrumProvider : IAudioProcessor
|
||||
{
|
||||
float[] Spectrum { get; }
|
||||
int SampleRate { get; }
|
||||
float Resolution { get; }
|
||||
ISpectrum GetLinearSpectrum(int bands = 64, float minFrequency = -1, float maxFrequency = -1);
|
||||
ISpectrum GetLogarithmicSpectrum(int bands = 1, float minFrequency = -1, float maxFrequency = -1);
|
||||
ISpectrum GetGammaSpectrum(int bands = 1, float gamma = 2, float minFrequency = -1, float maxFrequency = -1);
|
||||
ISpectrum GetRawSpectrum();
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using KeyboardAudioVisualizer.Helper;
|
||||
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.Spectrum
|
||||
{
|
||||
public class LinearSpectrum : AbstractSpectrum
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
public LinearSpectrum(float[] data, int bands, float minFrequency = -1, float maxFrequency = -1)
|
||||
{
|
||||
int dataReferenceCount = (data.Length - 1) * 2;
|
||||
|
||||
int fromIndex = minFrequency < 0 ? 0 : MathHelper.Clamp(FrequencyHelper.GetIndexOfFrequency(minFrequency, dataReferenceCount), 0, data.Length - 1 - bands); // -bands since we need at least enough data to get our bands
|
||||
int toIndex = maxFrequency < 0 ? data.Length - 1 : MathHelper.Clamp(FrequencyHelper.GetIndexOfFrequency(maxFrequency, dataReferenceCount), fromIndex, data.Length - 1);
|
||||
|
||||
int usableSourceData = Math.Max(bands, (toIndex - fromIndex) + 1);
|
||||
|
||||
Bands = new Band[bands];
|
||||
|
||||
double frequenciesPerBand = (double)usableSourceData / bands;
|
||||
double frequencyCounter = 0;
|
||||
|
||||
int index = fromIndex;
|
||||
for (int i = 0; i < Bands.Length; i++)
|
||||
{
|
||||
frequencyCounter += frequenciesPerBand;
|
||||
int count = (int)frequencyCounter;
|
||||
|
||||
float[] bandData = new float[count];
|
||||
Array.Copy(data, index, bandData, 0, count);
|
||||
Bands[i] = new Band(FrequencyHelper.GetFrequencyOfIndex(index, dataReferenceCount),
|
||||
FrequencyHelper.GetFrequencyOfIndex(index + count, dataReferenceCount),
|
||||
bandData);
|
||||
|
||||
index += count;
|
||||
frequencyCounter -= count;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using KeyboardAudioVisualizer.Helper;
|
||||
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.Spectrum
|
||||
{
|
||||
public class LogarithmicSpectrum : AbstractSpectrum
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
public LogarithmicSpectrum(float[] data, int bands, float minFrequency = -1, float maxFrequency = -1)
|
||||
{
|
||||
int dataReferenceCount = (data.Length - 1) * 2;
|
||||
|
||||
int fromIndex = minFrequency < 0 ? 0 : MathHelper.Clamp(FrequencyHelper.GetIndexOfFrequency(minFrequency, dataReferenceCount), 0, data.Length - 1 - bands); // -bands since we need at least enough data to get our bands
|
||||
int toIndex = maxFrequency < 0 ? data.Length - 1 : MathHelper.Clamp(FrequencyHelper.GetIndexOfFrequency(maxFrequency, dataReferenceCount), fromIndex, data.Length - 1);
|
||||
|
||||
int usableSourceData = Math.Max(bands, (toIndex - fromIndex) + 1);
|
||||
|
||||
Bands = new Band[bands];
|
||||
|
||||
double ratio = Math.Pow(usableSourceData, 1.0 / bands);
|
||||
double calculation = 1;
|
||||
|
||||
int index = fromIndex;
|
||||
for (int i = 0; i < Bands.Length; i++)
|
||||
{
|
||||
calculation *= ratio;
|
||||
int count = Math.Max(1, ((int)calculation) - index);
|
||||
|
||||
float[] bandData = new float[count];
|
||||
Array.Copy(data, index, bandData, 0, count);
|
||||
Bands[i] = new Band(FrequencyHelper.GetFrequencyOfIndex(index, dataReferenceCount),
|
||||
FrequencyHelper.GetFrequencyOfIndex(index + count, dataReferenceCount),
|
||||
bandData);
|
||||
|
||||
index += count;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
using KeyboardAudioVisualizer.Helper;
|
||||
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.Spectrum
|
||||
{
|
||||
public class RawSpectrumProvider : AbstractSpectrum
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
public RawSpectrumProvider(float[] data)
|
||||
{
|
||||
int dataReferenceCount = (data.Length - 1) * 2;
|
||||
|
||||
Bands = new Band[data.Length];
|
||||
|
||||
for (int i = 0; i < Bands.Length; i++)
|
||||
Bands[i] = new Band(FrequencyHelper.GetFrequencyOfIndex(i, dataReferenceCount), FrequencyHelper.GetFrequencyOfIndex(i, dataReferenceCount), new[] { data[i] });
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,31 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using KeyboardAudioVisualizer.AudioProcessing.Equalizer;
|
||||
using KeyboardAudioVisualizer.AudioProcessing.Spectrum;
|
||||
using KeyboardAudioVisualizer.Configuration;
|
||||
using KeyboardAudioVisualizer.Helper;
|
||||
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider
|
||||
{
|
||||
#region Configuration
|
||||
|
||||
public enum ValueMode { Max, Average, Sum }
|
||||
public enum SpectrumMode { Gamma, Logarithmic, Linear }
|
||||
|
||||
public class FrequencyBarsVisualizationProviderConfiguration : AbstractConfiguration
|
||||
{
|
||||
private ValueMode _valueMode = ValueMode.Max;
|
||||
public ValueMode ValueMode
|
||||
{
|
||||
get => _valueMode;
|
||||
set => SetProperty(ref _valueMode, value);
|
||||
}
|
||||
|
||||
private SpectrumMode _spectrumMode = SpectrumMode.Gamma;
|
||||
public SpectrumMode SpectrumMode
|
||||
{
|
||||
get => _spectrumMode;
|
||||
set => SetProperty(ref _spectrumMode, value);
|
||||
}
|
||||
|
||||
private int _bars = 48;
|
||||
public int Bars
|
||||
{
|
||||
@ -18,43 +33,43 @@ namespace KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider
|
||||
set => SetProperty(ref _bars, value);
|
||||
}
|
||||
|
||||
private double _smoothing = 8;
|
||||
private double _smoothing = 3;
|
||||
public double Smoothing
|
||||
{
|
||||
get => _smoothing;
|
||||
set => SetProperty(ref _smoothing, value);
|
||||
}
|
||||
|
||||
private double _minFrequency = 100;
|
||||
public double MinFrequency
|
||||
private float _minFrequency = 60;
|
||||
public float MinFrequency
|
||||
{
|
||||
get => _minFrequency;
|
||||
set => SetProperty(ref _minFrequency, value);
|
||||
}
|
||||
|
||||
private double _maxFrequency = 15000;
|
||||
public double MaxFrequency
|
||||
private float _maxFrequency = 15000;
|
||||
public float MaxFrequency
|
||||
{
|
||||
get => _maxFrequency;
|
||||
set => SetProperty(ref _maxFrequency, value);
|
||||
}
|
||||
|
||||
private double _scale = 20;
|
||||
public double Scale
|
||||
private double _referenceLevel = 90;
|
||||
public double ReferenceLevel
|
||||
{
|
||||
get => _scale;
|
||||
set => SetProperty(ref _scale, value);
|
||||
get => _referenceLevel;
|
||||
set => SetProperty(ref _referenceLevel, value);
|
||||
}
|
||||
|
||||
private int _emphasisePeaks = 1;
|
||||
public int EmphasisePeaks
|
||||
private float _emphasisePeaks = 0.5f;
|
||||
public float EmphasisePeaks
|
||||
{
|
||||
get => _emphasisePeaks;
|
||||
set => SetProperty(ref _emphasisePeaks, value);
|
||||
}
|
||||
|
||||
private double _gamma = 3;
|
||||
public double Gamma
|
||||
private int _gamma = 2;
|
||||
public int Gamma
|
||||
{
|
||||
get => _gamma;
|
||||
set => SetProperty(ref _gamma, value);
|
||||
@ -70,10 +85,8 @@ namespace KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider
|
||||
private readonly FrequencyBarsVisualizationProviderConfiguration _configuration;
|
||||
private readonly ISpectrumProvider _spectrumProvider;
|
||||
|
||||
private int _frequencySkipCount;
|
||||
private int _frequencyCount;
|
||||
private double _smoothingFactor;
|
||||
private double _scalingValue;
|
||||
private double _emphasiseFactor;
|
||||
|
||||
public IEqualizer Equalizer { get; set; }
|
||||
public IConfiguration Configuration => _configuration;
|
||||
@ -103,79 +116,65 @@ namespace KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider
|
||||
VisualizationData = new float[_configuration.Bars];
|
||||
|
||||
if ((changedPropertyName == null) || (changedPropertyName == nameof(FrequencyBarsVisualizationProviderConfiguration.Smoothing)))
|
||||
_smoothingFactor = Math.Pow((0.0000025 * Math.Pow(2, _configuration.Smoothing)), ((double)_spectrumProvider.Spectrum.Length * 2) / (double)_spectrumProvider.SampleRate);
|
||||
_smoothingFactor = Math.Log10(_configuration.Smoothing);
|
||||
|
||||
if ((changedPropertyName == null)
|
||||
|| (changedPropertyName == nameof(FrequencyBarsVisualizationProviderConfiguration.MinFrequency))
|
||||
|| (changedPropertyName == nameof(FrequencyBarsVisualizationProviderConfiguration.MaxFrequency)))
|
||||
CalculateFrequencyCount(MathHelper.Clamp(_configuration.MinFrequency, 0, _spectrumProvider.SampleRate / 2.0),
|
||||
MathHelper.Clamp(_configuration.MaxFrequency, 0, _spectrumProvider.SampleRate / 2.0),
|
||||
_spectrumProvider.SampleRate, _spectrumProvider.Spectrum.Length * 2);
|
||||
|
||||
if ((changedPropertyName == null) || (changedPropertyName == nameof(FrequencyBarsVisualizationProviderConfiguration.Scale)))
|
||||
_scalingValue = _configuration.Scale * 0.0001;
|
||||
}
|
||||
|
||||
private void CalculateFrequencyCount(double minFrequency, double maxFrequency, int sampleRate, int sampleSize)
|
||||
{
|
||||
int firstFrequency = Math.Max(0, (int)Math.Ceiling((minFrequency / sampleRate) * sampleSize) - 1);
|
||||
int lastFrequency = Math.Max(firstFrequency, (int)Math.Ceiling((maxFrequency / sampleRate) * sampleSize));
|
||||
|
||||
if (firstFrequency == lastFrequency)
|
||||
{
|
||||
if (firstFrequency == 0) lastFrequency++;
|
||||
else firstFrequency--;
|
||||
}
|
||||
|
||||
_frequencyCount = lastFrequency - firstFrequency;
|
||||
_frequencySkipCount = firstFrequency;
|
||||
if ((changedPropertyName == null) || (changedPropertyName == nameof(FrequencyBarsVisualizationProviderConfiguration.EmphasisePeaks)))
|
||||
_emphasiseFactor = (0.75 * (1 + _configuration.EmphasisePeaks));
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
float[] spectrum = _spectrumProvider.Spectrum.Skip(_frequencySkipCount).Take(_frequencyCount).ToArray();
|
||||
ISpectrum spectrum = GetSpectrum();
|
||||
if (spectrum == null) return;
|
||||
|
||||
int startFrequency = 0;
|
||||
for (int i = 0; i < VisualizationData.Length; i++)
|
||||
for (int i = 0; i < spectrum.BandCount; i++)
|
||||
{
|
||||
int endFrequency = Math.Max(startFrequency + 1, Math.Min(_frequencyCount, (int)Math.Round(Math.Pow((i + 1f) / VisualizationData.Length, _configuration.Gamma) * _frequencyCount)));
|
||||
int bandWidth = endFrequency - startFrequency;
|
||||
double binPower = 0;
|
||||
double binPower = Math.Max(0, 20 * Math.Log10(GetBandValue(spectrum[i])));
|
||||
|
||||
for (int j = 0; j < bandWidth; j++)
|
||||
{
|
||||
float power = spectrum[Math.Min(spectrum.Length - 1, startFrequency + j)];
|
||||
binPower = Math.Max(power, binPower);
|
||||
}
|
||||
//TODO DarthAffe 10.08.2017: Rewrite Equalizer
|
||||
//if (Equalizer?.IsEnabled == true)
|
||||
//{
|
||||
// float value = Equalizer.CalculateValues(VisualizationData.Length)[i];
|
||||
// if (Math.Abs(value) > 0.000001)
|
||||
// {
|
||||
// bool lower = value < 0;
|
||||
// value = 1 + (value * value);
|
||||
// binPower *= lower ? 1f / value : value;
|
||||
// }
|
||||
//}
|
||||
|
||||
if (Equalizer?.IsEnabled == true)
|
||||
{
|
||||
float value = Equalizer.CalculateValues(VisualizationData.Length)[i];
|
||||
if (Math.Abs(value) > 0.000001)
|
||||
{
|
||||
bool lower = value < 0;
|
||||
value = 1 + (value * value);
|
||||
binPower *= lower ? 1f / value : value;
|
||||
}
|
||||
}
|
||||
binPower /= _configuration.ReferenceLevel;
|
||||
if (_configuration.EmphasisePeaks > 0.001)
|
||||
binPower = Math.Pow(binPower, 1 + _configuration.EmphasisePeaks) * _emphasiseFactor;
|
||||
|
||||
binPower = Math.Log(binPower);
|
||||
binPower = Math.Max(0, binPower);
|
||||
VisualizationData[i] = (float)((VisualizationData[i] * _smoothingFactor) + (binPower * (1.0 - _smoothingFactor)));
|
||||
if (float.IsNaN(VisualizationData[i])) VisualizationData[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (_configuration.EmphasisePeaks == 1)
|
||||
{
|
||||
binPower *= binPower;
|
||||
binPower *= 0.15;
|
||||
}
|
||||
if (_configuration.EmphasisePeaks == 2)
|
||||
binPower *= binPower * binPower;
|
||||
else
|
||||
binPower *= 40;
|
||||
private ISpectrum GetSpectrum()
|
||||
{
|
||||
switch (_configuration.SpectrumMode)
|
||||
{
|
||||
case SpectrumMode.Gamma:
|
||||
return _spectrumProvider.GetGammaSpectrum(_configuration.Bars, _configuration.Gamma, _configuration.MinFrequency, _configuration.MaxFrequency);
|
||||
case SpectrumMode.Logarithmic:
|
||||
return _spectrumProvider.GetLogarithmicSpectrum(_configuration.Bars, _configuration.MinFrequency, _configuration.MaxFrequency);
|
||||
case SpectrumMode.Linear:
|
||||
return _spectrumProvider.GetLinearSpectrum(_configuration.Bars, _configuration.MinFrequency, _configuration.MaxFrequency);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
VisualizationData[i] = (float)((VisualizationData[i] * _smoothingFactor) + (binPower * _scalingValue * (1.0 - _smoothingFactor)));
|
||||
if (double.IsNaN(VisualizationData[i])) VisualizationData[i] = 0;
|
||||
|
||||
startFrequency = endFrequency;
|
||||
private float GetBandValue(Band band)
|
||||
{
|
||||
switch (_configuration.ValueMode)
|
||||
{
|
||||
case ValueMode.Max: return band.Max;
|
||||
case ValueMode.Average: return band.Average;
|
||||
case ValueMode.Sum: return band.Sum;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using KeyboardAudioVisualizer.AudioProcessing.Spectrum;
|
||||
using KeyboardAudioVisualizer.Configuration;
|
||||
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider
|
||||
{
|
||||
#region Configuration
|
||||
|
||||
public class BeatVisualizationProviderConfiguration : AbstractConfiguration
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class BeatVisualizationProvider : IVisualizationProvider
|
||||
{
|
||||
public IConfiguration Configuration { get; }
|
||||
public float[] VisualizationData { get; }
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
36
KeyboardAudioVisualizer/Brushes/BeatBrush.cs
Normal file
36
KeyboardAudioVisualizer/Brushes/BeatBrush.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider;
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace KeyboardAudioVisualizer.Brushes
|
||||
{
|
||||
public class BeatBrush : AbstractBrush
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly IVisualizationProvider _visualizationProvider;
|
||||
private readonly Color _color;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public BeatBrush(IVisualizationProvider visualizationProvider, Color color)
|
||||
{
|
||||
this._visualizationProvider = visualizationProvider;
|
||||
this._color = color;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
protected override Color GetColorAtPoint(Rectangle rectangle, BrushRenderTarget renderTarget)
|
||||
{
|
||||
Color color = new Color(_color);
|
||||
color.APercent *= _visualizationProvider.VisualizationData[0];
|
||||
return color;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
26
KeyboardAudioVisualizer/Helper/FrequencyHelper.cs
Normal file
26
KeyboardAudioVisualizer/Helper/FrequencyHelper.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
|
||||
namespace KeyboardAudioVisualizer.Helper
|
||||
{
|
||||
public static class FrequencyHelper
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const int SAMPLE_RATE = 44100; // Right now this is fixed
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public static float GetFrequencyOfIndex(int index, int count) => index * ((float)SAMPLE_RATE / count);
|
||||
public static int GetIndexOfFrequency(float frequency, int count) => (int)(frequency / ((float)SAMPLE_RATE / count));
|
||||
|
||||
public static double CalculatedBAForFrequency(float frequency)
|
||||
{
|
||||
double ra = (Math.Pow(12194, 2) * Math.Pow(frequency, 4)) / ((Math.Pow(frequency, 2) + Math.Pow(20.6, 2)) * Math.Sqrt((Math.Pow(frequency, 2) + Math.Pow(107.7, 2)) * (Math.Pow(frequency, 2) + Math.Pow(737.9, 2))) * (Math.Pow(frequency, 2) + Math.Pow(12194, 2)));
|
||||
return (20 * Math.Log10(ra)) + 2;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ namespace KeyboardAudioVisualizer.Helper
|
||||
|
||||
public static double Clamp(double value, double min, double max) => Math.Max(min, Math.Min(max, value));
|
||||
public static float Clamp(float value, float min, float max) => (float)Clamp((double)value, min, max);
|
||||
public static int Clamp(int value, int min, int max) => Math.Max(min, Math.Min(max, value));
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@ -106,14 +106,26 @@
|
||||
<Compile Include="AudioCapture\CSCoreAudioInput.cs" />
|
||||
<Compile Include="AudioCapture\IAudioInput.cs" />
|
||||
<Compile Include="AudioProcessing\AudioProcessor.cs" />
|
||||
<Compile Include="AudioProcessing\BeatDetection\BTrackBeatDetector.cs" />
|
||||
<Compile Include="AudioProcessing\BeatDetection\CircularBuffer.cs" />
|
||||
<Compile Include="AudioProcessing\BeatDetection\OnsetDetector.cs" />
|
||||
<Compile Include="AudioProcessing\Equalizer\IEqualizer.cs" />
|
||||
<Compile Include="AudioProcessing\Equalizer\MultiBandEqualizer.cs" />
|
||||
<Compile Include="AudioProcessing\IAudioProcessor.cs" />
|
||||
<Compile Include="AudioProcessing\Spectrum\Band.cs" />
|
||||
<Compile Include="AudioProcessing\Spectrum\AbstractSpectrum.cs" />
|
||||
<Compile Include="AudioProcessing\Spectrum\GammaSpectrum.cs" />
|
||||
<Compile Include="AudioProcessing\Spectrum\ISpectrum.cs" />
|
||||
<Compile Include="AudioProcessing\Spectrum\ISpectrumProvider.cs" />
|
||||
<Compile Include="AudioProcessing\Spectrum\FourierSpectrumProvider.cs" />
|
||||
<Compile Include="AudioProcessing\Spectrum\LogarithmicSpectrum.cs" />
|
||||
<Compile Include="AudioProcessing\Spectrum\LinearSpectrum.cs" />
|
||||
<Compile Include="AudioProcessing\VisualizationProvider\BeatVisualizationProvider.cs" />
|
||||
<Compile Include="AudioProcessing\VisualizationProvider\FrequencyBarsVisualizationProvider.cs" />
|
||||
<Compile Include="AudioProcessing\VisualizationProvider\IVisualizationProvider.cs" />
|
||||
<Compile Include="AudioProcessing\VisualizationProvider\LevelVisualizationProvider.cs" />
|
||||
<Compile Include="AudioProcessing\Spectrum\RawSpectrumProvider.cs" />
|
||||
<Compile Include="Brushes\BeatBrush.cs" />
|
||||
<Compile Include="Brushes\FrequencyBarsBrush.cs" />
|
||||
<Compile Include="Brushes\LevelBarBrush.cs" />
|
||||
<Compile Include="Configuration\AbstractConfiguration.cs" />
|
||||
@ -122,6 +134,7 @@
|
||||
<Compile Include="Controls\ImageButton.cs" />
|
||||
<Compile Include="Helper\ActionCommand.cs" />
|
||||
<Compile Include="Helper\ExceptionExtension.cs" />
|
||||
<Compile Include="Helper\FrequencyHelper.cs" />
|
||||
<Compile Include="Helper\MathHelper.cs" />
|
||||
<Compile Include="Settings.cs" />
|
||||
<Compile Include="Controls\BlurredDecorationWindow.cs" />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user