diff --git a/.gitignore b/.gitignore index 940794e..d660209 100644 --- a/.gitignore +++ b/.gitignore @@ -286,3 +286,6 @@ __pycache__/ *.btm.cs *.odx.cs *.xsd.cs +/NuGet.Config +/NuGet.Config +/KeyboardAudioVisualizer/NuGet.Config diff --git a/KeyboardAudioVisualizer/App.xaml b/KeyboardAudioVisualizer/App.xaml index 083b6f0..8fc4ebe 100644 --- a/KeyboardAudioVisualizer/App.xaml +++ b/KeyboardAudioVisualizer/App.xaml @@ -17,7 +17,7 @@ MenuActivation="RightClick"> - + diff --git a/KeyboardAudioVisualizer/App.xaml.cs b/KeyboardAudioVisualizer/App.xaml.cs index ec4a901..2585ed0 100644 --- a/KeyboardAudioVisualizer/App.xaml.cs +++ b/KeyboardAudioVisualizer/App.xaml.cs @@ -51,7 +51,9 @@ namespace KeyboardAudioVisualizer { File.WriteAllText("error.log", $"[{DateTime.Now:G}] Exception!\r\n\r\nMessage:\r\n{ex.GetFullMessage()}\r\n\r\nStackTrace:\r\n{ex.StackTrace}\r\n\r\n"); MessageBox.Show("An error occured while starting the Keyboard Audio-Visualizer.\r\nPlease double check if SDK-support for your devices is enabled.\r\nMore information can be found in the error.log file in the application directory.", "Can't start Keyboard Audio-Visualizer."); - Shutdown(); + + try { ApplicationManager.Instance.ExitCommand.Execute(null); } + catch { Environment.Exit(0); } } } diff --git a/KeyboardAudioVisualizer/ApplicationManager.cs b/KeyboardAudioVisualizer/ApplicationManager.cs index 8fae58d..0e9d3d6 100644 --- a/KeyboardAudioVisualizer/ApplicationManager.cs +++ b/KeyboardAudioVisualizer/ApplicationManager.cs @@ -54,14 +54,61 @@ namespace KeyboardAudioVisualizer //surface.LoadDevices(CoolerMasterDeviceProvider.Instance); ILedGroup background = new ListLedGroup(surface.Leds); - background.Brush = new SolidColorBrush(new Color(0, 0, 0)); + background.Brush = new SolidColorBrush(new Color(96, 0, 0, 0)); //TODO DarthAffe 06.08.2017: A-Channel gives some kind of blur - settings! //TODO DarthAffe 03.08.2017: Changeable, Settings etc. foreach (IRGBDevice device in surface.Devices) - { - if (device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard) - new ListLedGroup(device).Brush = new FrequencyBarsBrush(AudioProcessor.Instance.PrimaryVisualizationProvider, new RainbowGradient(300, -14)); - } + switch (device.DeviceInfo.DeviceType) + { + case RGBDeviceType.Keyboard: + //TODO DarthAffe 05.08.2017: Lighbar-support has to be better in RGB.NET + if (device.DeviceInfo.Model.Equals("K95 RGB Platinum")) + { + ILedGroup lightbarLeft = new ListLedGroup(new CorsairLedId(device, CorsairLedIds.Lightbar1), new CorsairLedId(device, CorsairLedIds.Lightbar2), + new CorsairLedId(device, CorsairLedIds.Lightbar3), new CorsairLedId(device, CorsairLedIds.Lightbar4), + new CorsairLedId(device, CorsairLedIds.Lightbar5), new CorsairLedId(device, CorsairLedIds.Lightbar6), + new CorsairLedId(device, CorsairLedIds.Lightbar7), new CorsairLedId(device, CorsairLedIds.Lightbar8), + new CorsairLedId(device, CorsairLedIds.Lightbar9)); + ILedGroup lightbarCenter = new ListLedGroup(new CorsairLedId(device, CorsairLedIds.Lightbar1)); + ILedGroup lightbarRight = new ListLedGroup(new CorsairLedId(device, CorsairLedIds.Lightbar11), new CorsairLedId(device, CorsairLedIds.Lightbar12), + new CorsairLedId(device, CorsairLedIds.Lightbar13), new CorsairLedId(device, CorsairLedIds.Lightbar14), + new CorsairLedId(device, CorsairLedIds.Lightbar15), new CorsairLedId(device, CorsairLedIds.Lightbar16), + new CorsairLedId(device, CorsairLedIds.Lightbar17), new CorsairLedId(device, CorsairLedIds.Lightbar18), + new CorsairLedId(device, CorsairLedIds.Lightbar19)); + ListLedGroup primary = new ListLedGroup(device); + primary.RemoveLeds(lightbarLeft.GetLeds()); + primary.RemoveLeds(lightbarCenter.GetLeds()); + primary.RemoveLeds(lightbarRight.GetLeds()); + + IGradient keyboardLevelGradient = new LinearGradient(new GradientStop(0, new Color(0, 0, 255)), new GradientStop(1, new Color(255, 0, 0))); + lightbarLeft.Brush = new LevelBarBrush(AudioProcessor.Instance.SecondaryVisualizationProvider, keyboardLevelGradient, LevelBarDirection.Left, 0); + lightbarRight.Brush = new LevelBarBrush(AudioProcessor.Instance.SecondaryVisualizationProvider, keyboardLevelGradient, LevelBarDirection.Right, 1); + lightbarCenter.Brush = new SolidColorBrush(new Color(255, 255, 255)); //TODO DarthAffe 06.08.2017: Insert beat-detetion here! + + primary.Brush = new FrequencyBarsBrush(AudioProcessor.Instance.PrimaryVisualizationProvider, new RainbowGradient(300, -14)); + } + else + new ListLedGroup(device).Brush = new FrequencyBarsBrush(AudioProcessor.Instance.PrimaryVisualizationProvider, new RainbowGradient(300, -14)); + + //{ + // ILedGroup left = new RectangleLedGroup(new Rectangle(device.Location.X, device.Location.Y, device.Size.Width / 2.0, device.Size.Height)); + // ILedGroup right = new RectangleLedGroup(new Rectangle(device.Location.X + (device.Size.Width / 2.0), device.Location.Y, device.Size.Width / 2.0, device.Size.Height)); + + // IGradient levelGradient = new LinearGradient(new GradientStop(0, new Color(0, 0, 255)), new GradientStop(1, new Color(255, 0, 0))); + // left.Brush = new LevelBarBrush(AudioProcessor.Instance.SecondaryVisualizationProvider, levelGradient, LevelBarDirection.Left, 0); + // right.Brush = new LevelBarBrush(AudioProcessor.Instance.SecondaryVisualizationProvider, levelGradient, LevelBarDirection.Right, 1); + //} + break; + + case RGBDeviceType.Mousemat: + ILedGroup left = new RectangleLedGroup(new Rectangle(device.Location.X, device.Location.Y, device.Size.Width / 2.0, device.Size.Height)); + ILedGroup right = new RectangleLedGroup(new Rectangle(device.Location.X + (device.Size.Width / 2.0), device.Location.Y, device.Size.Width / 2.0, device.Size.Height)); + + IGradient mousematLevelGradient = new LinearGradient(new GradientStop(0, new Color(0, 0, 255)), new GradientStop(1, new Color(255, 0, 0))); + left.Brush = new LevelBarBrush(AudioProcessor.Instance.SecondaryVisualizationProvider, mousematLevelGradient, LevelBarDirection.Top, 0); + right.Brush = new LevelBarBrush(AudioProcessor.Instance.SecondaryVisualizationProvider, mousematLevelGradient, LevelBarDirection.Top, 1); + break; + } surface.Updating += args => AudioProcessor.Instance.Update(); } diff --git a/KeyboardAudioVisualizer/AudioCapture/AudioBuffer.cs b/KeyboardAudioVisualizer/AudioCapture/AudioBuffer.cs index 8eff478..07d0afd 100644 --- a/KeyboardAudioVisualizer/AudioCapture/AudioBuffer.cs +++ b/KeyboardAudioVisualizer/AudioCapture/AudioBuffer.cs @@ -1,4 +1,6 @@ -namespace KeyboardAudioVisualizer.AudioCapture +using System; + +namespace KeyboardAudioVisualizer.AudioCapture { public class AudioBuffer { diff --git a/KeyboardAudioVisualizer/AudioProcessing/AudioProcessor.cs b/KeyboardAudioVisualizer/AudioProcessing/AudioProcessor.cs index 98ef042..7eb01ed 100644 --- a/KeyboardAudioVisualizer/AudioProcessing/AudioProcessor.cs +++ b/KeyboardAudioVisualizer/AudioProcessing/AudioProcessor.cs @@ -2,7 +2,7 @@ using KeyboardAudioVisualizer.AudioCapture; using KeyboardAudioVisualizer.AudioProcessing.Equalizer; using KeyboardAudioVisualizer.AudioProcessing.Spectrum; -using KeyboardAudioVisualizer.AudioProcessing.VisualizationPRovider; +using KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider; namespace KeyboardAudioVisualizer.AudioProcessing { @@ -22,6 +22,7 @@ namespace KeyboardAudioVisualizer.AudioProcessing private IAudioInput _audioInput; private ISpectrumProvider _spectrumProvider; public IVisualizationProvider PrimaryVisualizationProvider { get; private set; } + public IVisualizationProvider SecondaryVisualizationProvider { get; private set; } #endregion @@ -37,6 +38,7 @@ namespace KeyboardAudioVisualizer.AudioProcessing { _spectrumProvider.Update(); PrimaryVisualizationProvider.Update(); + SecondaryVisualizationProvider.Update(); } public static void Initialize() @@ -59,9 +61,12 @@ namespace KeyboardAudioVisualizer.AudioProcessing _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 }; + MultiBandEqualizer equalizer = new MultiBandEqualizer { [0] = -3, [1] = -1, [2] = -1, [3] = 1, [4] = 3 }; + PrimaryVisualizationProvider = new FrequencyBarsVisualizationProvider(new FrequencyBarsVisualizationProviderConfiguration { Scale = 34 }, _spectrumProvider) { Equalizer = equalizer }; PrimaryVisualizationProvider.Initialize(); + + SecondaryVisualizationProvider = new LevelVisualizationProvider(new LevelVisualizationProviderConfiguration(), _audioBuffer); + SecondaryVisualizationProvider.Initialize(); } private int CalculateSampleSize(int sampleRate, int maximumUpdateRate) diff --git a/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/FrequencyBarsVisualizationProvider.cs b/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/FrequencyBarsVisualizationProvider.cs index ae0c8b4..65fc6a9 100644 --- a/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/FrequencyBarsVisualizationProvider.cs +++ b/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/FrequencyBarsVisualizationProvider.cs @@ -5,7 +5,7 @@ using KeyboardAudioVisualizer.AudioProcessing.Spectrum; using KeyboardAudioVisualizer.Configuration; using KeyboardAudioVisualizer.Helper; -namespace KeyboardAudioVisualizer.AudioProcessing.VisualizationPRovider +namespace KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider { #region Configuration @@ -76,6 +76,7 @@ namespace KeyboardAudioVisualizer.AudioProcessing.VisualizationPRovider private double _scalingValue; public IEqualizer Equalizer { get; set; } + public IConfiguration Configuration => _configuration; public float[] VisualizationData { get; private set; } #endregion diff --git a/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/IVisualizationProvider.cs b/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/IVisualizationProvider.cs index f6b742a..1e5c0d8 100644 --- a/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/IVisualizationProvider.cs +++ b/KeyboardAudioVisualizer/AudioProcessing/VisualizationPRovider/IVisualizationProvider.cs @@ -1,7 +1,10 @@ -namespace KeyboardAudioVisualizer.AudioProcessing.VisualizationPRovider +using KeyboardAudioVisualizer.Configuration; + +namespace KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider { public interface IVisualizationProvider : IAudioProcessor { + IConfiguration Configuration { get; } float[] VisualizationData { get; } } } diff --git a/KeyboardAudioVisualizer/AudioProcessing/VisualizationProvider/LevelVisualizationProvider.cs b/KeyboardAudioVisualizer/AudioProcessing/VisualizationProvider/LevelVisualizationProvider.cs new file mode 100644 index 0000000..9c512dd --- /dev/null +++ b/KeyboardAudioVisualizer/AudioProcessing/VisualizationProvider/LevelVisualizationProvider.cs @@ -0,0 +1,133 @@ +using System; +using System.Linq; +using KeyboardAudioVisualizer.AudioCapture; +using KeyboardAudioVisualizer.Configuration; +using KeyboardAudioVisualizer.Helper; + +namespace KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider +{ + #region Configuration + + public enum ConversionMode + { + Linear, Logarithmic, Exponential + } + + public class LevelVisualizationProviderConfiguration : AbstractConfiguration + { + private double _smoothing = 8; + public double Smoothing + { + get => _smoothing; + set => SetProperty(ref _smoothing, value); + } + + private double _scale = 4; + public double Scale + { + get => _scale; + set => SetProperty(ref _scale, value); + } + + private ConversionMode _conversionMode = ConversionMode.Exponential; + public ConversionMode ConversionMode + { + get => _conversionMode; + set => SetProperty(ref _conversionMode, value); + } + } + + #endregion + + public class LevelVisualizationProvider : IVisualizationProvider + { + #region Constants + + private const int DB_THRESHOLD = 90; + + #endregion + + #region Properties & Fields + + private readonly LevelVisualizationProviderConfiguration _configuration; + private readonly AudioBuffer _audioBuffer; + + private float[] _sampleDataLeft; + private float[] _sampleDataRight; + private float[] _sampleDataMix; + private double _smoothingFactor; + + public IConfiguration Configuration => _configuration; + public float[] VisualizationData { get; } = new float[3]; + + #endregion + + #region Constructors + + public LevelVisualizationProvider(LevelVisualizationProviderConfiguration configuration, AudioBuffer audioBuffer) + { + this._configuration = configuration; + this._audioBuffer = audioBuffer; + + configuration.PropertyChanged += (sender, args) => RecalculateConfigValues(args.PropertyName); + } + + #endregion + + #region Methods + + public void Initialize() + { + _sampleDataLeft = new float[_audioBuffer.Size]; + _sampleDataRight = new float[_audioBuffer.Size]; + _sampleDataMix = new float[_audioBuffer.Size]; + + RecalculateConfigValues(null); + } + + private void RecalculateConfigValues(string changedPropertyName) + { + if ((changedPropertyName == null) || (changedPropertyName == nameof(LevelVisualizationProviderConfiguration.Smoothing))) + _smoothingFactor = MathHelper.Clamp(0.000015 * Math.Pow(2, _configuration.Smoothing), 0, 0.99); + } + + public void Update() + { + _audioBuffer.CopyLeftInto(ref _sampleDataLeft, 0); + _audioBuffer.CopyRightInto(ref _sampleDataRight, 0); + _audioBuffer.CopyMixInto(ref _sampleDataMix, 0); + + float levelLeft = Convert(GetRms(ref _sampleDataLeft)); + float levelRight = Convert(GetRms(ref _sampleDataRight)); + float levelMix = Convert(GetRms(ref _sampleDataMix)); + + UpdateData(0, levelLeft); + UpdateData(1, levelRight); + UpdateData(2, levelMix); + } + + private float GetRms(ref float[] data) => (float)Math.Sqrt(data.Average(x => x * x)); + + private float Convert(float level) + { + switch (_configuration.ConversionMode) + { + case ConversionMode.Exponential: + return level * level; + + case ConversionMode.Logarithmic: + return (float)Math.Max(0, (DB_THRESHOLD + (Math.Log10(level) * 20)) / DB_THRESHOLD); + + default: return level; + } + } + + private void UpdateData(int index, float level) + { + VisualizationData[index] = (float)((VisualizationData[index] * _smoothingFactor) + (level * _configuration.Scale * (1.0 - _smoothingFactor))); + if (double.IsNaN(VisualizationData[index])) VisualizationData[index] = 0; + } + + #endregion + } +} diff --git a/KeyboardAudioVisualizer/Brushes/FrequencyBarsBrush.cs b/KeyboardAudioVisualizer/Brushes/FrequencyBarsBrush.cs index 8f3df4e..e9c6909 100644 --- a/KeyboardAudioVisualizer/Brushes/FrequencyBarsBrush.cs +++ b/KeyboardAudioVisualizer/Brushes/FrequencyBarsBrush.cs @@ -1,5 +1,5 @@ using System; -using KeyboardAudioVisualizer.AudioProcessing.VisualizationPRovider; +using KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider; using RGB.NET.Brushes; using RGB.NET.Brushes.Gradients; using RGB.NET.Core; diff --git a/KeyboardAudioVisualizer/Brushes/LevelBarBrush.cs b/KeyboardAudioVisualizer/Brushes/LevelBarBrush.cs new file mode 100644 index 0000000..5878f47 --- /dev/null +++ b/KeyboardAudioVisualizer/Brushes/LevelBarBrush.cs @@ -0,0 +1,70 @@ +using KeyboardAudioVisualizer.AudioProcessing.VisualizationProvider; +using RGB.NET.Brushes; +using RGB.NET.Brushes.Gradients; +using RGB.NET.Core; + +namespace KeyboardAudioVisualizer.Brushes +{ + public class LevelBarBrush : LinearGradientBrush + { + #region Properties & Fields + + private readonly IVisualizationProvider _visualizationProvider; + public LevelBarDirection Direction { get; set; } + public int DataIndex { get; set; } + + #endregion + + #region Constructors + + public LevelBarBrush(IVisualizationProvider visualizationProvider, IGradient gradient, LevelBarDirection direction, int dataIndex) + : base(gradient) + { + this._visualizationProvider = visualizationProvider; + this.Direction = direction; + this.DataIndex = dataIndex; + } + + #endregion + + #region Methods + + protected override Color GetColorAtPoint(Rectangle rectangle, BrushRenderTarget renderTarget) + { + double offset = CalculateOffset(rectangle, renderTarget); + return offset < _visualizationProvider.VisualizationData[DataIndex] ? Gradient.GetColor(offset) : Color.Transparent; + } + + private double CalculateOffset(Rectangle rectangle, BrushRenderTarget renderTarget) + { + switch (Direction) + { + case LevelBarDirection.Left: + return (rectangle.Size.Width - renderTarget.Rectangle.Center.X) / rectangle.Size.Width; + + case LevelBarDirection.Right: + return renderTarget.Rectangle.Center.X / rectangle.Size.Width; + + case LevelBarDirection.Top: + return (rectangle.Size.Height - renderTarget.Rectangle.Center.Y) / rectangle.Size.Height; + + case LevelBarDirection.Bottom: + return renderTarget.Rectangle.Center.Y / rectangle.Size.Height; + + default: + return -1; + } + } + + #endregion + } + + #region Data + + public enum LevelBarDirection + { + Left, Right, Top, Bottom + } + + #endregion +} diff --git a/KeyboardAudioVisualizer/Configuration/AbstractConfiguration.cs b/KeyboardAudioVisualizer/Configuration/AbstractConfiguration.cs index 65925b1..fed82f4 100644 --- a/KeyboardAudioVisualizer/Configuration/AbstractConfiguration.cs +++ b/KeyboardAudioVisualizer/Configuration/AbstractConfiguration.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; namespace KeyboardAudioVisualizer.Configuration { - public class AbstractConfiguration : INotifyPropertyChanged + public class AbstractConfiguration : IConfiguration, INotifyPropertyChanged { #region Events diff --git a/KeyboardAudioVisualizer/Configuration/IConfiguration.cs b/KeyboardAudioVisualizer/Configuration/IConfiguration.cs new file mode 100644 index 0000000..87e3044 --- /dev/null +++ b/KeyboardAudioVisualizer/Configuration/IConfiguration.cs @@ -0,0 +1,5 @@ +namespace KeyboardAudioVisualizer.Configuration +{ + // DarthAffe 05.08.2017: Marker interface + public interface IConfiguration { } +} diff --git a/KeyboardAudioVisualizer/Controls/Formular.cs b/KeyboardAudioVisualizer/Controls/Formular.cs new file mode 100644 index 0000000..5ef7148 --- /dev/null +++ b/KeyboardAudioVisualizer/Controls/Formular.cs @@ -0,0 +1,202 @@ +using System; +using System.Windows; +using System.Windows.Controls; + +namespace KeyboardAudioVisualizer.Controls +{ + public class Formular : Panel + { + #region DependencyProperties + // ReSharper disable InconsistentNaming + + public static readonly DependencyProperty RowHeightProperty = DependencyProperty.Register("RowHeight", typeof(double), typeof(Formular), + new FrameworkPropertyMetadata(24.0, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double RowHeight + { + get => (double)GetValue(RowHeightProperty); + set + { + if (value < 0) throw new ArgumentOutOfRangeException(nameof(RowHeight), "Row height can't be negative"); + SetValue(RowHeightProperty, value); + } + } + + public static readonly DependencyProperty LabelWidthProperty = DependencyProperty.Register("LabelWidth", typeof(double), typeof(Formular), + new FrameworkPropertyMetadata(100.0, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double LabelWidth + { + get => (double)GetValue(LabelWidthProperty); + set + { + if (value < 0) throw new ArgumentOutOfRangeException(nameof(RowHeight), "Label width can't be negative"); + SetValue(LabelWidthProperty, value); + } + } + + public static readonly DependencyProperty ElementSpacingProperty = DependencyProperty.Register("ElementSpacing", typeof(double), typeof(Formular), + new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double ElementSpacing + { + get => (double)GetValue(ElementSpacingProperty); + set => SetValue(ElementSpacingProperty, value); + } + + public static readonly DependencyProperty RowSpacingProperty = DependencyProperty.Register("RowSpacing", typeof(double), typeof(Formular), + new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double RowSpacing + { + get => (double)GetValue(RowSpacingProperty); + set => SetValue(RowSpacingProperty, value); + } + + // ReSharper restore InconsistentNaming + #endregion + + #region AttachedProperties + // ReSharper disable InconsistentNaming + + public static readonly DependencyProperty IsLabelProperty = DependencyProperty.RegisterAttached("IsLabel", typeof(bool), typeof(Formular), + new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public static void SetIsLabel(UIElement element, bool value) => element.SetValue(IsLabelProperty, value); + public static bool GetIsLabel(UIElement element) => (bool)element.GetValue(IsLabelProperty); + + public static readonly DependencyProperty LineBreaksProperty = DependencyProperty.RegisterAttached("LineBreaks", typeof(int), typeof(Formular), + new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public static void SetLineBreaks(UIElement element, int value) => element.SetValue(LineBreaksProperty, value); + public static int GetLineBreaks(UIElement element) => (int)element.GetValue(LineBreaksProperty); + + public static readonly DependencyProperty RowSpanProperty = DependencyProperty.RegisterAttached("RowSpan", typeof(int), typeof(Formular), + new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public static void SetRowSpan(DependencyObject element, int value) => element.SetValue(RowSpanProperty, value); + public static int GetRowSpan(DependencyObject element) => (int)element.GetValue(RowSpanProperty); + + // ReSharper restore InconsistentNaming + #endregion + + #region Methods + + protected override Size MeasureOverride(Size availableSize) + { + if (InternalChildren.Count == 0) return new Size(0, 0); + + FormularLayout layout = new FormularLayout(RowHeight, LabelWidth, ElementSpacing, RowSpacing); + + foreach (UIElement child in InternalChildren) + { + child.Measure(availableSize); + layout.AddElement(child); + } + + return new Size(layout.Width, layout.Height); + } + + protected override Size ArrangeOverride(Size finalSize) + { + if (InternalChildren.Count == 0) return new Size(0, 0); + + FormularLayout layout = new FormularLayout(RowHeight, LabelWidth, ElementSpacing, RowSpacing); + + foreach (UIElement child in InternalChildren) + child.Arrange(layout.AddElement(child)); + + return new Size(layout.Width, layout.Height); + } + + #endregion + + #region Data + + private class FormularLayout + { + #region Properties & Fields + + private readonly double _rowHeight; + private readonly double _labelWidth; + private readonly double _elementSpacing; + private readonly double _rowSpacing; + + private double _currentRowWidth; + + private int _newRows = 0; + private int _rows = -1; + private double _currentMaxWidth; + public double Width => Math.Max((Math.Max(_currentMaxWidth, _currentRowWidth) - _elementSpacing), 0); + public double Height => ((_rows + 1) * _rowHeight) + (_rows * _rowSpacing); + + #endregion + + #region Constructors + + public FormularLayout(double rowHeight, double labelWidth, double elementSpacing, double rowSpacing) + { + this._rowHeight = rowHeight; + this._labelWidth = labelWidth; + this._elementSpacing = elementSpacing; + this._rowSpacing = rowSpacing; + } + + #endregion + + #region Methods + + public Rect AddElement(UIElement element) + { + bool isLabel = GetIsLabel(element); + int lineBreaks = GetLineBreaks(element); + int rowSpan = GetRowSpan(element); + + double elementWidth = isLabel ? _labelWidth : element.DesiredSize.Width; + double height = _rowHeight; + + if (_newRows > 0) + { + AddLineBreaks(_newRows); + _newRows = 0; + } + + if (lineBreaks > 0) AddLineBreaks(lineBreaks); + else if (isLabel) AddLineBreaks(1); + else if (_rows < 0) _rows = 0; + + if (!isLabel && (_currentRowWidth < _labelWidth)) + _currentRowWidth = _labelWidth + _elementSpacing; + + if (rowSpan > 1) + { + height = (rowSpan * _rowHeight) + ((rowSpan - 1) * _rowSpacing); + _newRows = Math.Max(_newRows, rowSpan - 1); + } + + if (element is FrameworkElement fe) + fe.MaxHeight = height; + + Rect rect = new Rect(new Point(_currentRowWidth, (_rows * _rowHeight) + (_rows * _rowSpacing)), new Size(elementWidth, height)); + + _currentRowWidth += elementWidth + _elementSpacing; + + return rect; + } + + private void AddLineBreaks(int count) + { + if (count <= 0) return; + + _currentMaxWidth = Math.Max(_currentMaxWidth, _currentRowWidth); + + _currentRowWidth = 0; + _rows += count; + } + + #endregion + } + + #endregion + } +} diff --git a/KeyboardAudioVisualizer/Helper/MathHelper.cs b/KeyboardAudioVisualizer/Helper/MathHelper.cs index 6a34d38..021eeb3 100644 --- a/KeyboardAudioVisualizer/Helper/MathHelper.cs +++ b/KeyboardAudioVisualizer/Helper/MathHelper.cs @@ -6,7 +6,7 @@ namespace KeyboardAudioVisualizer.Helper { #region Methods - public static double Clamp(double value, double min, double max) => Math.Max(min, Math.Min(max / 2.0, value)); + public static double Clamp(double value, double min, double max) => Math.Max(min, Math.Min(max, value)); public static float Clamp(float value, float min, float max) => (float)Clamp((double)value, min, max); #endregion diff --git a/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj b/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj index f9a65d2..b470db4 100644 --- a/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj +++ b/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj @@ -48,15 +48,24 @@ ..\packages\MathNet.Numerics.3.20.0\lib\net40\MathNet.Numerics.dll - + ..\packages\RGB.NET.Brushes.1.0.0\lib\net45\RGB.NET.Brushes.dll + True - + ..\packages\RGB.NET.Core.1.0.0\lib\net45\RGB.NET.Core.dll + True + + + ..\packages\RGB.NET.Devices.CoolerMaster.1.0.0\lib\net45\RGB.NET.Devices.CoolerMaster.dll + True ..\packages\RGB.NET.Devices.Corsair.1.0.0\lib\net45\RGB.NET.Devices.Corsair.dll + + ..\packages\RGB.NET.Devices.Logitech.1.0.0\lib\net45\RGB.NET.Devices.Logitech.dll + ..\packages\RGB.NET.Groups.1.0.0\lib\net45\RGB.NET.Groups.dll @@ -64,6 +73,9 @@ + + ..\packages\System.ValueTuple.4.3.1\lib\netstandard1.0\System.ValueTuple.dll + @@ -99,10 +111,14 @@ - - + + + + + + @@ -135,7 +151,10 @@ Resources.Designer.cs Designer - + + + Designer + SettingsSingleFileGenerator Settings.Designer.cs @@ -174,6 +193,14 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile Designer @@ -182,12 +209,19 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + + + + @@ -195,5 +229,9 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/KeyboardAudioVisualizer/Resources/KeyboardAudioVisualizer.xaml b/KeyboardAudioVisualizer/Resources/KeyboardAudioVisualizer.xaml index 6342872..7e81dd1 100644 --- a/KeyboardAudioVisualizer/Resources/KeyboardAudioVisualizer.xaml +++ b/KeyboardAudioVisualizer/Resources/KeyboardAudioVisualizer.xaml @@ -1,18 +1,22 @@  + xmlns:styles="clr-namespace:KeyboardAudioVisualizer.Styles" + xmlns:controls="clr-namespace:KeyboardAudioVisualizer.Controls"> + + - + + + + + + + + + + + \ No newline at end of file diff --git a/KeyboardAudioVisualizer/Styles/GroupBox.xaml b/KeyboardAudioVisualizer/Styles/GroupBox.xaml new file mode 100644 index 0000000..530ba34 --- /dev/null +++ b/KeyboardAudioVisualizer/Styles/GroupBox.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/KeyboardAudioVisualizer/Styles/Navigation.xaml b/KeyboardAudioVisualizer/Styles/Navigation.xaml index 5b8f101..50d4b33 100644 --- a/KeyboardAudioVisualizer/Styles/Navigation.xaml +++ b/KeyboardAudioVisualizer/Styles/Navigation.xaml @@ -12,7 +12,7 @@ - - - - \ No newline at end of file diff --git a/KeyboardAudioVisualizer/UI/Configuration/FrequencyBarsConfiguration.xaml b/KeyboardAudioVisualizer/UI/Configuration/FrequencyBarsConfiguration.xaml new file mode 100644 index 0000000..b5fb27d --- /dev/null +++ b/KeyboardAudioVisualizer/UI/Configuration/FrequencyBarsConfiguration.xaml @@ -0,0 +1,40 @@ + + + + + + + + + + +