Refactored audio processing

This commit is contained in:
Darth Affe 2017-08-10 22:34:01 +02:00
parent fc3db046c3
commit 260be351ae
21 changed files with 1105 additions and 120 deletions

View File

@ -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));

View File

@ -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;

View File

@ -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

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View 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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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; }
}
}

View File

@ -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();
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View 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
}
}

View 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
}
}

View File

@ -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
}

View File

@ -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" />