From 9dcf82e6aabc9151960deb81bafba20d087f850c Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 13 Aug 2017 11:57:23 +0200 Subject: [PATCH] Added Equalizer-UI --- .../Equalizer/EqualizerBand.cs | 7 +- .../AudioProcessing/Equalizer/IEqualizer.cs | 3 + .../Equalizer/MultiBandEqualizer.cs | 65 ++++++--- .../FrequencyBarsVisualizationProvider.cs | 16 ++- .../EqualizerBandsToPointsConverter.cs | 60 +++++++++ .../Converter/OffsetToPosXConverter.cs | 25 ++-- .../Converter/ValueToPosYConverter.cs | 20 ++- KeyboardAudioVisualizer/Helper/WPFHelper.cs | 32 +++++ .../KeyboardAudioVisualizer.csproj | 8 +- .../Visualization/EqualizerVisualization.xaml | 127 ++++++++++++++++++ .../UI/Visualization/EqualizerVisualizer.cs | 124 ++++++++++------- .../FrequencyBarsVisualization.xaml | 80 +---------- 12 files changed, 392 insertions(+), 175 deletions(-) create mode 100644 KeyboardAudioVisualizer/Converter/EqualizerBandsToPointsConverter.cs create mode 100644 KeyboardAudioVisualizer/Helper/WPFHelper.cs create mode 100644 KeyboardAudioVisualizer/UI/Visualization/EqualizerVisualization.xaml diff --git a/KeyboardAudioVisualizer/AudioProcessing/Equalizer/EqualizerBand.cs b/KeyboardAudioVisualizer/AudioProcessing/Equalizer/EqualizerBand.cs index 6d27b76..0e57aeb 100644 --- a/KeyboardAudioVisualizer/AudioProcessing/Equalizer/EqualizerBand.cs +++ b/KeyboardAudioVisualizer/AudioProcessing/Equalizer/EqualizerBand.cs @@ -1,4 +1,5 @@ -using RGB.NET.Core; +using KeyboardAudioVisualizer.Helper; +using RGB.NET.Core; namespace KeyboardAudioVisualizer.AudioProcessing.Equalizer { @@ -13,7 +14,7 @@ namespace KeyboardAudioVisualizer.AudioProcessing.Equalizer set { if (!IsFixedOffset) - SetProperty(ref _offset, value); + SetProperty(ref _offset, float.IsNaN(value) ? 0 : MathHelper.Clamp(value, 0, 1)); } } @@ -21,7 +22,7 @@ namespace KeyboardAudioVisualizer.AudioProcessing.Equalizer public float Value { get => _value; - set => SetProperty(ref _value, value); + set => SetProperty(ref _value, float.IsNaN(value) ? 0 : MathHelper.Clamp(value, -1, 1)); } public bool IsFixedOffset { get; } diff --git a/KeyboardAudioVisualizer/AudioProcessing/Equalizer/IEqualizer.cs b/KeyboardAudioVisualizer/AudioProcessing/Equalizer/IEqualizer.cs index dca2d9c..3c693ca 100644 --- a/KeyboardAudioVisualizer/AudioProcessing/Equalizer/IEqualizer.cs +++ b/KeyboardAudioVisualizer/AudioProcessing/Equalizer/IEqualizer.cs @@ -9,5 +9,8 @@ namespace KeyboardAudioVisualizer.AudioProcessing.Equalizer ObservableCollection Bands { get; } float[] CalculateValues(int count); + + EqualizerBand AddBand(float offset, float modification); + void RemoveBandBand(EqualizerBand band); } } diff --git a/KeyboardAudioVisualizer/AudioProcessing/Equalizer/MultiBandEqualizer.cs b/KeyboardAudioVisualizer/AudioProcessing/Equalizer/MultiBandEqualizer.cs index 4cbb630..ca3aa21 100644 --- a/KeyboardAudioVisualizer/AudioProcessing/Equalizer/MultiBandEqualizer.cs +++ b/KeyboardAudioVisualizer/AudioProcessing/Equalizer/MultiBandEqualizer.cs @@ -1,15 +1,18 @@ -using System.Collections.ObjectModel; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; +using RGB.NET.Core; namespace KeyboardAudioVisualizer.AudioProcessing.Equalizer { - public class MultiBandEqualizer : IEqualizer + public class MultiBandEqualizer : AbstractBindable, IEqualizer { #region Properties & Fields public ObservableCollection Bands { get; } = new ObservableCollection(); - private float[] _values; + private readonly Dictionary _values = new Dictionary(); public bool IsEnabled { get; set; } = true; @@ -27,40 +30,64 @@ namespace KeyboardAudioVisualizer.AudioProcessing.Equalizer #region Methods - public void AddBand(float frequency, float modification, bool isFixedFrequency = false) + public EqualizerBand AddBand(float offset, float modification) => AddBand(offset, modification, false); + + public EqualizerBand AddBand(float offset, float modification, bool isFixedFrequency) { - EqualizerBand band = new EqualizerBand(frequency, modification, isFixedFrequency); + EqualizerBand band = new EqualizerBand(offset, modification, isFixedFrequency); band.PropertyChanged += (sender, args) => InvalidateCache(); Bands.Add(band); InvalidateCache(); + + return band; } - public float[] CalculateValues(int values) + public void RemoveBandBand(EqualizerBand band) { - if ((_values == null) || (_values.Length != values)) + if (!band.IsFixedOffset) + Bands.Remove(band); + + InvalidateCache(); + } + + public float[] CalculateValues(int count) + { + if (!_values.TryGetValue(count, out float[] values)) { - _values = new float[values]; - RecalculateValues(); + values = RecalculateValues(count); + _values[count] = values; } - return _values; + return values; } - private void RecalculateValues() + private float[] RecalculateValues(int count) { - float width = _values.Length; - for (int i = 0; i < _values.Length; i++) + float[] values = new float[count]; + + List orderedBands = Bands.OrderBy(x => x.Offset).ToList(); + + for (int i = 0; i < count; i++) { - float offset = (i / width); - EqualizerBand bandBefore = Bands.Last(n => n.Offset <= offset); - EqualizerBand bandAfter = Bands.First(n => n.Offset >= offset); - offset = bandAfter.Offset <= 0 ? 0 : (offset - bandBefore.Offset) / (bandAfter.Offset - bandBefore.Offset); + float offset = (i / (float)count); + EqualizerBand bandBefore = orderedBands.Last(n => n.Offset <= offset); + EqualizerBand bandAfter = orderedBands.First(n => n.Offset >= offset); + offset = (bandAfter.Offset <= 0) || (Math.Abs(bandAfter.Offset - bandBefore.Offset) < 0.0001) + ? 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)); + values[i] = bandBefore.Value + (value * (bandAfter.Value - bandBefore.Value)); } + + return values; } - private void InvalidateCache() => _values = null; + private void InvalidateCache() + { + _values.Clear(); + + // ReSharper disable once ExplicitCallerInfoArgument + OnPropertyChanged(nameof(Bands)); + } #endregion } diff --git a/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/FrequencyBarsVisualizationProvider.cs b/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/FrequencyBarsVisualizationProvider.cs index 6007b9b..d337352 100644 --- a/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/FrequencyBarsVisualizationProvider.cs +++ b/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/FrequencyBarsVisualizationProvider.cs @@ -2,6 +2,7 @@ using KeyboardAudioVisualizer.AudioProcessing.Equalizer; using KeyboardAudioVisualizer.AudioProcessing.Spectrum; using KeyboardAudioVisualizer.Configuration; +using KeyboardAudioVisualizer.Helper; namespace KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider { @@ -131,10 +132,21 @@ namespace KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider for (int i = 0; i < spectrum.BandCount; i++) { - double binPower = Math.Max(0, 20 * Math.Log10(GetBandValue(spectrum[i]))); + double binPower = GetBandValue(spectrum[i]); if (equalizerValues != null) - binPower += equalizerValues[i]; + { + float equalizerValue = equalizerValues[i]; + equalizerValue *= 10; //TODO DarthAffe 13.08.2017: Equalizer-Scale through setting? + if (Math.Abs(equalizerValue) > 0.000001) + { + bool lower = equalizerValue < 0; + equalizerValue = 1 + (equalizerValue * equalizerValue); + binPower *= lower ? 1f / equalizerValue : equalizerValue; + } + } + + binPower = Math.Max(0, 20 * Math.Log10(binPower)); binPower = Math.Max(0, binPower); binPower /= _configuration.ReferenceLevel; diff --git a/KeyboardAudioVisualizer/Converter/EqualizerBandsToPointsConverter.cs b/KeyboardAudioVisualizer/Converter/EqualizerBandsToPointsConverter.cs new file mode 100644 index 0000000..6e8e651 --- /dev/null +++ b/KeyboardAudioVisualizer/Converter/EqualizerBandsToPointsConverter.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Linq; +using System.Windows; +using System.Windows.Data; +using System.Windows.Media; +using KeyboardAudioVisualizer.AudioProcessing.Equalizer; + +namespace KeyboardAudioVisualizer.Converter +{ + public class EqualizerBandsToPointsConverter : IMultiValueConverter + { + #region Methods + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + PointCollection points = new PointCollection(); + + if ((values.Length != 4) || (parameter == DependencyProperty.UnsetValue) + || (values[0] == null) || (values[0] == DependencyProperty.UnsetValue) + || (values[1] == null) || (values[1] == DependencyProperty.UnsetValue) //HACK DarthAffe 13.08.2017: I need this only to update the binding + || (values[2] == null) || (values[2] == DependencyProperty.UnsetValue) + || (values[3] == null) || (values[3] == DependencyProperty.UnsetValue)) + return points; + + IEqualizer equalizer = (IEqualizer)values[0]; + double width = (double)values[2]; + double height = (double)values[3]; + int valueCount = int.Parse(parameter.ToString()); + double halfHeight = height / 2.0; + + List<(float offset, float value)> pointValues = equalizer.Bands.Select(b => (b.Offset, b.Value)).ToList(); + float[] calculatedValues = equalizer.CalculateValues(valueCount); + for (int i = 0; i < calculatedValues.Length; i++) + pointValues.Add(((float)i / calculatedValues.Length, calculatedValues[i])); + + foreach ((float offset, float value) in pointValues.OrderBy(x => x.offset)) + points.Add(new Point(offset * width, GetPosY(value, halfHeight))); + + return points; + } + + private double GetPosY(float offset, double halfHeight) + { + if (offset < 0) + return halfHeight + (-offset * halfHeight); + + if (offset > 0) + return halfHeight - (offset * halfHeight); + + return halfHeight; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotSupportedException(); + + #endregion + } +} diff --git a/KeyboardAudioVisualizer/Converter/OffsetToPosXConverter.cs b/KeyboardAudioVisualizer/Converter/OffsetToPosXConverter.cs index 8aa97a8..28ba94f 100644 --- a/KeyboardAudioVisualizer/Converter/OffsetToPosXConverter.cs +++ b/KeyboardAudioVisualizer/Converter/OffsetToPosXConverter.cs @@ -5,31 +5,22 @@ using System.Windows.Data; namespace KeyboardAudioVisualizer.Converter { - public class ValueToPosYConverter : IMultiValueConverter + public class OffsetToPosXConverter : IMultiValueConverter { #region Methods public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { - if ((values.Length != 3) || (values[0] == null) || (values[0] == DependencyProperty.UnsetValue) - || (values[1] == null) || (values[1] == DependencyProperty.UnsetValue) - || (values[2] == null) || (values[2] == DependencyProperty.UnsetValue)) + if ((values.Length != 2) || (parameter == DependencyProperty.UnsetValue) + || (values[0] == null) || (values[0] == DependencyProperty.UnsetValue) + || (values[1] == null) || (values[1] == DependencyProperty.UnsetValue)) return 0; - float val = (float)values[0]; - double height = (double)values[1]; - double reference = (double)values[2]; + float offset = (float)values[0]; + double width = (double)values[1]; + double correction = double.Parse(parameter.ToString()); - double halfHeight = height / 2.0; - - double offset = val / reference; - if (offset < 0) - return halfHeight + (-offset * halfHeight); - - if (offset > 0) - return halfHeight - (offset * halfHeight); - - return halfHeight; + return (offset * width) - correction; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException(); diff --git a/KeyboardAudioVisualizer/Converter/ValueToPosYConverter.cs b/KeyboardAudioVisualizer/Converter/ValueToPosYConverter.cs index 19a975c..ad72ed1 100644 --- a/KeyboardAudioVisualizer/Converter/ValueToPosYConverter.cs +++ b/KeyboardAudioVisualizer/Converter/ValueToPosYConverter.cs @@ -5,20 +5,30 @@ using System.Windows.Data; namespace KeyboardAudioVisualizer.Converter { - public class OffsetToPosXConverter : IMultiValueConverter + public class ValueToPosYConverter : IMultiValueConverter { #region Methods public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { - if ((values.Length != 2) || (values[0] == null) || (values[0] == DependencyProperty.UnsetValue) - || (values[1] == null) || (values[1] == DependencyProperty.UnsetValue)) + if ((values.Length != 2) || (parameter == DependencyProperty.UnsetValue) + || (values[0] == null) || (values[0] == DependencyProperty.UnsetValue) + || (values[1] == null) || (values[1] == DependencyProperty.UnsetValue)) return 0; float offset = (float)values[0]; - double width = (double)values[1]; + double height = (double)values[1]; + double correction = double.Parse(parameter.ToString()); - return offset * width; + double halfHeight = height / 2.0; + + if (offset < 0) + return (halfHeight + (-offset * halfHeight)) - correction; + + if (offset > 0) + return (halfHeight - (offset * halfHeight)) - correction; + + return halfHeight - correction; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException(); diff --git a/KeyboardAudioVisualizer/Helper/WPFHelper.cs b/KeyboardAudioVisualizer/Helper/WPFHelper.cs new file mode 100644 index 0000000..c779697 --- /dev/null +++ b/KeyboardAudioVisualizer/Helper/WPFHelper.cs @@ -0,0 +1,32 @@ +using System.Windows; +using System.Windows.Media; + +namespace KeyboardAudioVisualizer.Helper +{ + public static class WPFHelper + { + #region Methods + + public static T GetVisualChild(this DependencyObject parent) + where T : Visual + { + T child = default(T); + + int numVisuals = VisualTreeHelper.GetChildrenCount(parent); + for (int i = 0; i < numVisuals; i++) + { + Visual v = (Visual)VisualTreeHelper.GetChild(parent, i); + child = v as T; + + if (child == null) + child = GetVisualChild(v); + + if (child != null) + break; + } + return child; + } + + #endregion + } +} diff --git a/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj b/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj index b252c77..274534d 100644 --- a/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj +++ b/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj @@ -131,13 +131,15 @@ + - + + @@ -248,6 +250,10 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + MSBuild:Compile Designer diff --git a/KeyboardAudioVisualizer/UI/Visualization/EqualizerVisualization.xaml b/KeyboardAudioVisualizer/UI/Visualization/EqualizerVisualization.xaml new file mode 100644 index 0000000..fbd2cdc --- /dev/null +++ b/KeyboardAudioVisualizer/UI/Visualization/EqualizerVisualization.xaml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/KeyboardAudioVisualizer/UI/Visualization/EqualizerVisualizer.cs b/KeyboardAudioVisualizer/UI/Visualization/EqualizerVisualizer.cs index 0b59aaa..e8f5210 100644 --- a/KeyboardAudioVisualizer/UI/Visualization/EqualizerVisualizer.cs +++ b/KeyboardAudioVisualizer/UI/Visualization/EqualizerVisualizer.cs @@ -1,10 +1,13 @@ using System.Windows; using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; using KeyboardAudioVisualizer.AudioProcessing.Equalizer; +using KeyboardAudioVisualizer.Helper; namespace KeyboardAudioVisualizer.UI.Visualization { - //[TemplatePart(Name = "PART_Grips", Type = typeof(Canvas))] + [TemplatePart(Name = "PART_Grips", Type = typeof(ItemsControl))] public class EqualizerVisualizer : Control { #region DependencyProperties @@ -19,82 +22,103 @@ namespace KeyboardAudioVisualizer.UI.Visualization set => SetValue(EqualizerProperty, value); } - public static readonly DependencyProperty ReferenceLevelProperty = DependencyProperty.Register( - "ReferenceLevel", typeof(double), typeof(EqualizerVisualizer), new PropertyMetadata(default(double))); - - public double ReferenceLevel - { - get => (double)GetValue(ReferenceLevelProperty); - set => SetValue(ReferenceLevelProperty, value); - } - // ReSharper restore InconsistentNaming #endregion #region Properties & Fields - //private Canvas _canvas; + private ItemsControl _grips; + private EqualizerBand _draggingBand; #endregion #region Constructors - //public EqualizerVisualizer() - //{ - // SizeChanged += (sender, args) => Update(); - // Update(); - //} #endregion #region Methods - //public override void OnApplyTemplate() - //{ - // base.OnApplyTemplate(); + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); - // _canvas = GetTemplateChild("PART_Grips") as Canvas; - //} + _grips = GetTemplateChild("PART_Grips") as ItemsControl; + } - //private static void EqualizerChanged(DependencyObject dependencyObject, - // DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) - //{ - // EqualizerVisualizer visualizer = dependencyObject as EqualizerVisualizer; - // if (visualizer == null) return; + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonDown(e); - // void BandsChanged(object sender, NotifyCollectionChangedEventArgs args) => visualizer.Update(); + EqualizerBand band = GetClickedBand(); + if (band != null) + _draggingBand = band; + } - // if (dependencyPropertyChangedEventArgs.OldValue is IEqualizer oldEqualizer) - // oldEqualizer.Bands.CollectionChanged -= BandsChanged; + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonUp(e); - // if (dependencyPropertyChangedEventArgs.NewValue is IEqualizer newEqualizer) - // newEqualizer.Bands.CollectionChanged += BandsChanged; - //} + _draggingBand = null; + } - //private void Update() - //{ - // if (_canvas == null) return; + protected override void OnMouseLeave(MouseEventArgs e) + { + base.OnMouseLeave(e); - // void OnBandChanged(object sender, PropertyChangedEventArgs args) => Update(); + _draggingBand = null; + } - // foreach (object child in _canvas.Children) - // { - // EqualizerBand band = (child as ContentControl)?.Content as EqualizerBand; - // if (band == null) continue; - // band.PropertyChanged -= OnBandChanged; - // } + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); - // _canvas.Children.Clear(); + if (_draggingBand == null) return; - // foreach (EqualizerBand band in Equalizer.Bands) - // { - // ContentControl ctrl = new ContentControl(); + UpdateBand(_draggingBand, e.GetPosition(_grips)); + } - // ctrl.Content = band; + protected override void OnMouseRightButtonDown(MouseButtonEventArgs e) + { + base.OnMouseRightButtonDown(e); - // _canvas.Children.Add(ctrl); - // } - //} + if (_grips == null) return; + + EqualizerBand band = GetClickedBand(); + if (band == null) + { + EqualizerBand newBand = Equalizer.AddBand(0, 0); + UpdateBand(newBand, e.GetPosition(_grips)); + } + else + Equalizer.RemoveBandBand(band); + } + + private void UpdateBand(EqualizerBand band, Point position) + { + double halfHeight = _grips.ActualHeight / 2.0; + + band.Offset = (float)(position.X / _grips.ActualWidth); + band.Value = (float)(-(position.Y - halfHeight) / halfHeight); + } + + private EqualizerBand GetClickedBand() + { + ItemsPresenter itemsPresenter = _grips.GetVisualChild(); + if (itemsPresenter == null) return null; + + Panel panel = VisualTreeHelper.GetChild(itemsPresenter, 0) as Panel; + if (panel == null) return null; + + foreach (UIElement element in panel.Children) + if (element.IsMouseOver) + { + EqualizerBand band = ((element as ContentPresenter)?.Content as EqualizerBand); + if (band != null) return band; + } + + return null; + } #endregion } diff --git a/KeyboardAudioVisualizer/UI/Visualization/FrequencyBarsVisualization.xaml b/KeyboardAudioVisualizer/UI/Visualization/FrequencyBarsVisualization.xaml index e887486..6f75556 100644 --- a/KeyboardAudioVisualizer/UI/Visualization/FrequencyBarsVisualization.xaml +++ b/KeyboardAudioVisualizer/UI/Visualization/FrequencyBarsVisualization.xaml @@ -3,12 +3,12 @@ xmlns:styles="clr-namespace:KeyboardAudioVisualizer.Styles" xmlns:visualizationProvider="clr-namespace:KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider" xmlns:visualization="clr-namespace:KeyboardAudioVisualizer.UI.Visualization" - xmlns:equalizer="clr-namespace:KeyboardAudioVisualizer.AudioProcessing.Equalizer" xmlns:converter="clr-namespace:KeyboardAudioVisualizer.Converter"> + @@ -29,86 +29,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +