mirror of
https://github.com/DarthAffe/KeyboardAudioVisualizer.git
synced 2025-12-12 23:28: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.IO;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using Hardcodet.Wpf.TaskbarNotification;
|
using Hardcodet.Wpf.TaskbarNotification;
|
||||||
|
using KeyboardAudioVisualizer.AudioProcessing;
|
||||||
using KeyboardAudioVisualizer.Helper;
|
using KeyboardAudioVisualizer.Helper;
|
||||||
|
|
||||||
namespace KeyboardAudioVisualizer
|
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);
|
_taskbarIcon.ShowBalloonTip("Keyboard Audio-Visualizer is starting in the tray!", "Click on the icon to open the configuration.", BalloonIcon.Info);
|
||||||
}
|
}
|
||||||
ApplicationManager.Instance.Settings = settings;
|
ApplicationManager.Instance.Settings = settings;
|
||||||
|
|
||||||
|
AudioProcessor.Initialize();
|
||||||
|
ApplicationManager.Instance.InitializeDevices();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using KeyboardAudioVisualizer.AudioProcessing;
|
||||||
|
using KeyboardAudioVisualizer.Brushes;
|
||||||
using KeyboardAudioVisualizer.Helper;
|
using KeyboardAudioVisualizer.Helper;
|
||||||
using KeyboardAudioVisualizer.UI;
|
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
|
namespace KeyboardAudioVisualizer
|
||||||
{
|
{
|
||||||
@ -28,13 +35,37 @@ namespace KeyboardAudioVisualizer
|
|||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
|
|
||||||
private ApplicationManager()
|
private ApplicationManager() { }
|
||||||
{ }
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Methods
|
#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()
|
private void OpenConfiguration()
|
||||||
{
|
{
|
||||||
if (_configurationWindow == null) _configurationWindow = new ConfigurationWindow();
|
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" />
|
||||||
<Reference Include="System.Data" />
|
<Reference Include="System.Data" />
|
||||||
<Reference Include="System.Drawing" />
|
<Reference Include="System.Drawing" />
|
||||||
|
<Reference Include="System.Numerics" />
|
||||||
<Reference Include="System.Xml" />
|
<Reference Include="System.Xml" />
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
@ -86,9 +87,23 @@
|
|||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="ApplicationManager.cs" />
|
<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="Controls\ImageButton.cs" />
|
||||||
<Compile Include="Helper\ActionCommand.cs" />
|
<Compile Include="Helper\ActionCommand.cs" />
|
||||||
<Compile Include="Helper\ExceptionExtension.cs" />
|
<Compile Include="Helper\ExceptionExtension.cs" />
|
||||||
|
<Compile Include="Helper\MathHelper.cs" />
|
||||||
<Compile Include="Settings.cs" />
|
<Compile Include="Settings.cs" />
|
||||||
<Compile Include="Controls\BlurredDecorationWindow.cs" />
|
<Compile Include="Controls\BlurredDecorationWindow.cs" />
|
||||||
<Compile Include="Styles\CachedResourceDictionary.cs" />
|
<Compile Include="Styles\CachedResourceDictionary.cs" />
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user