diff --git a/KeyboardAudioVisualizer/App.xaml.cs b/KeyboardAudioVisualizer/App.xaml.cs index 65acd1f..ec4a901 100644 --- a/KeyboardAudioVisualizer/App.xaml.cs +++ b/KeyboardAudioVisualizer/App.xaml.cs @@ -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) { diff --git a/KeyboardAudioVisualizer/ApplicationManager.cs b/KeyboardAudioVisualizer/ApplicationManager.cs index ee7a083..14db244 100644 --- a/KeyboardAudioVisualizer/ApplicationManager.cs +++ b/KeyboardAudioVisualizer/ApplicationManager.cs @@ -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(); diff --git a/KeyboardAudioVisualizer/AudioCapture/AudioBuffer.cs b/KeyboardAudioVisualizer/AudioCapture/AudioBuffer.cs new file mode 100644 index 0000000..8eff478 --- /dev/null +++ b/KeyboardAudioVisualizer/AudioCapture/AudioBuffer.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/AudioCapture/CSCoreAudioInput.cs b/KeyboardAudioVisualizer/AudioCapture/CSCoreAudioInput.cs new file mode 100644 index 0000000..e107287 --- /dev/null +++ b/KeyboardAudioVisualizer/AudioCapture/CSCoreAudioInput.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/AudioCapture/IAudioInput.cs b/KeyboardAudioVisualizer/AudioCapture/IAudioInput.cs new file mode 100644 index 0000000..74d64b0 --- /dev/null +++ b/KeyboardAudioVisualizer/AudioCapture/IAudioInput.cs @@ -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(); + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/AudioProcessor.cs b/KeyboardAudioVisualizer/AudioProcessing/AudioProcessor.cs new file mode 100644 index 0000000..c5a8385 --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/AudioProcessor.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/Equalizer/IEqualizer.cs b/KeyboardAudioVisualizer/AudioProcessing/Equalizer/IEqualizer.cs new file mode 100644 index 0000000..03427e8 --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/Equalizer/IEqualizer.cs @@ -0,0 +1,8 @@ +namespace KeyboardAudioVisualizer.AudioProcessing.Equalizer +{ + public interface IEqualizer + { + bool IsEnabled { get; set; } + float[] CalculateValues(int values); + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/Equalizer/MultiBandEqualizer.cs b/KeyboardAudioVisualizer/AudioProcessing/Equalizer/MultiBandEqualizer.cs new file mode 100644 index 0000000..6c77654 --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/Equalizer/MultiBandEqualizer.cs @@ -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 _bands = new List(); + + 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 + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/IAudioProcessor.cs b/KeyboardAudioVisualizer/AudioProcessing/IAudioProcessor.cs new file mode 100644 index 0000000..e91dccd --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/IAudioProcessor.cs @@ -0,0 +1,8 @@ +namespace KeyboardAudioVisualizer.AudioProcessing +{ + public interface IAudioProcessor + { + void Initialize(); + void Update(); + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/Spectrum/FourierSpectrumProvider.cs b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/FourierSpectrumProvider.cs new file mode 100644 index 0000000..b82fbcc --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/FourierSpectrumProvider.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/Spectrum/ISpectrumProvider.cs b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/ISpectrumProvider.cs new file mode 100644 index 0000000..14983db --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/Spectrum/ISpectrumProvider.cs @@ -0,0 +1,9 @@ +namespace KeyboardAudioVisualizer.AudioProcessing.Spectrum +{ + public interface ISpectrumProvider : IAudioProcessor + { + float[] Spectrum { get; } + int SampleRate { get; } + float Resolution { get; } + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/FrequencyBarsVisualizationProvider.cs b/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/FrequencyBarsVisualizationProvider.cs new file mode 100644 index 0000000..ae0c8b4 --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/FrequencyBarsVisualizationProvider.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/IVisualizationProvider.cs b/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/IVisualizationProvider.cs new file mode 100644 index 0000000..f6b742a --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/IVisualizationProvider.cs @@ -0,0 +1,7 @@ +namespace KeyboardAudioVisualizer.AudioProcessing.VisualizationPRovider +{ + public interface IVisualizationProvider : IAudioProcessor + { + float[] VisualizationData { get; } + } +} diff --git a/KeyboardAudioVisualizer/Brushes/FrequencyBarsBrush.cs b/KeyboardAudioVisualizer/Brushes/FrequencyBarsBrush.cs new file mode 100644 index 0000000..8f3df4e --- /dev/null +++ b/KeyboardAudioVisualizer/Brushes/FrequencyBarsBrush.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/Configuration/AbstractConfiguration.cs b/KeyboardAudioVisualizer/Configuration/AbstractConfiguration.cs new file mode 100644 index 0000000..65925b1 --- /dev/null +++ b/KeyboardAudioVisualizer/Configuration/AbstractConfiguration.cs @@ -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(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 + } +} diff --git a/KeyboardAudioVisualizer/Helper/MathHelper.cs b/KeyboardAudioVisualizer/Helper/MathHelper.cs new file mode 100644 index 0000000..6a34d38 --- /dev/null +++ b/KeyboardAudioVisualizer/Helper/MathHelper.cs @@ -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 + } +} diff --git a/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj b/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj index 326dc9d..08b2427 100644 --- a/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj +++ b/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj @@ -63,6 +63,7 @@ + @@ -86,9 +87,23 @@ Code + + + + + + + + + + + + + +