mirror of
https://github.com/DarthAffe/KeyboardAudioVisualizer.git
synced 2025-12-12 15:18:30 +00:00
Added first working visualization
This commit is contained in:
parent
d738e8fc5d
commit
89330368fa
@ -2,6 +2,7 @@
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
using KeyboardAudioVisualizer.AudioProcessing;
|
||||
using KeyboardAudioVisualizer.Helper;
|
||||
|
||||
namespace KeyboardAudioVisualizer
|
||||
@ -42,6 +43,9 @@ namespace KeyboardAudioVisualizer
|
||||
_taskbarIcon.ShowBalloonTip("Keyboard Audio-Visualizer is starting in the tray!", "Click on the icon to open the configuration.", BalloonIcon.Info);
|
||||
}
|
||||
ApplicationManager.Instance.Settings = settings;
|
||||
|
||||
AudioProcessor.Initialize();
|
||||
ApplicationManager.Instance.InitializeDevices();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
using System.Windows;
|
||||
using KeyboardAudioVisualizer.AudioProcessing;
|
||||
using KeyboardAudioVisualizer.Brushes;
|
||||
using KeyboardAudioVisualizer.Helper;
|
||||
using KeyboardAudioVisualizer.UI;
|
||||
using RGB.NET.Brushes;
|
||||
using RGB.NET.Brushes.Gradients;
|
||||
using RGB.NET.Core;
|
||||
using RGB.NET.Devices.Corsair;
|
||||
using RGB.NET.Groups;
|
||||
|
||||
namespace KeyboardAudioVisualizer
|
||||
{
|
||||
@ -28,13 +35,37 @@ namespace KeyboardAudioVisualizer
|
||||
|
||||
#region Constructors
|
||||
|
||||
private ApplicationManager()
|
||||
{ }
|
||||
private ApplicationManager() { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void InitializeDevices()
|
||||
{
|
||||
RGBSurface surface = RGBSurface.Instance;
|
||||
//surface.Exception += args =>;
|
||||
|
||||
surface.UpdateFrequency = 1 / 30.0; //TODO DarthAffe 03.08.2017: Settings
|
||||
surface.UpdateMode = UpdateMode.Continuous;
|
||||
|
||||
surface.LoadDevices(CorsairDeviceProvider.Instance);
|
||||
//surface.LoadDevices(LogitechDeviceProvider.Instance);
|
||||
//surface.LoadDevices(CoolerMasterDeviceProvider.Instance);
|
||||
|
||||
ILedGroup background = new ListLedGroup(surface.Leds);
|
||||
background.Brush = new SolidColorBrush(new Color(0, 0, 0));
|
||||
|
||||
//TODO DarthAffe 03.08.2017: Changeable, Settings etc.
|
||||
foreach (IRGBDevice device in surface.Devices)
|
||||
{
|
||||
if (device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard)
|
||||
new ListLedGroup(device).Brush = new FrequencyBarsBrush(AudioProcessor.Instance.PrimaryVisualizationProvider, new RainbowGradient(300, -14));
|
||||
}
|
||||
|
||||
surface.Updating += args => AudioProcessor.Instance.Update();
|
||||
}
|
||||
|
||||
private void OpenConfiguration()
|
||||
{
|
||||
if (_configurationWindow == null) _configurationWindow = new ConfigurationWindow();
|
||||
|
||||
79
KeyboardAudioVisualizer/AudioCapture/AudioBuffer.cs
Normal file
79
KeyboardAudioVisualizer/AudioCapture/AudioBuffer.cs
Normal file
@ -0,0 +1,79 @@
|
||||
namespace KeyboardAudioVisualizer.AudioCapture
|
||||
{
|
||||
public class AudioBuffer
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly int _capacity;
|
||||
private readonly float[] _bufferLeft;
|
||||
private readonly float[] _bufferRight;
|
||||
private int _currentIndex;
|
||||
|
||||
public int Size => _capacity;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public AudioBuffer(int capacity)
|
||||
{
|
||||
this._capacity = capacity;
|
||||
|
||||
_bufferLeft = new float[capacity];
|
||||
_bufferRight = new float[capacity];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void Put(float[] src, int offset, int count)
|
||||
{
|
||||
lock (_bufferLeft)
|
||||
{
|
||||
if ((count & 1) != 0) return; // we expect stereo-data to be an even amount of values
|
||||
|
||||
if (count > _capacity)
|
||||
{
|
||||
offset += count - _capacity;
|
||||
count = _capacity;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i += 2)
|
||||
{
|
||||
_currentIndex++;
|
||||
if (_currentIndex >= _capacity) _currentIndex = 0;
|
||||
|
||||
_bufferLeft[_currentIndex] = src[offset + i];
|
||||
_bufferRight[_currentIndex] = src[offset + i + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyLeftInto(ref float[] data, int offset)
|
||||
{
|
||||
lock (_bufferLeft)
|
||||
for (int i = 0; i < _capacity; i++)
|
||||
data[offset + i] = _bufferLeft[(_currentIndex + i) % _capacity];
|
||||
}
|
||||
|
||||
public void CopyRightInto(ref float[] data, int offset)
|
||||
{
|
||||
lock (_bufferLeft)
|
||||
for (int i = 0; i < _capacity; i++)
|
||||
data[offset + i] = _bufferRight[(_currentIndex + i) % _capacity];
|
||||
}
|
||||
|
||||
public void CopyMixInto(ref float[] data, int offset)
|
||||
{
|
||||
lock (_bufferLeft)
|
||||
for (int i = 0; i < _capacity; i++)
|
||||
{
|
||||
int index = (_currentIndex + i) % _capacity;
|
||||
data[offset + i] = (_bufferLeft[index] + _bufferRight[index]) / 2f;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
50
KeyboardAudioVisualizer/AudioCapture/CSCoreAudioInput.cs
Normal file
50
KeyboardAudioVisualizer/AudioCapture/CSCoreAudioInput.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using CSCore;
|
||||
using CSCore.SoundIn;
|
||||
using CSCore.Streams;
|
||||
|
||||
namespace KeyboardAudioVisualizer.AudioCapture
|
||||
{
|
||||
public class CSCoreAudioInput : IAudioInput
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private WasapiCapture _capture;
|
||||
private SoundInSource _soundInSource;
|
||||
private SingleBlockNotificationStream _stream;
|
||||
|
||||
private readonly float[] _readBuffer = new float[2048];
|
||||
|
||||
public int SampleRate => _soundInSource?.WaveFormat?.SampleRate ?? -1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event
|
||||
|
||||
public event AudioData DataAvailable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_capture = new WasapiLoopbackCapture();
|
||||
_capture.Initialize();
|
||||
_soundInSource = new SoundInSource(_capture) { FillWithZeros = false };
|
||||
_stream = new SingleBlockNotificationStream(_soundInSource.ToStereo().ToSampleSource());
|
||||
|
||||
_soundInSource.DataAvailable += OnSoundDataAvailable;
|
||||
|
||||
_capture.Start();
|
||||
}
|
||||
|
||||
private void OnSoundDataAvailable(object sender, DataAvailableEventArgs dataAvailableEventArgs)
|
||||
{
|
||||
int readCount;
|
||||
while ((readCount = _stream.Read(_readBuffer, 0, _readBuffer.Length)) > 0)
|
||||
DataAvailable?.Invoke(_readBuffer, 0, readCount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
13
KeyboardAudioVisualizer/AudioCapture/IAudioInput.cs
Normal file
13
KeyboardAudioVisualizer/AudioCapture/IAudioInput.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace KeyboardAudioVisualizer.AudioCapture
|
||||
{
|
||||
public delegate void AudioData(float[] data, int offset, int count);
|
||||
|
||||
public interface IAudioInput
|
||||
{
|
||||
int SampleRate { get; }
|
||||
|
||||
event AudioData DataAvailable;
|
||||
|
||||
void Initialize();
|
||||
}
|
||||
}
|
||||
77
KeyboardAudioVisualizer/AudioProcessing/AudioProcessor.cs
Normal file
77
KeyboardAudioVisualizer/AudioProcessing/AudioProcessor.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using KeyboardAudioVisualizer.AudioCapture;
|
||||
using KeyboardAudioVisualizer.AudioProcessing.Equalizer;
|
||||
using KeyboardAudioVisualizer.AudioProcessing.Spectrum;
|
||||
using KeyboardAudioVisualizer.AudioProcessing.VisualizationPRovider;
|
||||
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing
|
||||
{
|
||||
public class AudioProcessor
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const int MAXIMUM_UPDATE_RATE = 40; // We won't allow to change the FPS beyond this
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties & Fields
|
||||
|
||||
public static AudioProcessor Instance { get; private set; }
|
||||
|
||||
private AudioBuffer _audioBuffer;
|
||||
private IAudioInput _audioInput;
|
||||
private ISpectrumProvider _spectrumProvider;
|
||||
public IVisualizationProvider PrimaryVisualizationProvider { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
private AudioProcessor() { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void Update()
|
||||
{
|
||||
_spectrumProvider.Update();
|
||||
PrimaryVisualizationProvider.Update();
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
if (Instance != null) return;
|
||||
|
||||
Instance = new AudioProcessor();
|
||||
Instance.InitializeInstance();
|
||||
}
|
||||
|
||||
private void InitializeInstance()
|
||||
{
|
||||
_audioInput = new CSCoreAudioInput();
|
||||
_audioInput.Initialize();
|
||||
|
||||
_audioBuffer = new AudioBuffer(CalculateSampleSize(_audioInput.SampleRate, MAXIMUM_UPDATE_RATE));
|
||||
_audioInput.DataAvailable += (data, offset, count) => _audioBuffer.Put(data, offset, count);
|
||||
|
||||
_spectrumProvider = new FourierSpectrumProvider(_audioBuffer, _audioInput.SampleRate);
|
||||
_spectrumProvider.Initialize();
|
||||
|
||||
//TODO DarthAffe 03.08.2017: Initialize correctly; Settings
|
||||
MultiBandEqualizer equalizer = new MultiBandEqualizer { [0] = -5, [1] = -1, [2] = 0, [3] = 2, [4] = 2 };
|
||||
PrimaryVisualizationProvider = new FrequencyBarsVisualizationProvider(new FrequencyBarsVisualizationProviderConfiguration { Scale = 38 }, _spectrumProvider) { Equalizer = equalizer };
|
||||
PrimaryVisualizationProvider.Initialize();
|
||||
}
|
||||
|
||||
private int CalculateSampleSize(int sampleRate, int maximumUpdateRate)
|
||||
{
|
||||
int sampleSize = 2;
|
||||
while ((sampleSize * maximumUpdateRate) < sampleRate)
|
||||
sampleSize <<= 1;
|
||||
|
||||
return sampleSize;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.Equalizer
|
||||
{
|
||||
public interface IEqualizer
|
||||
{
|
||||
bool IsEnabled { get; set; }
|
||||
float[] CalculateValues(int values);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.Equalizer
|
||||
{
|
||||
public class MultiBandEqualizer : IEqualizer
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private float[] _values;
|
||||
|
||||
private readonly List<Band> _bands = new List<Band>();
|
||||
|
||||
public int Bands => _bands.Count;
|
||||
public float this[int band]
|
||||
{
|
||||
get => _bands[band].Value;
|
||||
set
|
||||
{
|
||||
_bands[band].Value = value;
|
||||
RecalculateValues();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public MultiBandEqualizer(int bands = 5)
|
||||
{
|
||||
if (bands < 2) throw new ArgumentOutOfRangeException(nameof(bands), "There must be at least two bands for an working equalizer!");
|
||||
|
||||
float reference = (float)Math.Log(bands);
|
||||
|
||||
for (int i = bands - 1; i >= 0; i--)
|
||||
{
|
||||
Band band = new Band((reference - (float)Math.Log(i + 1)) / reference);
|
||||
_bands.Add(band);
|
||||
}
|
||||
|
||||
CalculateValues(1);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public float[] CalculateValues(int values)
|
||||
{
|
||||
if ((_values == null) || (_values.Length != values))
|
||||
{
|
||||
_values = new float[values];
|
||||
RecalculateValues();
|
||||
}
|
||||
|
||||
return _values;
|
||||
}
|
||||
|
||||
private void RecalculateValues()
|
||||
{
|
||||
float width = _values.Length;
|
||||
for (int i = 0; i < _values.Length; i++)
|
||||
{
|
||||
float offset = (i / width);
|
||||
|
||||
Band bandBefore = _bands.Last(n => n.Offset <= offset);
|
||||
Band bandAfter = _bands.First(n => n.Offset >= offset);
|
||||
|
||||
offset = bandAfter.Offset <= 0 ? 0 : (offset - bandBefore.Offset) / (bandAfter.Offset - bandBefore.Offset);
|
||||
|
||||
float value = (float)((3.0 * (offset * offset)) - (2.0 * (offset * offset * offset)));
|
||||
_values[i] = bandBefore.Value + (value * (bandAfter.Value - bandBefore.Value));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Data
|
||||
|
||||
private class Band
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public float Offset { get; set; }
|
||||
public float Value { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public Band(float offset, float value = 0)
|
||||
{
|
||||
this.Offset = offset;
|
||||
this.Value = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing
|
||||
{
|
||||
public interface IAudioProcessor
|
||||
{
|
||||
void Initialize();
|
||||
void Update();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
using KeyboardAudioVisualizer.AudioCapture;
|
||||
using MathNet.Numerics;
|
||||
using MathNet.Numerics.IntegralTransforms;
|
||||
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.Spectrum
|
||||
{
|
||||
public class FourierSpectrumProvider : ISpectrumProvider
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly AudioBuffer _audioBuffer;
|
||||
|
||||
private float[] _sampleData;
|
||||
private double[] _hamming;
|
||||
|
||||
private float[] _spectrum;
|
||||
public float[] Spectrum => _spectrum;
|
||||
|
||||
public int SampleRate { get; private set; }
|
||||
public float Resolution { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public FourierSpectrumProvider(AudioBuffer audioBuffer, int sampleRate)
|
||||
{
|
||||
this._audioBuffer = audioBuffer;
|
||||
this.SampleRate = sampleRate;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_hamming = Window.Hamming(_audioBuffer.Size);
|
||||
_sampleData = new float[_audioBuffer.Size];
|
||||
_spectrum = new float[_audioBuffer.Size / 2];
|
||||
Resolution = (float)SampleRate / (float)_audioBuffer.Size;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
_audioBuffer.CopyMixInto(ref _sampleData, 0);
|
||||
ApplyHamming(ref _sampleData);
|
||||
CreateSpectrum(ref _sampleData);
|
||||
}
|
||||
|
||||
private void ApplyHamming(ref float[] data)
|
||||
{
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
data[i] = (float)(data[i] * _hamming[i]);
|
||||
}
|
||||
|
||||
private void CreateSpectrum(ref float[] data)
|
||||
{
|
||||
Complex32[] complexData = CreateComplexData(ref data);
|
||||
Fourier.Forward(complexData, FourierOptions.NoScaling);
|
||||
for (int i = 0; i < _spectrum.Length; i++)
|
||||
{
|
||||
Complex32 fourierData = complexData[i];
|
||||
_spectrum[i] = (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);
|
||||
|
||||
return complexData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.Spectrum
|
||||
{
|
||||
public interface ISpectrumProvider : IAudioProcessor
|
||||
{
|
||||
float[] Spectrum { get; }
|
||||
int SampleRate { get; }
|
||||
float Resolution { get; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,183 @@
|
||||
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 class FrequencyBarsVisualizationProviderConfiguration : AbstractConfiguration
|
||||
{
|
||||
private int _bars = 48;
|
||||
public int Bars
|
||||
{
|
||||
get => _bars;
|
||||
set => SetProperty(ref _bars, value);
|
||||
}
|
||||
|
||||
private double _smoothing = 8;
|
||||
public double Smoothing
|
||||
{
|
||||
get => _smoothing;
|
||||
set => SetProperty(ref _smoothing, value);
|
||||
}
|
||||
|
||||
private double _minFrequency = 100;
|
||||
public double MinFrequency
|
||||
{
|
||||
get => _minFrequency;
|
||||
set => SetProperty(ref _minFrequency, value);
|
||||
}
|
||||
|
||||
private double _maxFrequency = 15000;
|
||||
public double MaxFrequency
|
||||
{
|
||||
get => _maxFrequency;
|
||||
set => SetProperty(ref _maxFrequency, value);
|
||||
}
|
||||
|
||||
private double _scale = 20;
|
||||
public double Scale
|
||||
{
|
||||
get => _scale;
|
||||
set => SetProperty(ref _scale, value);
|
||||
}
|
||||
|
||||
private int _emphasisePeaks = 1;
|
||||
public int EmphasisePeaks
|
||||
{
|
||||
get => _emphasisePeaks;
|
||||
set => SetProperty(ref _emphasisePeaks, value);
|
||||
}
|
||||
|
||||
private double _gamma = 3;
|
||||
public double Gamma
|
||||
{
|
||||
get => _gamma;
|
||||
set => SetProperty(ref _gamma, value);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class FrequencyBarsVisualizationProvider : IVisualizationProvider
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly FrequencyBarsVisualizationProviderConfiguration _configuration;
|
||||
private readonly ISpectrumProvider _spectrumProvider;
|
||||
|
||||
private int _frequencySkipCount;
|
||||
private int _frequencyCount;
|
||||
private double _smoothingFactor;
|
||||
private double _scalingValue;
|
||||
|
||||
public IEqualizer Equalizer { get; set; }
|
||||
public float[] VisualizationData { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public FrequencyBarsVisualizationProvider(FrequencyBarsVisualizationProviderConfiguration configuration, ISpectrumProvider spectrumProvider)
|
||||
{
|
||||
this._configuration = configuration;
|
||||
this._spectrumProvider = spectrumProvider;
|
||||
|
||||
configuration.PropertyChanged += (sender, args) => RecalculateConfigValues(args.PropertyName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void Initialize() => RecalculateConfigValues(null);
|
||||
|
||||
private void RecalculateConfigValues(string changedPropertyName)
|
||||
{
|
||||
if ((changedPropertyName == null) || (changedPropertyName == nameof(FrequencyBarsVisualizationProviderConfiguration.Bars)))
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
float[] spectrum = _spectrumProvider.Spectrum.Skip(_frequencySkipCount).Take(_frequencyCount).ToArray();
|
||||
|
||||
int startFrequency = 0;
|
||||
for (int i = 0; i < VisualizationData.Length; 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;
|
||||
|
||||
for (int j = 0; j < bandWidth; j++)
|
||||
{
|
||||
float power = spectrum[Math.Min(spectrum.Length - 1, startFrequency + j)];
|
||||
binPower = Math.Max(power, binPower);
|
||||
}
|
||||
|
||||
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 = Math.Log(binPower);
|
||||
binPower = Math.Max(0, binPower);
|
||||
|
||||
if (_configuration.EmphasisePeaks == 1)
|
||||
{
|
||||
binPower *= binPower;
|
||||
binPower *= 0.15;
|
||||
}
|
||||
if (_configuration.EmphasisePeaks == 2)
|
||||
binPower *= binPower * binPower;
|
||||
else
|
||||
binPower *= 40;
|
||||
|
||||
VisualizationData[i] = (float)((VisualizationData[i] * _smoothingFactor) + (binPower * _scalingValue * (1.0 - _smoothingFactor)));
|
||||
if (double.IsNaN(VisualizationData[i])) VisualizationData[i] = 0;
|
||||
|
||||
startFrequency = endFrequency;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
namespace KeyboardAudioVisualizer.AudioProcessing.VisualizationPRovider
|
||||
{
|
||||
public interface IVisualizationProvider : IAudioProcessor
|
||||
{
|
||||
float[] VisualizationData { get; }
|
||||
}
|
||||
}
|
||||
42
KeyboardAudioVisualizer/Brushes/FrequencyBarsBrush.cs
Normal file
42
KeyboardAudioVisualizer/Brushes/FrequencyBarsBrush.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using KeyboardAudioVisualizer.AudioProcessing.VisualizationPRovider;
|
||||
using RGB.NET.Brushes;
|
||||
using RGB.NET.Brushes.Gradients;
|
||||
using RGB.NET.Core;
|
||||
using Color = RGB.NET.Core.Color;
|
||||
using Rectangle = RGB.NET.Core.Rectangle;
|
||||
|
||||
namespace KeyboardAudioVisualizer.Brushes
|
||||
{
|
||||
public class FrequencyBarsBrush : LinearGradientBrush
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly IVisualizationProvider _visualizationProvider;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public FrequencyBarsBrush(IVisualizationProvider visualizationProvider, IGradient gradient)
|
||||
: base(gradient)
|
||||
{
|
||||
this._visualizationProvider = visualizationProvider;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
protected override Color GetColorAtPoint(Rectangle rectangle, BrushRenderTarget renderTarget)
|
||||
{
|
||||
int barSampleIndex = (int)Math.Floor(_visualizationProvider.VisualizationData.Length * (renderTarget.Point.X / (rectangle.Location.X + rectangle.Size.Width)));
|
||||
double curBarHeight = 1.0 - Math.Max(0f, _visualizationProvider.VisualizationData[barSampleIndex]);
|
||||
double verticalPos = (renderTarget.Point.Y / rectangle.Size.Height);
|
||||
|
||||
return curBarHeight <= verticalPos ? base.GetColorAtPoint(rectangle, renderTarget) : Color.Transparent;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace KeyboardAudioVisualizer.Configuration
|
||||
{
|
||||
public class AbstractConfiguration : INotifyPropertyChanged
|
||||
{
|
||||
#region Events
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
|
||||
{
|
||||
if ((typeof(T) == typeof(double)) || (typeof(T) == typeof(float)))
|
||||
{
|
||||
if (Math.Abs((double)(object)storage - (double)(object)value) < 0.000001) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Equals(storage, value)) return false;
|
||||
}
|
||||
|
||||
storage = value;
|
||||
// ReSharper disable once ExplicitCallerInfoArgument
|
||||
OnPropertyChanged(propertyName);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
14
KeyboardAudioVisualizer/Helper/MathHelper.cs
Normal file
14
KeyboardAudioVisualizer/Helper/MathHelper.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace KeyboardAudioVisualizer.Helper
|
||||
{
|
||||
public static class MathHelper
|
||||
{
|
||||
#region Methods
|
||||
|
||||
public static double Clamp(double value, double min, double max) => Math.Max(min, Math.Min(max / 2.0, value));
|
||||
public static float Clamp(float value, float min, float max) => (float)Clamp((double)value, min, max);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -63,6 +63,7 @@
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Core" />
|
||||
@ -86,9 +87,23 @@
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="ApplicationManager.cs" />
|
||||
<Compile Include="AudioCapture\AudioBuffer.cs" />
|
||||
<Compile Include="AudioCapture\CSCoreAudioInput.cs" />
|
||||
<Compile Include="AudioCapture\IAudioInput.cs" />
|
||||
<Compile Include="AudioProcessing\AudioProcessor.cs" />
|
||||
<Compile Include="AudioProcessing\Equalizer\IEqualizer.cs" />
|
||||
<Compile Include="AudioProcessing\Equalizer\MultiBandEqualizer.cs" />
|
||||
<Compile Include="AudioProcessing\IAudioProcessor.cs" />
|
||||
<Compile Include="AudioProcessing\Spectrum\ISpectrumProvider.cs" />
|
||||
<Compile Include="AudioProcessing\Spectrum\FourierSpectrumProvider.cs" />
|
||||
<Compile Include="AudioProcessing\VisualizationPRovider\FrequencyBarsVisualizationProvider.cs" />
|
||||
<Compile Include="AudioProcessing\VisualizationPRovider\IVisualizationProvider.cs" />
|
||||
<Compile Include="Brushes\FrequencyBarsBrush.cs" />
|
||||
<Compile Include="Configuration\AbstractConfiguration.cs" />
|
||||
<Compile Include="Controls\ImageButton.cs" />
|
||||
<Compile Include="Helper\ActionCommand.cs" />
|
||||
<Compile Include="Helper\ExceptionExtension.cs" />
|
||||
<Compile Include="Helper\MathHelper.cs" />
|
||||
<Compile Include="Settings.cs" />
|
||||
<Compile Include="Controls\BlurredDecorationWindow.cs" />
|
||||
<Compile Include="Styles\CachedResourceDictionary.cs" />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user