diff --git a/KeyboardAudioVisualizer/ApplicationManager.cs b/KeyboardAudioVisualizer/ApplicationManager.cs index 0e9d3d6..bd6e9d8 100644 --- a/KeyboardAudioVisualizer/ApplicationManager.cs +++ b/KeyboardAudioVisualizer/ApplicationManager.cs @@ -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)); diff --git a/KeyboardAudioVisualizer/AudioCapture/CSCoreAudioInput.cs b/KeyboardAudioVisualizer/AudioCapture/CSCoreAudioInput.cs index f7163d8..3aeda39 100644 --- a/KeyboardAudioVisualizer/AudioCapture/CSCoreAudioInput.cs +++ b/KeyboardAudioVisualizer/AudioCapture/CSCoreAudioInput.cs @@ -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; diff --git a/KeyboardAudioVisualizer/AudioProcessing/AudioProcessor.cs b/KeyboardAudioVisualizer/AudioProcessing/AudioProcessor.cs index 7eb01ed..492a37d 100644 --- a/KeyboardAudioVisualizer/AudioProcessing/AudioProcessor.cs +++ b/KeyboardAudioVisualizer/AudioProcessing/AudioProcessor.cs @@ -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 diff --git a/KeyboardAudioVisualizer/AudioProcessing/BeatDetection/BTrackBeatDetector.cs b/KeyboardAudioVisualizer/AudioProcessing/BeatDetection/BTrackBeatDetector.cs new file mode 100644 index 0000000..8841ecf --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/BeatDetection/BTrackBeatDetector.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/BeatDetection/CircularBuffer.cs b/KeyboardAudioVisualizer/AudioProcessing/BeatDetection/CircularBuffer.cs new file mode 100644 index 0000000..40ef084 --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/BeatDetection/CircularBuffer.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/BeatDetection/OnsetDetector.cs b/KeyboardAudioVisualizer/AudioProcessing/BeatDetection/OnsetDetector.cs new file mode 100644 index 0000000..f4b984a --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/BeatDetection/OnsetDetector.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/Spectrum/AbstractSpectrum.cs b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/AbstractSpectrum.cs new file mode 100644 index 0000000..0529336 --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/AbstractSpectrum.cs @@ -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 GetEnumerator() => Bands.AsEnumerable().GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/Spectrum/Band.cs b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/Band.cs new file mode 100644 index 0000000..38505ac --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/Band.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/Spectrum/FourierSpectrumProvider.cs b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/FourierSpectrumProvider.cs index b82fbcc..83bb895 100644 --- a/KeyboardAudioVisualizer/AudioProcessing/Spectrum/FourierSpectrumProvider.cs +++ b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/FourierSpectrumProvider.cs @@ -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 } } diff --git a/KeyboardAudioVisualizer/AudioProcessing/Spectrum/GammaSpectrum.cs b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/GammaSpectrum.cs new file mode 100644 index 0000000..b348508 --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/GammaSpectrum.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/Spectrum/ISpectrum.cs b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/ISpectrum.cs new file mode 100644 index 0000000..d5d0d24 --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/ISpectrum.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace KeyboardAudioVisualizer.AudioProcessing.Spectrum +{ + public interface ISpectrum : IEnumerable + { + int BandCount { get; } + + Band this[int index] { get; } + Band this[float frequency] { get; } + Band[] this[float minFrequency, float maxFrequency] { get; } + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/Spectrum/ISpectrumProvider.cs b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/ISpectrumProvider.cs index 14983db..7a9b0a3 100644 --- a/KeyboardAudioVisualizer/AudioProcessing/Spectrum/ISpectrumProvider.cs +++ b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/ISpectrumProvider.cs @@ -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(); } } diff --git a/KeyboardAudioVisualizer/AudioProcessing/Spectrum/LinearSpectrum.cs b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/LinearSpectrum.cs new file mode 100644 index 0000000..aa0fb48 --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/LinearSpectrum.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/Spectrum/LogarithmicSpectrum.cs b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/LogarithmicSpectrum.cs new file mode 100644 index 0000000..58f459b --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/LogarithmicSpectrum.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/Spectrum/RawSpectrumProvider.cs b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/RawSpectrumProvider.cs new file mode 100644 index 0000000..1039fdd --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/RawSpectrumProvider.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/FrequencyBarsVisualizationProvider.cs b/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/FrequencyBarsVisualizationProvider.cs index 65fc6a9..5d7f9b3 100644 --- a/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/FrequencyBarsVisualizationProvider.cs +++ b/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/FrequencyBarsVisualizationProvider.cs @@ -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; } } diff --git a/KeyboardAudioVisualizer/AudioProcessing/VisualizationProvider/BeatVisualizationProvider.cs b/KeyboardAudioVisualizer/AudioProcessing/VisualizationProvider/BeatVisualizationProvider.cs new file mode 100644 index 0000000..25938cf --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/VisualizationProvider/BeatVisualizationProvider.cs @@ -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(); + } + } +} diff --git a/KeyboardAudioVisualizer/Brushes/BeatBrush.cs b/KeyboardAudioVisualizer/Brushes/BeatBrush.cs new file mode 100644 index 0000000..71901c6 --- /dev/null +++ b/KeyboardAudioVisualizer/Brushes/BeatBrush.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/Helper/FrequencyHelper.cs b/KeyboardAudioVisualizer/Helper/FrequencyHelper.cs new file mode 100644 index 0000000..a0213c2 --- /dev/null +++ b/KeyboardAudioVisualizer/Helper/FrequencyHelper.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/Helper/MathHelper.cs b/KeyboardAudioVisualizer/Helper/MathHelper.cs index 021eeb3..626d3ec 100644 --- a/KeyboardAudioVisualizer/Helper/MathHelper.cs +++ b/KeyboardAudioVisualizer/Helper/MathHelper.cs @@ -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 } diff --git a/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj b/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj index b470db4..2e21fa0 100644 --- a/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj +++ b/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj @@ -106,14 +106,26 @@ + + + + + + + + + + + + @@ -122,6 +134,7 @@ +