From 1fa606370677d9b8c1a1ebc264226b1039aea00d Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sun, 8 Jan 2017 23:55:21 +0100 Subject: [PATCH 01/32] WIP on new audio layers --- Artemis/Artemis/Artemis.csproj | 15 +- .../Artemis/Modules/Abstract/ModuleModel.cs | 5 + .../Games/RocketLeague/RocketLeagueModel.cs | 17 +- Artemis/Artemis/Modules/Games/WoW/WoWModel.cs | 20 +- .../Audio/AudioCapturing/AudioCapture.cs | 135 +++++++++++++ .../AudioCapturing/AudioCaptureManager.cs | 130 ++---------- .../AudioCapturing/BaseSpectrumProvider.cs | 54 +++++ .../Audio/AudioCapturing/FftEventArgs.cs | 17 -- .../Audio/AudioCapturing/ISpectrumProvider.cs | 8 + .../Audio/AudioCapturing/LineSpectrum.cs | 56 ++++++ .../Audio/AudioCapturing/SampleAggregator.cs | 52 ----- .../Audio/AudioCapturing/SpectrumBase.cs | 189 ++++++++++++++++++ .../Types/Audio/AudioPropertiesModel.cs | 7 + .../Profiles/Layers/Types/Audio/AudioType.cs | 116 ++--------- .../ViewModels/Profiles/ProfileViewModel.cs | 2 +- Artemis/Artemis/packages.config | 2 +- 16 files changed, 513 insertions(+), 312 deletions(-) create mode 100644 Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/BaseSpectrumProvider.cs delete mode 100644 Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/FftEventArgs.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/ISpectrumProvider.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs delete mode 100644 Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SampleAggregator.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SpectrumBase.cs diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index f80ef2f2c..ab2de8b36 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -151,6 +151,10 @@ ..\packages\Colore.5.1.0\lib\net35\Corale.Colore.dll True + + ..\packages\CSCore.1.1.0\lib\net35-client\CSCore.dll + True + ..\packages\CUE.NET.1.1.0.2\lib\net45\CUE.NET.dll True @@ -211,10 +215,6 @@ ..\packages\MoonSharp.2.0.0.0\lib\net40-client\MoonSharp.Interpreter.dll True - - ..\packages\NAudio.1.7.3\lib\net35\NAudio.dll - True - ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll True @@ -489,9 +489,12 @@ + - - + + + + AudioPropertiesView.xaml diff --git a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs index a3bbfc5c8..6734de719 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs @@ -99,6 +99,11 @@ namespace Artemis.Modules.Abstract ProfileModel = profileModel; ProfileModel?.Activate(_luaManager); + if (ProfileModel != null) + { + Settings.LastProfile = ProfileModel.Name; + Settings.Save(); + } RaiseProfileChangedEvent(new ProfileChangedEventArgs(ProfileModel)); } diff --git a/Artemis/Artemis/Modules/Games/RocketLeague/RocketLeagueModel.cs b/Artemis/Artemis/Modules/Games/RocketLeague/RocketLeagueModel.cs index 750c29643..729b16765 100644 --- a/Artemis/Artemis/Modules/Games/RocketLeague/RocketLeagueModel.cs +++ b/Artemis/Artemis/Modules/Games/RocketLeague/RocketLeagueModel.cs @@ -54,18 +54,21 @@ namespace Artemis.Modules.Games.RocketLeague { Updater.GetPointers(); _pointer = SettingsProvider.Load().RocketLeague; - - var tempProcess = MemoryHelpers.GetProcessIfRunning(ProcessName); - if (tempProcess == null) - return; - - _memory = new Memory(tempProcess); - + base.Enable(); } public override void Update() { + if (_memory == null) + { + var tempProcess = MemoryHelpers.GetProcessIfRunning(ProcessName); + if (tempProcess == null) + return; + + _memory = new Memory(tempProcess); + } + if (ProfileModel == null || DataModel == null || _memory == null) return; diff --git a/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs b/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs index e1aa1688a..1df6ff02f 100644 --- a/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs +++ b/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs @@ -72,19 +72,17 @@ namespace Artemis.Modules.Games.WoW _process = null; } - public override void Enable() - { - var tempProcess = MemoryHelpers.GetProcessIfRunning(ProcessName); - if (tempProcess == null) - return; - - _process = new ProcessSharp(tempProcess, MemoryType.Remote); - - base.Enable(); - } - public override void Update() { + if (_process == null) + { + var tempProcess = MemoryHelpers.GetProcessIfRunning(ProcessName); + if (tempProcess == null) + return; + + _process = new ProcessSharp(tempProcess, MemoryType.Remote); + } + if (ProfileModel == null || DataModel == null || _process == null) return; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs new file mode 100644 index 000000000..a8602a20b --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs @@ -0,0 +1,135 @@ +using System; +using System.Timers; +using CSCore; +using CSCore.CoreAudioAPI; +using CSCore.DSP; +using CSCore.SoundIn; +using CSCore.Streams; +using Ninject.Extensions.Logging; + +namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing +{ + public class AudioCapture + { + private const FftSize FftSize = CSCore.DSP.FftSize.Fft4096; + private WasapiLoopbackCapture _soundIn; + private GainSource _source; + private BasicSpectrumProvider _spectrumProvider; + private GainSource _volume; + private Timer _timer; + + public AudioCapture(ILogger logger, MMDevice device) + { + Logger = logger; + Device = device; + + _timer = new Timer(1000); + _timer.Elapsed += TimerOnElapsed; + } + + private void TimerOnElapsed(object sender, ElapsedEventArgs e) + { + // If MayStop is true for longer than a second, this will stop the audio capture + if (MayStop) + { + Stop(); + MayStop = false; + } + else + MayStop = true; + } + + public bool MayStop { get; set; } + + public ILogger Logger { get; } + + public float Volume + { + get { return _volume.Volume; } + set { _volume.Volume = value; } + } + + public MMDevice Device { get; } + public bool Running { get; set; } + + public LineSpectrum GetLineSpectrum(int barCount, int volume, ScalingStrategy scalingStrategy) + { + return new LineSpectrum(FftSize) + { + SpectrumProvider = _spectrumProvider, + UseAverage = true, + BarCount = barCount, + IsXLogScale = true, + ScalingStrategy = scalingStrategy + }; + } + + public void Start() + { + if (Running) + return; + + try + { + _soundIn = new WasapiLoopbackCapture(); + _soundIn.Initialize(); + // Not sure if this null check is needed but doesnt hurt + if (Device != null) + _soundIn.Device = Device; + + var soundInSource = new SoundInSource(_soundIn); + _source = soundInSource.ToSampleSource().AppendSource(x => new GainSource(x), out _volume); + + // create a spectrum provider which provides fft data based on some input + _spectrumProvider = new BasicSpectrumProvider(_source.WaveFormat.Channels, _source.WaveFormat.SampleRate, + FftSize); + + // the SingleBlockNotificationStream is used to intercept the played samples + var notificationSource = new SingleBlockNotificationStream(_source); + // pass the intercepted samples as input data to the spectrumprovider (which will calculate a fft based on them) + notificationSource.SingleBlockRead += (s, a) => _spectrumProvider.Add(a.Left, a.Right); + + var waveSource = notificationSource.ToWaveSource(16); + // We need to read from our source otherwise SingleBlockRead is never called and our spectrum provider is not populated + var buffer = new byte[waveSource.WaveFormat.BytesPerSecond / 2]; + soundInSource.DataAvailable += (s, aEvent) => + { + while (waveSource.Read(buffer, 0, buffer.Length) > 0) + { + } + }; + + _soundIn.Start(); + _timer.Start(); + Running = true; + MayStop = false; + } + catch (Exception e) + { + Logger.Warn(e, "Failed to start WASAPI audio capture"); + } + } + + public void Stop() + { + if (!Running) + return; + + try + { + _timer.Stop(); + _soundIn.Stop(); + _soundIn.Dispose(); + _source.Dispose(); + _soundIn = null; + _source = null; + + Running = false; + } + catch (Exception e) + { + Logger.Warn(e, "Failed to stop WASAPI audio capture"); + } + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs index 04ca51a5d..cece1ddb0 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs @@ -1,133 +1,33 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using NAudio.CoreAudioApi; -using NAudio.Dsp; -using NAudio.Wave; +using CSCore.CoreAudioAPI; using Ninject.Extensions.Logging; namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing { public class AudioCaptureManager { - private readonly SampleAggregator _sampleAggregator = new SampleAggregator(1024); - private readonly WasapiLoopbackCapture _waveIn; - private Complex[] _fft; - private DateTime _lastAudioUpdate; - private DateTime _lastRequest; + private readonly List _audioCaptures; public AudioCaptureManager(ILogger logger) { Logger = logger; - Device = new MMDeviceEnumerator().EnumerateAudioEndPoints(DataFlow.All, DeviceState.Active).FirstOrDefault(); + _audioCaptures = new List(); + } - _sampleAggregator.FftCalculated += FftCalculated; - _sampleAggregator.PerformFFT = true; + public AudioCapture GetAudioCapture(MMDevice device) + { + // Return existing audio capture if found + var audioCapture = _audioCaptures.FirstOrDefault(a => a.Device == device); + if (audioCapture != null) + return audioCapture; - // Start listening for sound data - _waveIn = new WasapiLoopbackCapture(); - _waveIn.DataAvailable += OnDataAvailable; + // Else create a new one and return that + var newAudioCapture = new AudioCapture(Logger, device); + _audioCaptures.Add(newAudioCapture); + return newAudioCapture; } public ILogger Logger { get; set; } - public MMDevice Device { get; set; } - - public bool Running { get; set; } - - public void Start() - { - if (Running) - return; - - try - { - _waveIn.StartRecording(); - Running = true; - } - catch (Exception e) - { - Logger.Warn(e, "Failed to start WASAPI audio capture"); - } - } - - public void Stop() - { - if (!Running) - return; - - try - { - _waveIn.StopRecording(); - Running = false; - } - catch (Exception e) - { - Logger.Warn(e, "Failed to start WASAPI audio capture"); - } - } - - private void FftCalculated(object sender, FftEventArgs e) - { - _fft = e.Result; - } - - private void OnDataAvailable(object sender, WaveInEventArgs e) - { - if (DateTime.Now - _lastAudioUpdate < TimeSpan.FromMilliseconds(40)) - return; - if (DateTime.Now - _lastRequest > TimeSpan.FromSeconds(5)) - { - Stop(); - return; - } - - _lastAudioUpdate = DateTime.Now; - - var buffer = e.Buffer; - var bytesRecorded = e.BytesRecorded; - var bufferIncrement = _waveIn.WaveFormat.BlockAlign; - - for (var index = 0; index < bytesRecorded; index += bufferIncrement) - { - var sample32 = BitConverter.ToSingle(buffer, index); - _sampleAggregator.Add(sample32); - } - } - - public List GetSpectrumData(int lines) - { - _lastRequest = DateTime.Now; - if (!Running) - Start(); - - var spectrumData = new List(); - - if (_fft == null) - return spectrumData; - - int x; - var b0 = 0; - - for (x = 0; x < lines; x++) - { - float peak = 0; - var b1 = (int) Math.Pow(2, x*10.0/(lines - 1)); - if (b1 > 1023) - b1 = 1023; - if (b1 <= b0) - b1 = b0 + 1; - for (; b0 < b1; b0++) - if (peak < _fft[1 + b0].X) - peak = _fft[1 + b0].X; - var y = (int) (Math.Sqrt(peak)*3*255 - 4); - if (y > 255) - y = 255; - if (y < 0) - y = 0; - spectrumData.Add((byte) y); - } - - return spectrumData; - } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/BaseSpectrumProvider.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/BaseSpectrumProvider.cs new file mode 100644 index 000000000..a89a3724f --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/BaseSpectrumProvider.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using CSCore.DSP; + +namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing +{ + /// + /// BasicSpectrumProvider + /// + public class BasicSpectrumProvider : FftProvider, ISpectrumProvider + { + private readonly List _contexts = new List(); + private readonly int _sampleRate; + + public BasicSpectrumProvider(int channels, int sampleRate, FftSize fftSize) + : base(channels, fftSize) + { + if (sampleRate <= 0) + throw new ArgumentOutOfRangeException("sampleRate"); + _sampleRate = sampleRate; + } + + public int GetFftBandIndex(float frequency) + { + var fftSize = (int) FftSize; + var f = _sampleRate / 2.0; + // ReSharper disable once PossibleLossOfFraction + return (int) (frequency / f * (fftSize / 2)); + } + + public bool GetFftData(float[] fftResultBuffer, object context) + { + if (_contexts.Contains(context)) + return false; + + _contexts.Add(context); + GetFftData(fftResultBuffer); + return true; + } + + public override void Add(float[] samples, int count) + { + base.Add(samples, count); + if (count > 0) + _contexts.Clear(); + } + + public override void Add(float left, float right) + { + base.Add(left, right); + _contexts.Clear(); + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/FftEventArgs.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/FftEventArgs.cs deleted file mode 100644 index fb5466c7b..000000000 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/FftEventArgs.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Diagnostics; -using NAudio.Dsp; - -namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing -{ - public class FftEventArgs : EventArgs - { - [DebuggerStepThrough] - public FftEventArgs(Complex[] result) - { - Result = result; - } - - public Complex[] Result { get; private set; } - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/ISpectrumProvider.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/ISpectrumProvider.cs new file mode 100644 index 000000000..bf6d8abb1 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/ISpectrumProvider.cs @@ -0,0 +1,8 @@ +namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing +{ + public interface ISpectrumProvider + { + bool GetFftData(float[] fftBuffer, object context); + int GetFftBandIndex(float frequency); + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs new file mode 100644 index 000000000..39c900ea4 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using Artemis.Profiles.Layers.Models; +using CSCore.DSP; + +namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing +{ + public class LineSpectrum : SpectrumBase + { + private int _barCount; + + public LineSpectrum(FftSize fftSize) + { + FftSize = fftSize; + } + + public int BarCount + { + get { return _barCount; } + set + { + if (value <= 0) + throw new ArgumentOutOfRangeException("value"); + _barCount = value; + SpectrumResolution = value; + UpdateFrequencyMapping(); + } + } + + public void SetupLayersVertical(double height, List audioLayers) + { + var fftBuffer = new float[(int)FftSize]; + + // get the fft result from the spectrum provider + if (!SpectrumProvider.GetFftData(fftBuffer, this)) + return; + + var spectrumPoints = CalculateSpectrumPoints(height, fftBuffer); + foreach (var p in spectrumPoints) + audioLayers[p.SpectrumPointIndex].Height = p.Value; + } + + public void SetupLayersHorizontal(double width, List audioLayers) + { + var fftBuffer = new float[(int)FftSize]; + + // get the fft result from the spectrum provider + if (!SpectrumProvider.GetFftData(fftBuffer, this)) + return; + + var spectrumPoints = CalculateSpectrumPoints(width, fftBuffer); + foreach (var p in spectrumPoints) + audioLayers[p.SpectrumPointIndex].Width = p.Value; + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SampleAggregator.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SampleAggregator.cs deleted file mode 100644 index ed286733a..000000000 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SampleAggregator.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using NAudio.Dsp; - -namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing -{ - public class SampleAggregator - { - private readonly FftEventArgs fftArgs; - - // This Complex is NAudio's own! - private readonly Complex[] fftBuffer; - private readonly int fftLength; - private readonly int m; - private int fftPos; - - public SampleAggregator(int fftLength) - { - if (!IsPowerOfTwo(fftLength)) - throw new ArgumentException("FFT Length must be a power of two"); - m = (int) Math.Log(fftLength, 2.0); - this.fftLength = fftLength; - fftBuffer = new Complex[fftLength]; - fftArgs = new FftEventArgs(fftBuffer); - } - - public bool PerformFFT { get; set; } - // FFT - public event EventHandler FftCalculated; - - private bool IsPowerOfTwo(int x) - { - return (x & (x - 1)) == 0; - } - - public void Add(float value) - { - if (PerformFFT && FftCalculated != null) - { - // Remember the window function! There are many others as well. - fftBuffer[fftPos].X = (float) (value * FastFourierTransform.HammingWindow(fftPos, fftLength)); - fftBuffer[fftPos].Y = 0; // This is always zero with audio. - fftPos++; - if (fftPos >= fftLength) - { - fftPos = 0; - FastFourierTransform.FFT(true, m, fftBuffer); - FftCalculated(this, fftArgs); - } - } - } - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SpectrumBase.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SpectrumBase.cs new file mode 100644 index 000000000..3c361f0cc --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SpectrumBase.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using CSCore; +using CSCore.DSP; + +namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing +{ + public class SpectrumBase + { + private const int ScaleFactorLinear = 9; + protected const int ScaleFactorSqr = 2; + protected const double MinDbValue = -90; + protected const double MaxDbValue = 0; + protected const double DbScale = MaxDbValue - MinDbValue; + + private int _fftSize; + private bool _isXLogScale; + private int _maxFftIndex; + private int _maximumFrequency = 20000; + private int _maximumFrequencyIndex; + private int _minimumFrequency = 20; //Default spectrum from 20Hz to 20kHz + private int _minimumFrequencyIndex; + private int[] _spectrumIndexMax; + private int[] _spectrumLogScaleIndexMax; + private ISpectrumProvider _spectrumProvider; + + protected int SpectrumResolution; + + public int MaximumFrequency + { + get { return _maximumFrequency; } + set + { + if (value <= MinimumFrequency) + throw new ArgumentOutOfRangeException("value", + "Value must not be less or equal the MinimumFrequency."); + _maximumFrequency = value; + UpdateFrequencyMapping(); + } + } + + public int MinimumFrequency + { + get { return _minimumFrequency; } + set + { + if (value < 0) + throw new ArgumentOutOfRangeException("value"); + _minimumFrequency = value; + UpdateFrequencyMapping(); + } + } + + [Browsable(false)] + public ISpectrumProvider SpectrumProvider + { + get { return _spectrumProvider; } + set + { + if (value == null) + throw new ArgumentNullException("value"); + _spectrumProvider = value; + } + } + + public bool IsXLogScale + { + get { return _isXLogScale; } + set + { + _isXLogScale = value; + UpdateFrequencyMapping(); + } + } + + public ScalingStrategy ScalingStrategy { get; set; } + + public bool UseAverage { get; set; } + + [Browsable(false)] + public FftSize FftSize + { + get { return (FftSize) _fftSize; } + protected set + { + if ((int) Math.Log((int) value, 2) % 1 != 0) + throw new ArgumentOutOfRangeException("value"); + + _fftSize = (int) value; + _maxFftIndex = _fftSize / 2 - 1; + } + } + + protected virtual void UpdateFrequencyMapping() + { + _maximumFrequencyIndex = Math.Min(_spectrumProvider.GetFftBandIndex(MaximumFrequency) + 1, _maxFftIndex); + _minimumFrequencyIndex = Math.Min(_spectrumProvider.GetFftBandIndex(MinimumFrequency), _maxFftIndex); + + var actualResolution = SpectrumResolution; + + var indexCount = _maximumFrequencyIndex - _minimumFrequencyIndex; + var linearIndexBucketSize = Math.Round(indexCount / (double) actualResolution, 3); + + _spectrumIndexMax = _spectrumIndexMax.CheckBuffer(actualResolution, true); + _spectrumLogScaleIndexMax = _spectrumLogScaleIndexMax.CheckBuffer(actualResolution, true); + + var maxLog = Math.Log(actualResolution, actualResolution); + for (var i = 1; i < actualResolution; i++) + { + var logIndex = + (int) ((maxLog - Math.Log(actualResolution + 1 - i, actualResolution + 1)) * indexCount) + + _minimumFrequencyIndex; + + _spectrumIndexMax[i - 1] = _minimumFrequencyIndex + (int) (i * linearIndexBucketSize); + _spectrumLogScaleIndexMax[i - 1] = logIndex; + } + + if (actualResolution > 0) + _spectrumIndexMax[_spectrumIndexMax.Length - 1] = + _spectrumLogScaleIndexMax[_spectrumLogScaleIndexMax.Length - 1] = _maximumFrequencyIndex; + } + + protected virtual SpectrumPointData[] CalculateSpectrumPoints(double maxValue, float[] fftBuffer) + { + var dataPoints = new List(); + + double value0 = 0, value = 0; + double lastValue = 0; + var actualMaxValue = maxValue; + var spectrumPointIndex = 0; + + for (var i = _minimumFrequencyIndex; i <= _maximumFrequencyIndex; i++) + { + switch (ScalingStrategy) + { + case ScalingStrategy.Decibel: + value0 = (20 * Math.Log10(fftBuffer[i]) - MinDbValue) / DbScale * actualMaxValue; + break; + case ScalingStrategy.Linear: + value0 = fftBuffer[i] * ScaleFactorLinear * actualMaxValue; + break; + case ScalingStrategy.Sqrt: + value0 = Math.Sqrt(fftBuffer[i]) * ScaleFactorSqr * actualMaxValue; + break; + } + + var recalc = true; + + value = Math.Max(0, Math.Max(value0, value)); + + while (spectrumPointIndex <= _spectrumIndexMax.Length - 1 && + i == + (IsXLogScale + ? _spectrumLogScaleIndexMax[spectrumPointIndex] + : _spectrumIndexMax[spectrumPointIndex])) + { + if (!recalc) + value = lastValue; + + if (value > maxValue) + value = maxValue; + + if (UseAverage && spectrumPointIndex > 0) + value = (lastValue + value) / 2.0; + + dataPoints.Add(new SpectrumPointData {SpectrumPointIndex = spectrumPointIndex, Value = value}); + + lastValue = value; + value = 0.0; + spectrumPointIndex++; + recalc = false; + } + + //value = 0; + } + + return dataPoints.ToArray(); + } + + [DebuggerDisplay("{Value}")] + protected struct SpectrumPointData + { + public int SpectrumPointIndex; + public double Value; + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesModel.cs index b31c0972e..1c2b2b9be 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesModel.cs @@ -21,4 +21,11 @@ namespace Artemis.Profiles.Layers.Types.Audio [Description("Left to right")] LeftToRight, [Description("Right to left")] RightToLeft } + + public enum ScalingStrategy + { + [Description("Decibel")] Decibel, + [Description("Linear")] Linear, + [Description("Square root")] Sqrt + } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index e2ee2999a..99879e224 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -24,16 +24,15 @@ namespace Artemis.Profiles.Layers.Types.Audio private DateTime _lastUpdate; private int _lines; private string _previousSettings; + private LineSpectrum _lineSpectrum; + private AudioCapture _audioCapture; public AudioType(IKernel kernel, AudioCaptureManager audioCaptureManager) { _kernel = kernel; - AudioCaptureManager = audioCaptureManager; + _audioCapture = audioCaptureManager.GetAudioCapture(null); } - [JsonIgnore] - public AudioCaptureManager AudioCaptureManager { get; set; } - public string Name => "Keyboard - Audio visualization"; public bool ShowInEdtor => true; public DrawType DrawType => DrawType.Keyboard; @@ -83,23 +82,25 @@ namespace Artemis.Profiles.Layers.Types.Audio if (isPreview) return; + // Start audio capture in case it wasn't running + _audioCapture.Start(); + _audioCapture.MayStop = false; + lock (_audioLayers) { + // Called every update but only runs every second SetupLayers(layerModel); - var spectrumData = AudioCaptureManager.GetSpectrumData(_lines); - if (!spectrumData.Any()) - return; - + var settings = (AudioPropertiesModel) layerModel.Properties; switch (settings.Direction) { case Direction.TopToBottom: case Direction.BottomToTop: - ApplyVertical(spectrumData, settings); + _lineSpectrum.SetupLayersVertical(layerModel.Height, _audioLayers); break; case Direction.LeftToRight: case Direction.RightToLeft: - ApplyHorizontal(spectrumData, settings); + _lineSpectrum.SetupLayersHorizontal(layerModel.Width, _audioLayers); break; } } @@ -124,97 +125,7 @@ namespace Artemis.Profiles.Layers.Types.Audio return layerPropertiesViewModel; return new AudioPropertiesViewModel(layerEditorViewModel); } - - private void ApplyVertical(List spectrumData, AudioPropertiesModel settings) - { - var index = 0; - foreach (var audioLayer in _audioLayers) - { - int height; - if (spectrumData.Count > index) - height = (int) Math.Round(spectrumData[index]/2.55); - else - height = 0; - - // Apply Sensitivity setting - height = height*settings.Sensitivity; - - var newHeight = settings.Height/100.0*height; - if (newHeight >= audioLayer.Properties.Height) - audioLayer.Properties.Height = newHeight; - else - audioLayer.Properties.Height = audioLayer.Properties.Height - settings.FadeSpeed; - if (audioLayer.Properties.Height < 0) - audioLayer.Properties.Height = 0; - - // Reverse the direction if settings require it - if (settings.Direction == Direction.BottomToTop) - audioLayer.Properties.Y = settings.Y + (settings.Height - audioLayer.Properties.Height); - - FakeUpdate(settings, audioLayer); - index++; - } - } - - private void ApplyHorizontal(List spectrumData, AudioPropertiesModel settings) - { - var index = 0; - foreach (var audioLayer in _audioLayers) - { - int width; - if (spectrumData.Count > index) - width = (int) Math.Round(spectrumData[index]/2.55); - else - width = 0; - - // Apply Sensitivity setting - width = width*settings.Sensitivity; - - var newWidth = settings.Width/100.0*width; - if (newWidth >= audioLayer.Properties.Width) - audioLayer.Properties.Width = newWidth; - else - audioLayer.Properties.Width = audioLayer.Properties.Width - settings.FadeSpeed; - if (audioLayer.Properties.Width < 0) - audioLayer.Properties.Width = 0; - - audioLayer.Properties.Brush = settings.Brush; - audioLayer.Properties.Contain = false; - - // Reverse the direction if settings require it - if (settings.Direction == Direction.RightToLeft) - audioLayer.Properties.X = settings.X + (settings.Width - audioLayer.Properties.Width); - - FakeUpdate(settings, audioLayer); - index++; - } - } - - /// - /// Updates the layer manually faking the width and height for a properly working animation - /// - /// - /// - private static void FakeUpdate(LayerPropertiesModel settings, LayerModel audioLayer) - { - // Call the regular update - audioLayer.LayerType?.Update(audioLayer, null); - - // Store the original height and width - var oldHeight = audioLayer.Properties.Height; - var oldWidth = audioLayer.Properties.Width; - - // Fake the height and width and update the animation - audioLayer.Properties.Width = settings.Width; - audioLayer.Properties.Height = settings.Height; - audioLayer.LastRender = DateTime.Now; - audioLayer.LayerAnimation?.Update(audioLayer, true); - - // Restore the height and width - audioLayer.Properties.Height = oldHeight; - audioLayer.Properties.Width = oldWidth; - } - + /// /// Sets up the inner layers when the settings have changed /// @@ -230,7 +141,7 @@ namespace Artemis.Profiles.Layers.Types.Audio var currentSettings = JsonConvert.SerializeObject(settings, Formatting.Indented); var currentType = _audioLayers.FirstOrDefault()?.LayerAnimation?.GetType(); - if (currentSettings == _previousSettings && (layerModel.LayerAnimation.GetType() == currentType)) + if (currentSettings == _previousSettings && layerModel.LayerAnimation.GetType() == currentType) return; _previousSettings = JsonConvert.SerializeObject(settings, Formatting.Indented); @@ -249,6 +160,7 @@ namespace Artemis.Profiles.Layers.Types.Audio default: throw new ArgumentOutOfRangeException(); } + _lineSpectrum = _audioCapture.GetLineSpectrum(_audioLayers.Count, 5, ScalingStrategy.Decibel); } private void SetupVertical(LayerModel layerModel) diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs index 0c5009373..0df27992d 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs @@ -118,7 +118,7 @@ namespace Artemis.ViewModels.Profiles var renderLayers = GetRenderLayers(); // Draw the current frame to the preview - var keyboardRect = _deviceManager.ActiveKeyboard.KeyboardRectangle(4); + var keyboardRect = _deviceManager.ActiveKeyboard.KeyboardRectangle(); var visual = new DrawingVisual(); using (var drawingContext = visual.RenderOpen()) { diff --git a/Artemis/Artemis/packages.config b/Artemis/Artemis/packages.config index 507184401..55b725806 100644 --- a/Artemis/Artemis/packages.config +++ b/Artemis/Artemis/packages.config @@ -5,6 +5,7 @@ + @@ -15,7 +16,6 @@ - From 0656687b881fd5bd00ffe61d1f6e3a8b8133b67a Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Wed, 11 Jan 2017 22:34:05 +0100 Subject: [PATCH 02/32] Audio layer progress --- Artemis/Artemis/Artemis.csproj | 1 + Artemis/Artemis/ArtemisBootstrapper.cs | 1 + Artemis/Artemis/DAL/ProfileProvider.cs | 2 +- .../DeviceProviders/Corsair/CorsairHeadset.cs | 2 +- .../DeviceProviders/Corsair/CorsairMouse.cs | 2 +- .../Corsair/CorsairMousemat.cs | 2 +- .../DeviceProviders/KeyboardProvider.cs | 5 +- .../Logitech/LogitechGeneric.cs | 2 +- Artemis/Artemis/Managers/ProfileManager.cs | 5 + .../Audio/AudioCapturing/AudioCapture.cs | 147 ++++++++++---- .../Audio/AudioCapturing/LineSpectrum.cs | 25 ++- .../Audio/AudioCapturing/SingleSpectrum.cs | 31 +++ .../Profiles/Layers/Types/Audio/AudioType.cs | 188 ++++++------------ Artemis/Artemis/Profiles/ProfileModel.cs | 8 +- Artemis/Artemis/Utilities/ImageUtilities.cs | 1 + .../Profiles/ProfileEditorViewModel.cs | 15 +- .../ViewModels/Profiles/ProfileViewModel.cs | 3 +- 17 files changed, 232 insertions(+), 208 deletions(-) create mode 100644 Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SingleSpectrum.cs diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index ab2de8b36..8f81676a9 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -494,6 +494,7 @@ + diff --git a/Artemis/Artemis/ArtemisBootstrapper.cs b/Artemis/Artemis/ArtemisBootstrapper.cs index e15174d44..bfd41ad63 100644 --- a/Artemis/Artemis/ArtemisBootstrapper.cs +++ b/Artemis/Artemis/ArtemisBootstrapper.cs @@ -91,6 +91,7 @@ namespace Artemis JsonConvert.DefaultSettings = () => settings; //TODO DarthAffe 17.12.2016: Is this the right location for this? + //TODO Move to Mainmanager and make disposable ActiveWindowHelper.Initialize(); } diff --git a/Artemis/Artemis/DAL/ProfileProvider.cs b/Artemis/Artemis/DAL/ProfileProvider.cs index 547582dc0..2721ae7d6 100644 --- a/Artemis/Artemis/DAL/ProfileProvider.cs +++ b/Artemis/Artemis/DAL/ProfileProvider.cs @@ -89,7 +89,7 @@ namespace Artemis.DAL } File.WriteAllText(path + $@"\{prof.Slug}.json", json); - Logger.Trace("Saved profile {0}/{1}/{2}", prof.KeyboardSlug, prof.GameName, prof.Name); + Logger.Debug("Saved profile {0}/{1}/{2}", prof.KeyboardSlug, prof.GameName, prof.Name); } } diff --git a/Artemis/Artemis/DeviceProviders/Corsair/CorsairHeadset.cs b/Artemis/Artemis/DeviceProviders/Corsair/CorsairHeadset.cs index 908d4ae07..9883c0253 100644 --- a/Artemis/Artemis/DeviceProviders/Corsair/CorsairHeadset.cs +++ b/Artemis/Artemis/DeviceProviders/Corsair/CorsairHeadset.cs @@ -34,7 +34,7 @@ namespace Artemis.DeviceProviders.Corsair public override void Disable() { - throw new NotImplementedException("Can only disable a keyboard"); + throw new NotSupportedException("Can only disable a keyboard"); } public override void UpdateDevice(Bitmap bitmap) diff --git a/Artemis/Artemis/DeviceProviders/Corsair/CorsairMouse.cs b/Artemis/Artemis/DeviceProviders/Corsair/CorsairMouse.cs index 7e18ba746..20767e34b 100644 --- a/Artemis/Artemis/DeviceProviders/Corsair/CorsairMouse.cs +++ b/Artemis/Artemis/DeviceProviders/Corsair/CorsairMouse.cs @@ -34,7 +34,7 @@ namespace Artemis.DeviceProviders.Corsair public override void Disable() { - throw new NotImplementedException("Can only disable a keyboard"); + throw new NotSupportedException("Can only disable a keyboard"); } public override void UpdateDevice(Bitmap bitmap) diff --git a/Artemis/Artemis/DeviceProviders/Corsair/CorsairMousemat.cs b/Artemis/Artemis/DeviceProviders/Corsair/CorsairMousemat.cs index 3062862d5..421d3b958 100644 --- a/Artemis/Artemis/DeviceProviders/Corsair/CorsairMousemat.cs +++ b/Artemis/Artemis/DeviceProviders/Corsair/CorsairMousemat.cs @@ -34,7 +34,7 @@ namespace Artemis.DeviceProviders.Corsair public override void Disable() { - throw new NotImplementedException("Can only disable a keyboard"); + throw new NotSupportedException("Can only disable a keyboard"); } public override void UpdateDevice(Bitmap bitmap) diff --git a/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs b/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs index afa83b09c..59b4889fa 100644 --- a/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs +++ b/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs @@ -89,13 +89,12 @@ namespace Artemis.DeviceProviders public override void UpdateDevice(Bitmap bitmap) { - throw new NotImplementedException("KeyboardProvider doesn't implement UpdateDevice, use DrawBitmap instead."); + throw new NotSupportedException("KeyboardProvider doesn't implement UpdateDevice, use DrawBitmap instead."); } public override bool TryEnable() { - throw new NotImplementedException( - "KeyboardProvider doesn't implement TryEnable, use CanEnableAsync instead."); + throw new NotSupportedException("KeyboardProvider doesn't implement TryEnable, use CanEnableAsync instead."); } /// diff --git a/Artemis/Artemis/DeviceProviders/Logitech/LogitechGeneric.cs b/Artemis/Artemis/DeviceProviders/Logitech/LogitechGeneric.cs index 5da53fe28..9c978a791 100644 --- a/Artemis/Artemis/DeviceProviders/Logitech/LogitechGeneric.cs +++ b/Artemis/Artemis/DeviceProviders/Logitech/LogitechGeneric.cs @@ -49,7 +49,7 @@ namespace Artemis.DeviceProviders.Logitech public override void Disable() { - throw new NotImplementedException("Can only disable a keyboard"); + throw new NotSupportedException("Can only disable a keyboard"); } } } \ No newline at end of file diff --git a/Artemis/Artemis/Managers/ProfileManager.cs b/Artemis/Artemis/Managers/ProfileManager.cs index a28397959..ed0268ff8 100644 --- a/Artemis/Artemis/Managers/ProfileManager.cs +++ b/Artemis/Artemis/Managers/ProfileManager.cs @@ -76,6 +76,11 @@ namespace Artemis.Managers return; _logger.Debug("Deactivate profile preview"); + + // Save the profile the editor was using + var activePreview = PreviewViewModules.FirstOrDefault(p => p.IsModuleActive); + activePreview?.ProfileEditor?.SaveSelectedProfile(); + var lastModule = _moduleManager.GetLastModule(); if (lastModule != null) _moduleManager.ChangeActiveModule(lastModule); diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs index a8602a20b..c1092fc31 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Timers; using CSCore; using CSCore.CoreAudioAPI; @@ -12,32 +13,31 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing public class AudioCapture { private const FftSize FftSize = CSCore.DSP.FftSize.Fft4096; + private readonly Timer _activityTimer; + private readonly Timer _volumeTimer; + private readonly double[] _volumeValues; + private SingleSpectrum _singleSpectrum; private WasapiLoopbackCapture _soundIn; private GainSource _source; private BasicSpectrumProvider _spectrumProvider; private GainSource _volume; - private Timer _timer; + private int _volumeIndex; public AudioCapture(ILogger logger, MMDevice device) { Logger = logger; Device = device; + DesiredAverage = 0.75; - _timer = new Timer(1000); - _timer.Elapsed += TimerOnElapsed; + _volumeValues = new double[5]; + _volumeIndex = 0; + _activityTimer = new Timer(1000); + _activityTimer.Elapsed += ActivityTimerOnElapsed; + _volumeTimer = new Timer(200); + _volumeTimer.Elapsed += VolumeTimerOnElapsed; } - private void TimerOnElapsed(object sender, ElapsedEventArgs e) - { - // If MayStop is true for longer than a second, this will stop the audio capture - if (MayStop) - { - Stop(); - MayStop = false; - } - else - MayStop = true; - } + public double DesiredAverage { get; set; } public bool MayStop { get; set; } @@ -52,7 +52,64 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing public MMDevice Device { get; } public bool Running { get; set; } - public LineSpectrum GetLineSpectrum(int barCount, int volume, ScalingStrategy scalingStrategy) + private void VolumeTimerOnElapsed(object sender, ElapsedEventArgs e) + { + if (Volume <= 0) + Volume = 1; + + var currentValue = _singleSpectrum.GetValue(); + if (currentValue == null) + return; + + _volumeValues[_volumeIndex] = currentValue.Value; + + if (_volumeIndex == 4) + { + _volumeIndex = 0; + } + else + { + _volumeIndex++; + return; + } + + var averageVolume = _volumeValues.Average(); + // Don't adjust when there is virtually no audio + if (averageVolume < 0.01) + return; + // Don't bother when the volume with within a certain marigin + if (averageVolume > DesiredAverage - 0.1 && averageVolume < DesiredAverage + 0.1) + return; + + if (averageVolume < DesiredAverage && Volume < 50) + { + Logger.Trace("averageVolume:{0} | DesiredAverage:{1} | Volume:{2} so increase.", currentValue, + DesiredAverage, Volume); + Volume++; + } + else if (Volume > 1) + { + Logger.Trace("averageVolume:{0} | DesiredAverage:{1} | Volume:{2} so decrease.", currentValue, + DesiredAverage, Volume); + Volume--; + } + } + + private void ActivityTimerOnElapsed(object sender, ElapsedEventArgs e) + { + // If MayStop is true for longer than a second, this will stop the audio capture + if (MayStop) + { + Stop(); + MayStop = false; + } + else + { + MayStop = true; + } + } + + public LineSpectrum GetLineSpectrum(int barCount, ScalingStrategy scalingStrategy) { return new LineSpectrum(FftSize) { @@ -64,11 +121,41 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing }; } - public void Start() + public void Stop() { + if (!Running) + return; + + Logger.Debug("Stopping audio capture for device: {0}", Device?.FriendlyName ?? "default"); + + try + { + _activityTimer.Stop(); + _volumeTimer.Stop(); + + _soundIn.Stop(); + _soundIn.Dispose(); + _source.Dispose(); + _soundIn = null; + _source = null; + + Running = false; + } + catch (Exception e) + { + Logger.Warn(e, "Failed to stop WASAPI audio capture"); + } + } + + public void Pulse() + { + MayStop = false; + if (Running) return; + Logger.Debug("Starting audio capture for device: {0}", Device?.FriendlyName ?? "default"); + try { _soundIn = new WasapiLoopbackCapture(); @@ -99,8 +186,12 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing } }; + _singleSpectrum = new SingleSpectrum(FftSize, _spectrumProvider); + + _activityTimer.Start(); + _volumeTimer.Start(); + _soundIn.Start(); - _timer.Start(); Running = true; MayStop = false; } @@ -109,27 +200,5 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing Logger.Warn(e, "Failed to start WASAPI audio capture"); } } - - public void Stop() - { - if (!Running) - return; - - try - { - _timer.Stop(); - _soundIn.Stop(); - _soundIn.Dispose(); - _source.Dispose(); - _soundIn = null; - _source = null; - - Running = false; - } - catch (Exception e) - { - Logger.Warn(e, "Failed to stop WASAPI audio capture"); - } - } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs index 39c900ea4..c4373e78b 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs @@ -1,6 +1,5 @@ using System; -using System.Collections.Generic; -using Artemis.Profiles.Layers.Models; +using System.Windows; using CSCore.DSP; namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing @@ -27,30 +26,36 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing } } - public void SetupLayersVertical(double height, List audioLayers) + public void UpdateLinesVertical(double height, Point[] points) { - var fftBuffer = new float[(int)FftSize]; + var fftBuffer = new float[(int) FftSize]; // get the fft result from the spectrum provider if (!SpectrumProvider.GetFftData(fftBuffer, this)) return; var spectrumPoints = CalculateSpectrumPoints(height, fftBuffer); - foreach (var p in spectrumPoints) - audioLayers[p.SpectrumPointIndex].Height = p.Value; + for (var index = 0; index < spectrumPoints.Length; index++) + { + var spectrumPointData = spectrumPoints[index]; + points[index].Y = spectrumPointData.Value; + } } - public void SetupLayersHorizontal(double width, List audioLayers) + public void UpdateLinesHorizontal(double width, Point[] points) { - var fftBuffer = new float[(int)FftSize]; + var fftBuffer = new float[(int) FftSize]; // get the fft result from the spectrum provider if (!SpectrumProvider.GetFftData(fftBuffer, this)) return; var spectrumPoints = CalculateSpectrumPoints(width, fftBuffer); - foreach (var p in spectrumPoints) - audioLayers[p.SpectrumPointIndex].Width = p.Value; + for (var index = 0; index < spectrumPoints.Length; index++) + { + var spectrumPointData = spectrumPoints[index]; + points[index].X = spectrumPointData.Value; + } } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SingleSpectrum.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SingleSpectrum.cs new file mode 100644 index 000000000..4757f1dfa --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SingleSpectrum.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using Artemis.Profiles.Layers.Models; +using CSCore.DSP; + +namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing +{ + public sealed class SingleSpectrum : SpectrumBase + { + public SingleSpectrum(FftSize fftSize, ISpectrumProvider spectrumProvider) + { + SpectrumProvider = spectrumProvider; + SpectrumResolution = 1; + FftSize = fftSize; + + UpdateFrequencyMapping(); + } + + public double? GetValue() + { + var fftBuffer = new float[(int)FftSize]; + + // get the fft result from the spectrum provider + if (SpectrumProvider == null || !SpectrumProvider.GetFftData(fftBuffer, this)) + return null; + + var spectrumPoints = CalculateSpectrumPoints(1, fftBuffer); + return spectrumPoints[0].Value; + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index 99879e224..f2d3dfde8 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows; +using System.Windows; using System.Windows.Media; using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Abstract; @@ -11,25 +8,18 @@ using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; using Artemis.Properties; using Artemis.Utilities; using Artemis.ViewModels.Profiles; -using Newtonsoft.Json; -using Ninject; namespace Artemis.Profiles.Layers.Types.Audio { public class AudioType : ILayerType { - private readonly List _audioLayers = new List(); - private readonly IKernel _kernel; - - private DateTime _lastUpdate; + private readonly AudioCapture _audioCapture; private int _lines; - private string _previousSettings; private LineSpectrum _lineSpectrum; - private AudioCapture _audioCapture; + private Point[] _points; - public AudioType(IKernel kernel, AudioCaptureManager audioCaptureManager) + public AudioType(AudioCaptureManager audioCaptureManager) { - _kernel = kernel; _audioCapture = audioCaptureManager.GetAudioCapture(null); } @@ -52,26 +42,29 @@ namespace Artemis.Profiles.Layers.Types.Audio public void Draw(LayerModel layerModel, DrawingContext c) { - lock (_audioLayers) + var parentX = layerModel.X * 4; + var parentY = layerModel.Y * 4; + var pen = new Pen(layerModel.Brush, 4); + var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; + if (direction == Direction.BottomToTop || direction == Direction.TopToBottom) { - foreach (var audioLayer in _audioLayers) + for (var index = 0; index < _points.Length; index++) { - // This is cheating but it ensures that the brush is drawn across the entire main-layer - var oldWidth = audioLayer.Properties.Width; - var oldHeight = audioLayer.Properties.Height; - var oldX = audioLayer.Properties.X; - var oldY = audioLayer.Properties.Y; - - audioLayer.Properties.Width = layerModel.Properties.Width; - audioLayer.Properties.Height = layerModel.Properties.Height; - audioLayer.Properties.X = layerModel.Properties.X; - audioLayer.Properties.Y = layerModel.Properties.Y; - audioLayer.LayerType.Draw(audioLayer, c); - - audioLayer.Properties.Width = oldWidth; - audioLayer.Properties.Height = oldHeight; - audioLayer.Properties.X = oldX; - audioLayer.Properties.Y = oldY; + var startPoint = new Point(index * 4 + 2 + parentX, _points[index].Y * 4 + parentY); + var endPoint = new Point(index * 4 + 2 + parentX, parentY); + var clip = new Rect(startPoint, endPoint); + clip.Width = 4; + c.PushClip(new RectangleGeometry(new Rect(startPoint, endPoint))); + var point = new Point(index * 4 + 2 + parentX, _points[index].Y * 4 + parentY); + c.DrawLine(pen, startPoint, endPoint); + } + } + else + { + for (var index = 0; index < _points.Length; index++) + { + var point = new Point(_points[index].X * 4 + parentX, index * 4 + 2 + parentY); + c.DrawLine(pen, point, new Point(parentX, index * 4 + 2 + parentY)); } } } @@ -79,31 +72,39 @@ namespace Artemis.Profiles.Layers.Types.Audio public void Update(LayerModel layerModel, ModuleDataModel dataModel, bool isPreview = false) { layerModel.ApplyProperties(true); - if (isPreview) + + var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; + + int currentLines; + double currentHeight; + if (direction == Direction.BottomToTop || direction == Direction.TopToBottom) + { + currentLines = (int) layerModel.Width; + currentHeight = layerModel.Height; + } + else + { + currentLines = (int) layerModel.Height; + currentHeight = layerModel.Width; + } + + if (_lines != currentLines) + { + _lines = currentLines; + _points = new Point[_lines]; + _lineSpectrum = _audioCapture.GetLineSpectrum(_lines, ScalingStrategy.Decibel); + } + + // Let audio capture know it is being listened to + _audioCapture.Pulse(); + + if (_lineSpectrum == null) return; - // Start audio capture in case it wasn't running - _audioCapture.Start(); - _audioCapture.MayStop = false; - - lock (_audioLayers) - { - // Called every update but only runs every second - SetupLayers(layerModel); - - var settings = (AudioPropertiesModel) layerModel.Properties; - switch (settings.Direction) - { - case Direction.TopToBottom: - case Direction.BottomToTop: - _lineSpectrum.SetupLayersVertical(layerModel.Height, _audioLayers); - break; - case Direction.LeftToRight: - case Direction.RightToLeft: - _lineSpectrum.SetupLayersHorizontal(layerModel.Width, _audioLayers); - break; - } - } + if (direction == Direction.BottomToTop || direction == Direction.TopToBottom) + _lineSpectrum.UpdateLinesVertical(currentHeight, _points); + else + _lineSpectrum.UpdateLinesHorizontal(currentHeight, _points); } public void SetupProperties(LayerModel layerModel) @@ -125,82 +126,5 @@ namespace Artemis.Profiles.Layers.Types.Audio return layerPropertiesViewModel; return new AudioPropertiesViewModel(layerEditorViewModel); } - - /// - /// Sets up the inner layers when the settings have changed - /// - /// - private void SetupLayers(LayerModel layerModel) - { - // Checking on settings update is expensive, only do it every second - if (DateTime.Now - _lastUpdate < TimeSpan.FromSeconds(1)) - return; - _lastUpdate = DateTime.Now; - - var settings = (AudioPropertiesModel) layerModel.Properties; - var currentSettings = JsonConvert.SerializeObject(settings, Formatting.Indented); - var currentType = _audioLayers.FirstOrDefault()?.LayerAnimation?.GetType(); - - if (currentSettings == _previousSettings && layerModel.LayerAnimation.GetType() == currentType) - return; - - _previousSettings = JsonConvert.SerializeObject(settings, Formatting.Indented); - - _audioLayers.Clear(); - switch (settings.Direction) - { - case Direction.TopToBottom: - case Direction.BottomToTop: - SetupVertical(layerModel); - break; - case Direction.LeftToRight: - case Direction.RightToLeft: - SetupHorizontal(layerModel); - break; - default: - throw new ArgumentOutOfRangeException(); - } - _lineSpectrum = _audioCapture.GetLineSpectrum(_audioLayers.Count, 5, ScalingStrategy.Decibel); - } - - private void SetupVertical(LayerModel layerModel) - { - _lines = (int) layerModel.Properties.Width; - for (var i = 0; i < _lines; i++) - { - var layer = LayerModel.CreateLayer(); - layer.Properties.X = layerModel.Properties.X + i; - layer.Properties.Y = layerModel.Properties.Y; - layer.Properties.Width = 1; - layer.Properties.Height = 0; - layer.Properties.AnimationSpeed = layerModel.Properties.AnimationSpeed; - layer.Properties.Brush = layerModel.Properties.Brush; - layer.Properties.Contain = false; - layer.LayerAnimation = (ILayerAnimation) _kernel.Get(layerModel.LayerAnimation.GetType()); - - _audioLayers.Add(layer); - layer.Update(null, false, true); - } - } - - private void SetupHorizontal(LayerModel layerModel) - { - _lines = (int) layerModel.Properties.Height; - for (var i = 0; i < _lines; i++) - { - var layer = LayerModel.CreateLayer(); - layer.Properties.X = layerModel.Properties.X; - layer.Properties.Y = layerModel.Properties.Y + i; - layer.Properties.Width = 0; - layer.Properties.Height = 1; - layer.Properties.AnimationSpeed = layerModel.Properties.AnimationSpeed; - layer.Properties.Brush = layerModel.Properties.Brush; - layer.Properties.Contain = false; - layer.LayerAnimation = (ILayerAnimation) _kernel.Get(layerModel.LayerAnimation.GetType()); - - _audioLayers.Add(layer); - layer.Update(null, false, true); - } - } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/ProfileModel.cs b/Artemis/Artemis/Profiles/ProfileModel.cs index 0f076058a..5dd3cbfed 100644 --- a/Artemis/Artemis/Profiles/ProfileModel.cs +++ b/Artemis/Artemis/Profiles/ProfileModel.cs @@ -108,8 +108,10 @@ namespace Artemis.Profiles Rect rect, bool preview) { renderLayers = renderLayers.Where(rl => rl.LayerType.DrawType == drawType).ToList(); + if (!renderLayers.Any()) + return; + var visual = new DrawingVisual(); - var layerModels = renderLayers.ToList(); using (var c = visual.RenderOpen()) { // Setup the DrawingVisual's size @@ -117,12 +119,12 @@ namespace Artemis.Profiles c.DrawRectangle(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)), null, rect); // Update the layers - foreach (var layerModel in layerModels) + foreach (var layerModel in renderLayers) layerModel.Update(dataModel, preview, true); RaiseDeviceUpdatedEvent(new ProfileDeviceEventsArg(drawType, dataModel, preview, null)); // Draw the layers - foreach (var layerModel in layerModels) + foreach (var layerModel in renderLayers) layerModel.Draw(dataModel, c, preview, true); RaiseDeviceDrawnEvent(new ProfileDeviceEventsArg(drawType, dataModel, preview, c)); diff --git a/Artemis/Artemis/Utilities/ImageUtilities.cs b/Artemis/Artemis/Utilities/ImageUtilities.cs index 51fc2f66f..d8ac1122d 100644 --- a/Artemis/Artemis/Utilities/ImageUtilities.cs +++ b/Artemis/Artemis/Utilities/ImageUtilities.cs @@ -56,6 +56,7 @@ namespace Artemis.Utilities public static Bitmap DrawingVisualToBitmap(DrawingVisual visual, Rect rect) { + // TODO: Improve performance by dividing by 4 here var bmp = new RenderTargetBitmap((int) rect.Width, (int) rect.Height, 96, 96, PixelFormats.Pbgra32); bmp.Render(visual); diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs index 13afbfbd3..af0659dd9 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Timers; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Media; @@ -31,7 +30,6 @@ using DragDropEffects = System.Windows.DragDropEffects; using IDropTarget = GongSolutions.Wpf.DragDrop.IDropTarget; using MouseEventArgs = System.Windows.Input.MouseEventArgs; using Screen = Caliburn.Micro.Screen; -using Timer = System.Timers.Timer; namespace Artemis.ViewModels.Profiles { @@ -41,7 +39,6 @@ namespace Artemis.ViewModels.Profiles private readonly MetroDialogService _dialogService; private readonly LuaManager _luaManager; private readonly ModuleModel _moduleModel; - private readonly Timer _saveTimer; private readonly WindowService _windowService; private ImageSource _keyboardPreview; private ObservableCollection _layers; @@ -69,10 +66,6 @@ namespace Artemis.ViewModels.Profiles _deviceManager.OnKeyboardChanged += DeviceManagerOnOnKeyboardChanged; _moduleModel.ProfileChanged += ModuleModelOnProfileChanged; LoadProfiles(); - - _saveTimer = new Timer(5000); - _saveTimer.Elapsed += ProfileSaveHandler; - _saveTimer.Start(); } public ProfileViewModel ProfileViewModel { get; set; } @@ -695,11 +688,6 @@ namespace Artemis.ViewModels.Profiles NotifyOfPropertyChange(() => LayerSelected); } - private void ProfileSaveHandler(object sender, ElapsedEventArgs e) - { - SaveSelectedProfile(); - } - public void SaveSelectedProfile() { if (_saving || SelectedProfile == null || _deviceManager.ChangingKeyboard) @@ -779,9 +767,8 @@ namespace Artemis.ViewModels.Profiles public void Dispose() { + SaveSelectedProfile(); ProfileViewModel.Dispose(); - _saveTimer?.Stop(); - _saveTimer?.Dispose(); _watcher?.Dispose(); } diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs index 0df27992d..bc74299a0 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs @@ -167,7 +167,7 @@ namespace Artemis.ViewModels.Profiles new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 0.7)); } - SelectedProfile.RaiseDeviceDrawnEvent(new ProfileDeviceEventsArg(DrawType.Preview, null, true, drawingContext)); + SelectedProfile?.RaiseDeviceDrawnEvent(new ProfileDeviceEventsArg(DrawType.Preview, null, true, drawingContext)); // Remove the clip drawingContext.Pop(); @@ -380,7 +380,6 @@ namespace Artemis.ViewModels.Profiles public void Dispose() { - _keyboardPreviewCursor?.Dispose(); _loopManager.RenderCompleted -= LoopManagerOnRenderCompleted; _deviceManager.OnKeyboardChanged -= DeviceManagerOnOnKeyboardChanged; } From de0ff1ebadd873677fe0bbd87fd163a6e7658f87 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Thu, 12 Jan 2017 14:21:27 +0100 Subject: [PATCH 03/32] Added AudioType brush drawing and animations back in --- .../Audio/AudioCapturing/LineSpectrum.cs | 27 +--- .../Profiles/Layers/Types/Audio/AudioType.cs | 123 ++++++++++++------ 2 files changed, 91 insertions(+), 59 deletions(-) diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs index c4373e78b..1830033be 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Windows; using CSCore.DSP; @@ -26,36 +28,17 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing } } - public void UpdateLinesVertical(double height, Point[] points) + public List GetLineValues(double height) { var fftBuffer = new float[(int) FftSize]; // get the fft result from the spectrum provider if (!SpectrumProvider.GetFftData(fftBuffer, this)) - return; + return null; var spectrumPoints = CalculateSpectrumPoints(height, fftBuffer); - for (var index = 0; index < spectrumPoints.Length; index++) - { - var spectrumPointData = spectrumPoints[index]; - points[index].Y = spectrumPointData.Value; - } - } + return spectrumPoints?.Select(s => s.Value).ToList(); - public void UpdateLinesHorizontal(double width, Point[] points) - { - var fftBuffer = new float[(int) FftSize]; - - // get the fft result from the spectrum provider - if (!SpectrumProvider.GetFftData(fftBuffer, this)) - return; - - var spectrumPoints = CalculateSpectrumPoints(width, fftBuffer); - for (var index = 0; index < spectrumPoints.Length; index++) - { - var spectrumPointData = spectrumPoints[index]; - points[index].X = spectrumPointData.Value; - } } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index f2d3dfde8..5bf340179 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -1,7 +1,9 @@ -using System.Windows; +using System.Collections.Generic; +using System.Windows; using System.Windows.Media; using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Abstract; +using Artemis.Profiles.Layers.Animations; using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; @@ -13,10 +15,11 @@ namespace Artemis.Profiles.Layers.Types.Audio { public class AudioType : ILayerType { + private const GeometryCombineMode CombineMode = GeometryCombineMode.Union; private readonly AudioCapture _audioCapture; private int _lines; private LineSpectrum _lineSpectrum; - private Point[] _points; + private List _lineValues; public AudioType(AudioCaptureManager audioCaptureManager) { @@ -42,31 +45,56 @@ namespace Artemis.Profiles.Layers.Types.Audio public void Draw(LayerModel layerModel, DrawingContext c) { - var parentX = layerModel.X * 4; - var parentY = layerModel.Y * 4; - var pen = new Pen(layerModel.Brush, 4); + if (_lineValues == null) + return; + + var parentX = layerModel.X; + var parentY = layerModel.Y; var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; - if (direction == Direction.BottomToTop || direction == Direction.TopToBottom) + + // Create a geometry that will be formed by all the bars + Geometry barGeometry = new RectangleGeometry(); + + switch (direction) { - for (var index = 0; index < _points.Length; index++) - { - var startPoint = new Point(index * 4 + 2 + parentX, _points[index].Y * 4 + parentY); - var endPoint = new Point(index * 4 + 2 + parentX, parentY); - var clip = new Rect(startPoint, endPoint); - clip.Width = 4; - c.PushClip(new RectangleGeometry(new Rect(startPoint, endPoint))); - var point = new Point(index * 4 + 2 + parentX, _points[index].Y * 4 + parentY); - c.DrawLine(pen, startPoint, endPoint); - } - } - else - { - for (var index = 0; index < _points.Length; index++) - { - var point = new Point(_points[index].X * 4 + parentX, index * 4 + 2 + parentY); - c.DrawLine(pen, point, new Point(parentX, index * 4 + 2 + parentY)); - } + case Direction.BottomToTop: + for (var index = 0; index < _lineValues.Count; index++) + { + var clipRect = new Rect((parentX + index)*4, parentY*4, 4, _lineValues[index]*4); + var barRect = new RectangleGeometry(clipRect); + barGeometry = Geometry.Combine(barGeometry, barRect, CombineMode, Transform.Identity); + } + break; + case Direction.TopToBottom: + for (var index = 0; index < _lineValues.Count; index++) + { + var clipRect = new Rect((parentX + index)*4, parentY*4, 4, _lineValues[index]*4); + var barRect = new RectangleGeometry(clipRect); + barGeometry = Geometry.Combine(barGeometry, barRect, CombineMode, Transform.Identity); + } + break; + case Direction.LeftToRight: + for (var index = 0; index < _lineValues.Count; index++) + { + var clipRect = new Rect((parentX + index)*4, parentY*4, 4, _lineValues[index]*4); + var barRect = new RectangleGeometry(clipRect); + barGeometry = Geometry.Combine(barGeometry, barRect, CombineMode, Transform.Identity); + } + break; + default: + for (var index = 0; index < _lineValues.Count; index++) + { + var clipRect = new Rect((parentX + index)*4, parentY*4, 4, _lineValues[index]*4); + var barRect = new RectangleGeometry(clipRect); + barGeometry = Geometry.Combine(barGeometry, barRect, CombineMode, Transform.Identity); + } + break; } + + // Push the created geometry + c.PushClip(barGeometry); + BrushDraw(layerModel, c); + c.Pop(); } public void Update(LayerModel layerModel, ModuleDataModel dataModel, bool isPreview = false) @@ -88,23 +116,18 @@ namespace Artemis.Profiles.Layers.Types.Audio currentHeight = layerModel.Width; } - if (_lines != currentLines) - { - _lines = currentLines; - _points = new Point[_lines]; - _lineSpectrum = _audioCapture.GetLineSpectrum(_lines, ScalingStrategy.Decibel); - } - // Let audio capture know it is being listened to _audioCapture.Pulse(); - if (_lineSpectrum == null) - return; + if (_lines != currentLines || _lineSpectrum == null) + { + _lines = currentLines; + _lineSpectrum = _audioCapture.GetLineSpectrum(_lines, ScalingStrategy.Decibel); + } - if (direction == Direction.BottomToTop || direction == Direction.TopToBottom) - _lineSpectrum.UpdateLinesVertical(currentHeight, _points); - else - _lineSpectrum.UpdateLinesHorizontal(currentHeight, _points); + var newLineValues = _lineSpectrum?.GetLineValues(currentHeight); + if (newLineValues != null) + _lineValues = newLineValues; } public void SetupProperties(LayerModel layerModel) @@ -126,5 +149,31 @@ namespace Artemis.Profiles.Layers.Types.Audio return layerPropertiesViewModel; return new AudioPropertiesViewModel(layerEditorViewModel); } + + public void BrushDraw(LayerModel layerModel, DrawingContext c) + { + // If an animation is present, let it handle the drawing + if (layerModel.LayerAnimation != null && !(layerModel.LayerAnimation is NoneAnimation)) + { + layerModel.LayerAnimation.Draw(layerModel, c); + return; + } + + // Otherwise draw the rectangle with its layer.AppliedProperties dimensions and brush + var rect = layerModel.Properties.Contain + ? layerModel.LayerRect() + : new Rect(layerModel.Properties.X*4, layerModel.Properties.Y*4, + layerModel.Properties.Width*4, layerModel.Properties.Height*4); + + var clip = layerModel.LayerRect(); + + // Can't meddle with the original brush because it's frozen. + var brush = layerModel.Brush.Clone(); + brush.Opacity = layerModel.Opacity; + + c.PushClip(new RectangleGeometry(clip)); + c.DrawRectangle(brush, null, rect); + c.Pop(); + } } } \ No newline at end of file From 15275c83a07c52635383300bee1ad4d29befb238 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Thu, 12 Jan 2017 19:50:56 +0100 Subject: [PATCH 04/32] Implemented BottomToTop again --- Artemis/Artemis/Artemis.csproj | 3 +- .../InjectionModules/ManagerModules.cs | 2 +- Artemis/Artemis/Managers/MainManager.cs | 6 +- .../{ProfileManager.cs => PreviewManager.cs} | 6 +- .../Modules/Abstract/ModuleViewModel.cs | 1 - .../Audio/AudioCapturing/AudioCapture.cs | 65 ++----------------- .../Profiles/Layers/Types/Audio/AudioType.cs | 8 +-- Artemis/Artemis/Utilities/EditorHelper.cs | 12 ++++ .../Artemis/ViewModels/GeneralViewModel.cs | 6 +- .../Profiles/ProfileEditorViewModel.cs | 58 +++++++++++------ .../ViewModels/Profiles/ProfileViewModel.cs | 17 ++--- Artemis/Artemis/ViewModels/ShellViewModel.cs | 2 +- .../Views/Profiles/ProfileEditorView.xaml | 2 +- 13 files changed, 77 insertions(+), 111 deletions(-) rename Artemis/Artemis/Managers/{ProfileManager.cs => PreviewManager.cs} (95%) create mode 100644 Artemis/Artemis/Utilities/EditorHelper.cs diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 8f81676a9..3500de5f3 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -355,7 +355,7 @@ - + @@ -622,6 +622,7 @@ + diff --git a/Artemis/Artemis/InjectionModules/ManagerModules.cs b/Artemis/Artemis/InjectionModules/ManagerModules.cs index 0d5870d63..b01f7fab9 100644 --- a/Artemis/Artemis/InjectionModules/ManagerModules.cs +++ b/Artemis/Artemis/InjectionModules/ManagerModules.cs @@ -11,7 +11,7 @@ namespace Artemis.InjectionModules Bind().ToSelf().InSingletonScope(); Bind().ToSelf().InSingletonScope(); Bind().ToSelf().InSingletonScope(); - Bind().ToSelf().InSingletonScope(); + Bind().ToSelf().InSingletonScope(); Bind().ToSelf().InSingletonScope(); } } diff --git a/Artemis/Artemis/Managers/MainManager.cs b/Artemis/Artemis/Managers/MainManager.cs index 6dcd1490b..f4de0de5f 100644 --- a/Artemis/Artemis/Managers/MainManager.cs +++ b/Artemis/Artemis/Managers/MainManager.cs @@ -22,14 +22,14 @@ namespace Artemis.Managers private readonly Timer _processTimer; public MainManager(ILogger logger, LoopManager loopManager, DeviceManager deviceManager, - ModuleManager moduleManager, ProfileManager profileManager, PipeServer pipeServer, + ModuleManager moduleManager, PreviewManager previewManager, PipeServer pipeServer, GameStateWebServer gameStateWebServer) { Logger = logger; LoopManager = loopManager; DeviceManager = deviceManager; ModuleManager = moduleManager; - ProfileManager = profileManager; + PreviewManager = previewManager; PipeServer = pipeServer; _processTimer = new Timer(1000); @@ -64,7 +64,7 @@ namespace Artemis.Managers public LoopManager LoopManager { get; } public DeviceManager DeviceManager { get; set; } public ModuleManager ModuleManager { get; set; } - public ProfileManager ProfileManager { get; set; } + public PreviewManager PreviewManager { get; set; } public PipeServer PipeServer { get; set; } public GameStateWebServer GameStateWebServer { get; set; } diff --git a/Artemis/Artemis/Managers/ProfileManager.cs b/Artemis/Artemis/Managers/PreviewManager.cs similarity index 95% rename from Artemis/Artemis/Managers/ProfileManager.cs rename to Artemis/Artemis/Managers/PreviewManager.cs index ed0268ff8..ff5657762 100644 --- a/Artemis/Artemis/Managers/ProfileManager.cs +++ b/Artemis/Artemis/Managers/PreviewManager.cs @@ -9,7 +9,7 @@ using Ninject.Extensions.Logging; namespace Artemis.Managers { - public class ProfileManager + public class PreviewManager { private readonly DeviceManager _deviceManager; private readonly GeneralSettings _generalSettings; @@ -17,7 +17,7 @@ namespace Artemis.Managers private readonly LoopManager _loopManager; private readonly ModuleManager _moduleManager; - public ProfileManager(ILogger logger, ModuleManager moduleManager, DeviceManager deviceManager, + public PreviewManager(ILogger logger, ModuleManager moduleManager, DeviceManager deviceManager, LoopManager loopManager) { _logger = logger; @@ -32,7 +32,7 @@ namespace Artemis.Managers profilePreviewTimer.Elapsed += SetupProfilePreview; profilePreviewTimer.Start(); - _logger.Info("Intialized ProfileManager"); + _logger.Info("Intialized PreviewManager"); } public List PreviewViewModules { get; set; } diff --git a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs index a1a6fd435..55fdfef5d 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs @@ -26,7 +26,6 @@ namespace Artemis.Modules.Abstract _generalSettings = DAL.SettingsProvider.Load(); ModuleModel = moduleModel; Settings = moduleModel.Settings; - _mainManager.EnabledChanged += MainManagerOnEnabledChanged; _moduleManager.EffectChanged += ModuleManagerOnModuleChanged; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs index c1092fc31..0cdeafc7c 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs @@ -13,7 +13,6 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing public class AudioCapture { private const FftSize FftSize = CSCore.DSP.FftSize.Fft4096; - private readonly Timer _activityTimer; private readonly Timer _volumeTimer; private readonly double[] _volumeValues; private SingleSpectrum _singleSpectrum; @@ -31,17 +30,15 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing _volumeValues = new double[5]; _volumeIndex = 0; - _activityTimer = new Timer(1000); - _activityTimer.Elapsed += ActivityTimerOnElapsed; _volumeTimer = new Timer(200); _volumeTimer.Elapsed += VolumeTimerOnElapsed; + + Start(); } - public double DesiredAverage { get; set; } - - public bool MayStop { get; set; } - public ILogger Logger { get; } + public MMDevice Device { get; } + public double DesiredAverage { get; set; } public float Volume { @@ -49,9 +46,6 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing set { _volume.Volume = value; } } - public MMDevice Device { get; } - public bool Running { get; set; } - private void VolumeTimerOnElapsed(object sender, ElapsedEventArgs e) { if (Volume <= 0) @@ -95,20 +89,6 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing } } - private void ActivityTimerOnElapsed(object sender, ElapsedEventArgs e) - { - // If MayStop is true for longer than a second, this will stop the audio capture - if (MayStop) - { - Stop(); - MayStop = false; - } - else - { - MayStop = true; - } - } - public LineSpectrum GetLineSpectrum(int barCount, ScalingStrategy scalingStrategy) { return new LineSpectrum(FftSize) @@ -121,39 +101,8 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing }; } - public void Stop() + private void Start() { - if (!Running) - return; - - Logger.Debug("Stopping audio capture for device: {0}", Device?.FriendlyName ?? "default"); - - try - { - _activityTimer.Stop(); - _volumeTimer.Stop(); - - _soundIn.Stop(); - _soundIn.Dispose(); - _source.Dispose(); - _soundIn = null; - _source = null; - - Running = false; - } - catch (Exception e) - { - Logger.Warn(e, "Failed to stop WASAPI audio capture"); - } - } - - public void Pulse() - { - MayStop = false; - - if (Running) - return; - Logger.Debug("Starting audio capture for device: {0}", Device?.FriendlyName ?? "default"); try @@ -188,12 +137,8 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing _singleSpectrum = new SingleSpectrum(FftSize, _spectrumProvider); - _activityTimer.Start(); _volumeTimer.Start(); - _soundIn.Start(); - Running = true; - MayStop = false; } catch (Exception e) { diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index 5bf340179..e983d0969 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -57,7 +57,7 @@ namespace Artemis.Profiles.Layers.Types.Audio switch (direction) { - case Direction.BottomToTop: + case Direction.TopToBottom: for (var index = 0; index < _lineValues.Count; index++) { var clipRect = new Rect((parentX + index)*4, parentY*4, 4, _lineValues[index]*4); @@ -65,10 +65,11 @@ namespace Artemis.Profiles.Layers.Types.Audio barGeometry = Geometry.Combine(barGeometry, barRect, CombineMode, Transform.Identity); } break; - case Direction.TopToBottom: + case Direction.BottomToTop: for (var index = 0; index < _lineValues.Count; index++) { var clipRect = new Rect((parentX + index)*4, parentY*4, 4, _lineValues[index]*4); + clipRect.Y = clipRect.Y + layerModel.Height*4 - clipRect.Height; var barRect = new RectangleGeometry(clipRect); barGeometry = Geometry.Combine(barGeometry, barRect, CombineMode, Transform.Identity); } @@ -116,9 +117,6 @@ namespace Artemis.Profiles.Layers.Types.Audio currentHeight = layerModel.Width; } - // Let audio capture know it is being listened to - _audioCapture.Pulse(); - if (_lines != currentLines || _lineSpectrum == null) { _lines = currentLines; diff --git a/Artemis/Artemis/Utilities/EditorHelper.cs b/Artemis/Artemis/Utilities/EditorHelper.cs new file mode 100644 index 000000000..ba7ab2eb1 --- /dev/null +++ b/Artemis/Artemis/Utilities/EditorHelper.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Artemis.Utilities +{ + public static class EditorHelper + { + } +} diff --git a/Artemis/Artemis/ViewModels/GeneralViewModel.cs b/Artemis/Artemis/ViewModels/GeneralViewModel.cs index f800712fe..c32531bf1 100644 --- a/Artemis/Artemis/ViewModels/GeneralViewModel.cs +++ b/Artemis/Artemis/ViewModels/GeneralViewModel.cs @@ -10,14 +10,14 @@ namespace Artemis.ViewModels { private readonly List _vms; - public GeneralViewModel(List moduleViewModels, ProfileManager profileManager) + public GeneralViewModel(List moduleViewModels, PreviewManager previewManager) { DisplayName = "General"; _vms = moduleViewModels.Where(m => !m.ModuleModel.IsOverlay && !m.ModuleModel.IsBoundToProcess) .OrderBy(m => m.DisplayName).ToList(); - profileManager.PreviewViewModules.Clear(); - profileManager.PreviewViewModules.AddRange(moduleViewModels.Where(m => m.UsesProfileEditor)); + previewManager.PreviewViewModules.Clear(); + previewManager.PreviewViewModules.AddRange(moduleViewModels.Where(m => m.UsesProfileEditor)); } protected override void OnActivate() diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs index af0659dd9..734598963 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs @@ -45,6 +45,8 @@ namespace Artemis.ViewModels.Profiles private ObservableCollection _profileNames; private bool _saving; private FileSystemWatcher _watcher; + private ProfileViewModel _profileViewModel; + private LayerModel _selectedLayer; public ProfileEditorViewModel(DeviceManager deviceManager, LuaManager luaManager, ModuleModel moduleModel, ProfileViewModel profileViewModel, MetroDialogService dialogService, WindowService windowService) @@ -62,13 +64,22 @@ namespace Artemis.ViewModels.Profiles ProfileViewModel.ModuleModel = _moduleModel; PropertyChanged += EditorStateHandler; - ProfileViewModel.PropertyChanged += LayerSelectedHandler; _deviceManager.OnKeyboardChanged += DeviceManagerOnOnKeyboardChanged; _moduleModel.ProfileChanged += ModuleModelOnProfileChanged; LoadProfiles(); } - public ProfileViewModel ProfileViewModel { get; set; } + public ProfileViewModel ProfileViewModel + { + get { return _profileViewModel; } + set + { + if (Equals(value, _profileViewModel)) return; + _profileViewModel = value; + NotifyOfPropertyChange(); + NotifyOfPropertyChange(nameof(LayerSelected)); + } + } public bool EditorEnabled => @@ -76,6 +87,21 @@ namespace Artemis.ViewModels.Profiles _deviceManager.ActiveKeyboard != null; public ProfileModel SelectedProfile => _moduleModel?.ProfileModel; + public LayerModel SelectedLayer + { + get { return _selectedLayer; } + set + { + if (_profileViewModel != null) + _profileViewModel.SelectedLayer = value; + if (Equals(value, _selectedLayer)) + return; + + _selectedLayer = value; + NotifyOfPropertyChange(() => SelectedLayer); + NotifyOfPropertyChange(() => LayerSelected); + } + } public ObservableCollection ProfileNames { @@ -126,7 +152,7 @@ namespace Artemis.ViewModels.Profiles public PreviewSettings? PreviewSettings => _deviceManager.ActiveKeyboard?.PreviewSettings; public bool ProfileSelected => SelectedProfile != null; - public bool LayerSelected => SelectedProfile != null && ProfileViewModel.SelectedLayer != null; + public bool LayerSelected => SelectedProfile != null && SelectedLayer != null; public void DragOver(IDropInfo dropInfo) { @@ -230,7 +256,7 @@ namespace Artemis.ViewModels.Profiles public void EditLayerFromDoubleClick() { - if (ProfileViewModel.SelectedLayer?.LayerType is FolderType) + if (SelectedLayer?.LayerType is FolderType) return; EditLayer(); @@ -238,10 +264,10 @@ namespace Artemis.ViewModels.Profiles public void EditLayer() { - if (ProfileViewModel.SelectedLayer == null) + if (SelectedLayer == null) return; - var selectedLayer = ProfileViewModel.SelectedLayer; + var selectedLayer = SelectedLayer; EditLayer(selectedLayer); } @@ -290,7 +316,7 @@ namespace Artemis.ViewModels.Profiles if (SelectedProfile == null) return null; - var layer = SelectedProfile.AddLayer(ProfileViewModel.SelectedLayer); + var layer = SelectedProfile.AddLayer(SelectedLayer); UpdateLayerList(layer); return layer; @@ -314,7 +340,7 @@ namespace Artemis.ViewModels.Profiles /// public void RemoveLayer() { - RemoveLayer(ProfileViewModel.SelectedLayer); + RemoveLayer(SelectedLayer); } /// @@ -368,10 +394,10 @@ namespace Artemis.ViewModels.Profiles /// public void CloneLayer() { - if (ProfileViewModel.SelectedLayer == null) + if (SelectedLayer == null) return; - CloneLayer(ProfileViewModel.SelectedLayer); + CloneLayer(SelectedLayer); } /// @@ -390,7 +416,7 @@ namespace Artemis.ViewModels.Profiles { // Update the UI Layers.Clear(); - ProfileViewModel.SelectedLayer = null; + SelectedLayer = null; if (SelectedProfile != null) Layers.AddRange(SelectedProfile.Layers); @@ -402,7 +428,7 @@ namespace Artemis.ViewModels.Profiles Task.Factory.StartNew(() => { Thread.Sleep(100); - ProfileViewModel.SelectedLayer = selectModel; + SelectedLayer = selectModel; }); } @@ -680,14 +706,6 @@ namespace Artemis.ViewModels.Profiles NotifyOfPropertyChange(() => ProfileSelected); } - private void LayerSelectedHandler(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName != "SelectedLayer") - return; - - NotifyOfPropertyChange(() => LayerSelected); - } - public void SaveSelectedProfile() { if (_saving || SelectedProfile == null || _deviceManager.ChangingKeyboard) diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs index bc74299a0..9487b026a 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs @@ -32,7 +32,6 @@ namespace Artemis.ViewModels.Profiles private DrawingImage _keyboardPreview; private Cursor _keyboardPreviewCursor; private bool _resizing; - private LayerModel _selectedLayer; private bool _showAll; public ProfileViewModel(DeviceManager deviceManager, LoopManager loopManager) @@ -47,18 +46,9 @@ namespace Artemis.ViewModels.Profiles } public ModuleModel ModuleModel { get; set; } - public ProfileModel SelectedProfile => ModuleModel?.ProfileModel; + public LayerModel SelectedLayer { get; set; } - public LayerModel SelectedLayer - { - get { return _selectedLayer; } - set - { - if (Equals(value, _selectedLayer)) return; - _selectedLayer = value; - NotifyOfPropertyChange(() => SelectedLayer); - } - } + public ProfileModel SelectedProfile => ModuleModel?.ProfileModel; public DrawingImage KeyboardPreview { @@ -380,10 +370,13 @@ namespace Artemis.ViewModels.Profiles public void Dispose() { + Disposed = true; _loopManager.RenderCompleted -= LoopManagerOnRenderCompleted; _deviceManager.OnKeyboardChanged -= DeviceManagerOnOnKeyboardChanged; } + public bool Disposed { get; set; } + #endregion } } \ No newline at end of file diff --git a/Artemis/Artemis/ViewModels/ShellViewModel.cs b/Artemis/Artemis/ViewModels/ShellViewModel.cs index c8b83a39f..4d59daf60 100644 --- a/Artemis/Artemis/ViewModels/ShellViewModel.cs +++ b/Artemis/Artemis/ViewModels/ShellViewModel.cs @@ -216,7 +216,7 @@ namespace Artemis.ViewModels dialog.SetIndeterminate(); while (MainManager.DeviceManager.ChangingKeyboard) - await Task.Delay(10); + await Task.Delay(1000); try { diff --git a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml b/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml index c4cfd119c..4a3c9e8b7 100644 --- a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml +++ b/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml @@ -185,7 +185,7 @@ cal:Message.Attach="[Event MouseDoubleClick] = [Action EditLayerFromDoubleClick]"> + SelectedItem="{Binding SelectedLayer, Mode=TwoWay}" /> Date: Sat, 14 Jan 2017 00:26:55 +0100 Subject: [PATCH 05/32] Added ProfileEditorModel Refactored some namespaces Got rid of ProfileViewModel --- Artemis/Artemis/Artemis.csproj | 15 +- .../Artemis/InjectionModules/BaseModules.cs | 10 +- Artemis/Artemis/Models/LayerEditorModel.cs | 12 + Artemis/Artemis/Models/ProfileEditorModel.cs | 354 +++++ .../Modules/Abstract/ModuleViewModel.cs | 1 + .../Abstract/LayerPropertiesViewModel.cs | 1 + .../Profiles/Layers/Interfaces/ILayerType.cs | 1 + .../AmbientLightPropertiesViewModel.cs | 1 + .../Types/AmbientLight/AmbientLightType.cs | 1 + .../Types/Audio/AudioPropertiesViewModel.cs | 1 + .../Profiles/Layers/Types/Audio/AudioType.cs | 1 + .../Types/Folder/FolderPropertiesViewModel.cs | 1 + .../Layers/Types/Folder/FolderType.cs | 1 + .../Generic/GenericPropertiesViewModel.cs | 1 + .../Layers/Types/Generic/GenericType.cs | 1 + .../Headset/HeadsetPropertiesViewModel.cs | 1 + .../Layers/Types/Headset/HeadsetType.cs | 1 + .../KeyPress/KeyPressPropertiesViewModel.cs | 1 + .../Layers/Types/KeyPress/KeyPressType.cs | 1 + .../Keyboard/KeyboardPropertiesViewModel.cs | 1 + .../Layers/Types/Keyboard/KeyboardType.cs | 1 + .../Types/KeyboardGif/KeyboardGifType.cs | 1 + .../Types/Mouse/MousePropertiesViewModel.cs | 1 + .../Profiles/Layers/Types/Mouse/MouseType.cs | 1 + .../Mousemat/MousematPropertiesViewModel.cs | 1 + .../Layers/Types/Mousemat/MousematType.cs | 1 + .../{Profiles => }/LayerEditorViewModel.cs | 13 +- .../{Profiles => }/ProfileEditorViewModel.cs | 1178 +++++++++-------- .../ViewModels/Profiles/ProfileViewModel.cs | 382 ------ .../Views/{Profiles => }/LayerEditorView.xaml | 2 +- .../{Profiles => }/LayerEditorView.xaml.cs | 2 +- .../{Profiles => }/ProfileEditorView.xaml | 583 ++++---- .../{Profiles => }/ProfileEditorView.xaml.cs | 2 +- 33 files changed, 1303 insertions(+), 1272 deletions(-) create mode 100644 Artemis/Artemis/Models/LayerEditorModel.cs create mode 100644 Artemis/Artemis/Models/ProfileEditorModel.cs rename Artemis/Artemis/ViewModels/{Profiles => }/LayerEditorViewModel.cs (95%) rename Artemis/Artemis/ViewModels/{Profiles => }/ProfileEditorViewModel.cs (52%) delete mode 100644 Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs rename Artemis/Artemis/Views/{Profiles => }/LayerEditorView.xaml (99%) rename Artemis/Artemis/Views/{Profiles => }/LayerEditorView.xaml.cs (89%) rename Artemis/Artemis/Views/{Profiles => }/ProfileEditorView.xaml (93%) rename Artemis/Artemis/Views/{Profiles => }/ProfileEditorView.xaml.cs (89%) diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 3500de5f3..6452856de 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -356,6 +356,8 @@ + + @@ -654,17 +656,16 @@ - - + - + @@ -709,7 +710,7 @@ LayerDynamicPropertiesView.xaml - + LayerEditorView.xaml @@ -721,7 +722,7 @@ LayerTweenView.xaml - + ProfileEditorView.xaml @@ -952,7 +953,7 @@ Designer MSBuild:Compile - + MSBuild:Compile Designer @@ -968,7 +969,7 @@ Designer MSBuild:Compile - + Designer MSBuild:Compile diff --git a/Artemis/Artemis/InjectionModules/BaseModules.cs b/Artemis/Artemis/InjectionModules/BaseModules.cs index bd0108284..eb4bc0bad 100644 --- a/Artemis/Artemis/InjectionModules/BaseModules.cs +++ b/Artemis/Artemis/InjectionModules/BaseModules.cs @@ -1,4 +1,5 @@ using Artemis.DeviceProviders; +using Artemis.Models; using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; @@ -8,7 +9,6 @@ using Artemis.Utilities.DataReaders; using Artemis.Utilities.GameState; using Artemis.ViewModels; using Artemis.ViewModels.Abstract; -using Artemis.ViewModels.Profiles; using Ninject.Extensions.Conventions; using Ninject.Modules; @@ -18,10 +18,16 @@ namespace Artemis.InjectionModules { public override void Load() { + #region Models + + Bind().ToSelf(); + Bind().ToSelf(); + + #endregion + #region ViewModels Bind().ToSelf().InSingletonScope(); - Bind().ToSelf(); Bind().ToSelf(); Bind().ToSelf().InSingletonScope(); Kernel.Bind(x => diff --git a/Artemis/Artemis/Models/LayerEditorModel.cs b/Artemis/Artemis/Models/LayerEditorModel.cs new file mode 100644 index 000000000..c8c374310 --- /dev/null +++ b/Artemis/Artemis/Models/LayerEditorModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Artemis.Models +{ + public class LayerEditorModel + { + } +} diff --git a/Artemis/Artemis/Models/ProfileEditorModel.cs b/Artemis/Artemis/Models/ProfileEditorModel.cs new file mode 100644 index 000000000..caefa2926 --- /dev/null +++ b/Artemis/Artemis/Models/ProfileEditorModel.cs @@ -0,0 +1,354 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Artemis.DAL; +using Artemis.Managers; +using Artemis.Modules.Abstract; +using Artemis.Profiles; +using Artemis.Profiles.Layers.Models; +using Artemis.Profiles.Layers.Types.Folder; +using Artemis.Properties; +using Artemis.Services; +using Artemis.Utilities; +using Artemis.ViewModels; +using Ninject.Parameters; + +namespace Artemis.Models +{ + public class ProfileEditorModel : IDisposable + { + private readonly DeviceManager _deviceManager; + private readonly LuaManager _luaManager; + private readonly DialogService _dialogService; + private readonly WindowService _windowService; + private FileSystemWatcher _watcher; + private ProfileModel _luaProfile; + + public ProfileEditorModel(WindowService windowService, MetroDialogService dialogService, + DeviceManager deviceManager, LuaManager luaManager) + { + _windowService = windowService; + _dialogService = dialogService; + _deviceManager = deviceManager; + _luaManager = luaManager; + } + + #region Layers + + /// + /// Opens a new LayerEditorView for the given layer + /// + /// The layer to open the view for + /// The datamodel to bind the editor to + public void EditLayer(LayerModel layer, ModuleDataModel dataModel) + { + IParameter[] args = + { + new ConstructorArgument("dataModel", dataModel), + new ConstructorArgument("layer", layer) + }; + _windowService.ShowDialog(args); + + // If the layer was a folder, but isn't anymore, assign it's children to it's parent. + if (layer.LayerType is FolderType || !layer.Children.Any()) + return; + + while (layer.Children.Any()) + { + var child = layer.Children[0]; + layer.Children.Remove(child); + if (layer.Parent != null) + { + layer.Parent.Children.Add(child); + layer.Parent.FixOrder(); + } + else + { + layer.Profile.Layers.Add(child); + layer.Profile.FixOrder(); + } + } + } + + /// + /// Removes the given layer from the profile + /// + /// The layer to remove + /// The profile to remove it from + public void RemoveLayer(LayerModel layer, ProfileModel profileModel) + { + if (layer == null) + return; + + if (layer.Parent != null) + { + var parent = layer.Parent; + layer.Parent.Children.Remove(layer); + parent.FixOrder(); + } + else if (layer.Profile != null) + { + var profile = layer.Profile; + layer.Profile.Layers.Remove(layer); + profile.FixOrder(); + } + + // Extra cleanup in case of a wonky layer that has no parent + if (profileModel.Layers.Contains(layer)) + profileModel.Layers.Remove(layer); + } + + #endregion + + #region Profiles + + public async Task AddProfile(ModuleModel moduleModel) + { + if (_deviceManager.ActiveKeyboard == null) + { + _dialogService.ShowMessageBox("Cannot add profile.", + "To add a profile, please select a keyboard in the options menu first."); + return null; + } + + var name = await GetValidProfileName("Name profile", "Please enter a unique name for your new profile"); + // User cancelled + if (name == null) + return null; + + var profile = new ProfileModel + { + Name = name, + KeyboardSlug = _deviceManager.ActiveKeyboard.Slug, + Width = _deviceManager.ActiveKeyboard.Width, + Height = _deviceManager.ActiveKeyboard.Height, + GameName = moduleModel.Name + }; + + if (!ProfileProvider.IsProfileUnique(profile)) + { + var overwrite = await _dialogService.ShowQuestionMessageBox("Overwrite existing profile", + "A profile with this name already exists for this game. Would you like to overwrite it?"); + if (!overwrite.Value) + return null; + } + + ProfileProvider.AddOrUpdate(profile); + return profile; + } + + public async Task RenameProfile(ProfileModel profileModel) + { + var name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name"); + // User cancelled + if (name == null) + return; + var doRename = await MakeProfileUnique(profileModel, name, profileModel.Name); + if (!doRename) + return; + + ProfileProvider.RenameProfile(profileModel, profileModel.Name); + } + + public async Task DuplicateProfile(ProfileModel selectedProfile) + { + var newProfile = GeneralHelpers.Clone(selectedProfile); + var name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name"); + // User cancelled + if (name == null) + return; + var doRename = await MakeProfileUnique(newProfile, name, newProfile.Name); + if (!doRename) + return; + + // Make sure it's not default, in case of copying a default profile + newProfile.IsDefault = false; + ProfileProvider.AddOrUpdate(newProfile); + } + + public async Task DeleteProfile(ProfileModel selectedProfile, ModuleModel moduleModel) + { + var confirm = await _dialogService.ShowQuestionMessageBox("Delete profile", + $"Are you sure you want to delete the profile named: {selectedProfile.Name}?\n\n" + + "This cannot be undone."); + if (!confirm.Value) + return; + + var defaultProfile = ProfileProvider.GetProfile(_deviceManager.ActiveKeyboard, moduleModel, "Default"); + var deleteProfile = selectedProfile; + + moduleModel.ChangeProfile(defaultProfile); + ProfileProvider.DeleteProfile(deleteProfile); + } + + public async Task ImportProfile(ModuleModel moduleModel) + { + var dialog = new OpenFileDialog {Filter = "Artemis profile (*.json)|*.json"}; + var result = dialog.ShowDialog(); + if (result != DialogResult.OK) + return; + + var profileModel = ProfileProvider.LoadProfileIfValid(dialog.FileName); + if (profileModel == null) + { + _dialogService.ShowErrorMessageBox("Oh noes, the profile you provided is invalid. " + + "If this keeps happening, please make an issue on GitHub and provide the profile."); + return; + } + + // Verify the game + if (profileModel.GameName != moduleModel.Name) + { + _dialogService.ShowErrorMessageBox( + $"Oh oops! This profile is ment for {profileModel.GameName}, not {moduleModel.Name} :c"); + return; + } + + // Verify the keyboard + var deviceManager = _deviceManager; + if (profileModel.KeyboardSlug != deviceManager.ActiveKeyboard.Slug) + { + var adjustKeyboard = await _dialogService.ShowQuestionMessageBox("Profile not made for this keyboard", + $"Watch out, this profile wasn't ment for this keyboard, but for the {profileModel.KeyboardSlug}. " + + "You can still import it but you'll probably have to do some adjusting\n\n" + + "Continue?"); + if (!adjustKeyboard.Value) + return; + + // Resize layers that are on the full keyboard width + profileModel.ResizeLayers(deviceManager.ActiveKeyboard); + // Put layers back into the canvas if they fell outside it + profileModel.FixBoundaries(deviceManager.ActiveKeyboard.KeyboardRectangle(1)); + + // Setup profile metadata to match the new keyboard + profileModel.KeyboardSlug = deviceManager.ActiveKeyboard.Slug; + profileModel.Width = deviceManager.ActiveKeyboard.Width; + profileModel.Height = deviceManager.ActiveKeyboard.Height; + } + + var name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name"); + // User cancelled + if (name == null) + return; + var doRename = await MakeProfileUnique(profileModel, name, profileModel.Name); + if (!doRename) + return; + + profileModel.IsDefault = false; + ProfileProvider.AddOrUpdate(profileModel); + } + + private async Task GetValidProfileName(string title, string text) + { + var name = await _dialogService.ShowInputDialog(title, text); + + // Null when the user cancelled + if (name == null) + return null; + + if (name.Length >= 2) + return name; + + _dialogService.ShowMessageBox("Invalid profile name", + "Please provide a valid profile name that's longer than 2 symbols"); + + return await GetValidProfileName(title, text); + } + + private async Task MakeProfileUnique(ProfileModel profileModel, string name, string oldName) + { + profileModel.Name = name; + if (ProfileProvider.IsProfileUnique(profileModel)) + return true; + + name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name"); + if (name != null) + return await MakeProfileUnique(profileModel, name, oldName); + + // If cancelled, restore old name and stop + profileModel.Name = oldName; + return false; + } + + #endregion + + #region LUA + + public void OpenLuaEditor(ProfileModel profileModel) + { + // Clean up old environment + DisposeLuaWatcher(); + + // Create a temp file + var fileName = Guid.NewGuid() + ".lua"; + var file = File.Create(Path.GetTempPath() + fileName); + file.Dispose(); + + // Add instructions to LUA script if it's a new file + if (string.IsNullOrEmpty(profileModel.LuaScript)) + profileModel.LuaScript = Encoding.UTF8.GetString(Resources.lua_placeholder); + File.WriteAllText(Path.GetTempPath() + fileName, profileModel.LuaScript); + + // Watch the file for changes + _luaProfile = profileModel; + _watcher = new FileSystemWatcher(Path.GetTempPath(), fileName); + _watcher.Changed += LuaFileChanged; + _watcher.EnableRaisingEvents = true; + _watcher.Path = Path.GetTempPath(); + _watcher.Filter = fileName; + + // Open the temp file with the default editor + System.Diagnostics.Process.Start(Path.GetTempPath() + fileName); + } + + private void LuaFileChanged(object sender, FileSystemEventArgs args) + { + if (_luaProfile == null) + { + DisposeLuaWatcher(); + return; + } + + if (args.ChangeType != WatcherChangeTypes.Changed) + return; + + lock (_luaProfile) + { + using (var fs = new FileStream(args.FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + using (var sr = new StreamReader(fs)) + { + _luaProfile.LuaScript = sr.ReadToEnd(); + } + } + + ProfileProvider.AddOrUpdate(_luaProfile); + _luaManager.SetupLua(_luaProfile); + } + } + + private void DisposeLuaWatcher() + { + if (_watcher == null) return; + _watcher.Changed -= LuaFileChanged; + _watcher.Dispose(); + _watcher = null; + } + + public void Dispose() + { + DisposeLuaWatcher(); + } + + #endregion + + #region Rendering + + + + #endregion + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs index 55fdfef5d..fa0eeaca5 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs @@ -2,6 +2,7 @@ using Artemis.Managers; using Artemis.Services; using Artemis.Settings; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; using Ninject; diff --git a/Artemis/Artemis/Profiles/Layers/Abstract/LayerPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Abstract/LayerPropertiesViewModel.cs index 33ea330e7..3669713a0 100644 --- a/Artemis/Artemis/Profiles/Layers/Abstract/LayerPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Abstract/LayerPropertiesViewModel.cs @@ -1,5 +1,6 @@ using System.Windows.Media; using Artemis.Profiles.Layers.Models; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Interfaces/ILayerType.cs b/Artemis/Artemis/Profiles/Layers/Interfaces/ILayerType.cs index 83f8c9185..0faba2b3c 100644 --- a/Artemis/Artemis/Profiles/Layers/Interfaces/ILayerType.cs +++ b/Artemis/Artemis/Profiles/Layers/Interfaces/ILayerType.cs @@ -2,6 +2,7 @@ using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Models; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Newtonsoft.Json; diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs index 7e71757d0..d4fcf84c1 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs @@ -1,4 +1,5 @@ using Artemis.Profiles.Layers.Abstract; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.AmbientLight diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs index d1170138b..01c4922ee 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs @@ -13,6 +13,7 @@ using Artemis.Profiles.Layers.Types.AmbientLight.Model.Extensions; using Artemis.Profiles.Layers.Types.AmbientLight.ScreenCapturing; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Newtonsoft.Json; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs index ca276c666..079b386b4 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs @@ -1,6 +1,7 @@ using System.Linq; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index e983d0969..a115f9bcb 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -9,6 +9,7 @@ using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Audio diff --git a/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderPropertiesViewModel.cs index 318662690..ca33f411a 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderPropertiesViewModel.cs @@ -1,4 +1,5 @@ using Artemis.Profiles.Layers.Abstract; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Folder diff --git a/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderType.cs b/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderType.cs index 52f5668c2..cfec51a00 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderType.cs @@ -6,6 +6,7 @@ using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Folder diff --git a/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericPropertiesViewModel.cs index 80c7aa4ec..3de180e31 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericPropertiesViewModel.cs @@ -1,6 +1,7 @@ using System.Linq; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericType.cs b/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericType.cs index 04a01016e..f7ca251d5 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericType.cs @@ -8,6 +8,7 @@ using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Generic diff --git a/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetPropertiesViewModel.cs index e6db95ab0..5e81c97c5 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetPropertiesViewModel.cs @@ -1,6 +1,7 @@ using System.Linq; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetType.cs b/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetType.cs index 1df4777bc..3c99e55f4 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetType.cs @@ -8,6 +8,7 @@ using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Headset diff --git a/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressPropertiesViewModel.cs index 9efbd9907..352f7c45f 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressPropertiesViewModel.cs @@ -1,4 +1,5 @@ using Artemis.Profiles.Layers.Abstract; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.KeyPress diff --git a/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressType.cs b/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressType.cs index 74f4d7cb0..a2a366ec4 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressType.cs @@ -13,6 +13,7 @@ using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; using Artemis.Utilities.Keyboard; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.KeyPress diff --git a/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardPropertiesViewModel.cs index c84b3c3aa..eeb7943b7 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardPropertiesViewModel.cs @@ -3,6 +3,7 @@ using System.Windows.Forms; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardType.cs b/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardType.cs index 6e6627d43..2ace6c684 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardType.cs @@ -5,6 +5,7 @@ using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Animations; using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Keyboard diff --git a/Artemis/Artemis/Profiles/Layers/Types/KeyboardGif/KeyboardGifType.cs b/Artemis/Artemis/Profiles/Layers/Types/KeyboardGif/KeyboardGifType.cs index bd0527a47..ef3e201e2 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/KeyboardGif/KeyboardGifType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/KeyboardGif/KeyboardGifType.cs @@ -9,6 +9,7 @@ using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Keyboard; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.KeyboardGif diff --git a/Artemis/Artemis/Profiles/Layers/Types/Mouse/MousePropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Mouse/MousePropertiesViewModel.cs index 00d683cc8..d88b692f9 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Mouse/MousePropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Mouse/MousePropertiesViewModel.cs @@ -1,6 +1,7 @@ using System.Linq; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Mouse/MouseType.cs b/Artemis/Artemis/Profiles/Layers/Types/Mouse/MouseType.cs index df6d46592..31a2d4fa1 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Mouse/MouseType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Mouse/MouseType.cs @@ -8,6 +8,7 @@ using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Mouse diff --git a/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematPropertiesViewModel.cs index 65c42d6a2..b960966e9 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematPropertiesViewModel.cs @@ -1,6 +1,7 @@ using System.Linq; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematType.cs b/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematType.cs index 58583e475..f1379e754 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematType.cs @@ -8,6 +8,7 @@ using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Mousemat diff --git a/Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs b/Artemis/Artemis/ViewModels/LayerEditorViewModel.cs similarity index 95% rename from Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs rename to Artemis/Artemis/ViewModels/LayerEditorViewModel.cs index 035fd42ad..1c387abe2 100644 --- a/Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/LayerEditorViewModel.cs @@ -10,13 +10,14 @@ using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Keyboard; using Artemis.Profiles.Layers.Types.KeyboardGif; using Artemis.Services; +using Artemis.Utilities; +using Artemis.ViewModels.Profiles; using Artemis.ViewModels.Profiles.Events; using Caliburn.Micro; using Newtonsoft.Json; using Ninject; -using static Artemis.Utilities.GeneralHelpers; -namespace Artemis.ViewModels.Profiles +namespace Artemis.ViewModels { public sealed class LayerEditorViewModel : Screen { @@ -30,13 +31,13 @@ namespace Artemis.ViewModels.Profiles IEnumerable layerAnimations) { Layer = layer; - ProposedLayer = Clone(layer); + ProposedLayer = GeneralHelpers.Clone(layer); ProposedLayer.Children.Clear(); DataModel = DataModel; LayerTypes = new BindableCollection(types.OrderBy(t => t.Name)); LayerAnimations = layerAnimations.OrderBy(l => l.Name).ToList(); - DataModelProps = new BindableCollection(GenerateTypeMap(dataModel)); + DataModelProps = new BindableCollection(GeneralHelpers.GenerateTypeMap(dataModel)); if (Layer.Properties == null) Layer.SetupProperties(); @@ -57,7 +58,7 @@ namespace Artemis.ViewModels.Profiles public MetroDialogService DialogService { get; set; } public BindableCollection LayerTypes { get; set; } - public BindableCollection DataModelProps { get; set; } + public BindableCollection DataModelProps { get; set; } public BindableCollection LayerConditionVms { get; set; } public bool KeyboardGridIsVisible => ProposedLayer.LayerType is KeyboardType; public bool GifGridIsVisible => ProposedLayer.LayerType is KeyboardGifType; @@ -215,7 +216,7 @@ namespace Artemis.ViewModels.Profiles // Ignore the children, can't just temporarily add them to the proposed layer because // that would upset the child layers' relations (sounds like Dr. Phil amirite?) - var currentObj = Clone(Layer); + var currentObj = GeneralHelpers.Clone(Layer); currentObj.Children.Clear(); // Apply the IsEvent boolean diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs similarity index 52% rename from Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs rename to Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs index 734598963..2b1da49ae 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs @@ -1,11 +1,12 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; -using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Media; @@ -13,8 +14,10 @@ using Artemis.DAL; using Artemis.DeviceProviders; using Artemis.Events; using Artemis.Managers; +using Artemis.Models; using Artemis.Modules.Abstract; using Artemis.Profiles; +using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Folder; using Artemis.Properties; @@ -22,86 +25,84 @@ using Artemis.Services; using Artemis.Styles.DropTargetAdorners; using Artemis.Utilities; using Caliburn.Micro; +using Castle.Components.DictionaryAdapter; using GongSolutions.Wpf.DragDrop; +using MahApps.Metro; using MahApps.Metro.Controls.Dialogs; -using Ninject.Parameters; using NuGet; +using Application = System.Windows.Application; +using Cursor = System.Windows.Input.Cursor; +using Cursors = System.Windows.Input.Cursors; using DragDropEffects = System.Windows.DragDropEffects; using IDropTarget = GongSolutions.Wpf.DragDrop.IDropTarget; using MouseEventArgs = System.Windows.Input.MouseEventArgs; using Screen = Caliburn.Micro.Screen; -namespace Artemis.ViewModels.Profiles +namespace Artemis.ViewModels { public sealed class ProfileEditorViewModel : Screen, IDropTarget, IDisposable { private readonly DeviceManager _deviceManager; private readonly MetroDialogService _dialogService; - private readonly LuaManager _luaManager; + private readonly LoopManager _loopManager; private readonly ModuleModel _moduleModel; - private readonly WindowService _windowService; private ImageSource _keyboardPreview; private ObservableCollection _layers; private ObservableCollection _profileNames; private bool _saving; - private FileSystemWatcher _watcher; - private ProfileViewModel _profileViewModel; private LayerModel _selectedLayer; + private bool _showAll; - public ProfileEditorViewModel(DeviceManager deviceManager, LuaManager luaManager, ModuleModel moduleModel, - ProfileViewModel profileViewModel, MetroDialogService dialogService, WindowService windowService) + public ProfileEditorViewModel(ProfileEditorModel profileEditorModel, DeviceManager deviceManager, + LoopManager loopManager, ModuleModel moduleModel, MetroDialogService dialogService) { _deviceManager = deviceManager; - _luaManager = luaManager; + _loopManager = loopManager; _moduleModel = moduleModel; _dialogService = dialogService; - _windowService = windowService; ProfileNames = new ObservableCollection(); Layers = new ObservableCollection(); - ProfileViewModel = profileViewModel; - - ProfileViewModel.ModuleModel = _moduleModel; + ProfileEditorModel = profileEditorModel; PropertyChanged += EditorStateHandler; _deviceManager.OnKeyboardChanged += DeviceManagerOnOnKeyboardChanged; _moduleModel.ProfileChanged += ModuleModelOnProfileChanged; + _loopManager.RenderCompleted += LoopManagerOnRenderCompleted; LoadProfiles(); } - public ProfileViewModel ProfileViewModel + public void Dispose() { - get { return _profileViewModel; } - set + SaveSelectedProfile(); + ProfileEditorModel.Dispose(); + _loopManager.RenderCompleted -= LoopManagerOnRenderCompleted; + _deviceManager.OnKeyboardChanged -= DeviceManagerOnOnKeyboardChanged; + } + + #region LUA + + public void EditLua() + { + if (SelectedProfile == null) + return; + try { - if (Equals(value, _profileViewModel)) return; - _profileViewModel = value; - NotifyOfPropertyChange(); - NotifyOfPropertyChange(nameof(LayerSelected)); + ProfileEditorModel.OpenLuaEditor(SelectedProfile); + } + catch (Exception e) + { + _dialogService.ShowMessageBox("Couldn't open LUA file", + "Please make sure you have a text editor associated with the .lua extension.\n\n" + + "Windows error message: \n" + e.Message); } } - public bool EditorEnabled - => - SelectedProfile != null && !SelectedProfile.IsDefault && - _deviceManager.ActiveKeyboard != null; + #endregion - public ProfileModel SelectedProfile => _moduleModel?.ProfileModel; - public LayerModel SelectedLayer - { - get { return _selectedLayer; } - set - { - if (_profileViewModel != null) - _profileViewModel.SelectedLayer = value; - if (Equals(value, _selectedLayer)) - return; - - _selectedLayer = value; - NotifyOfPropertyChange(() => SelectedLayer); - NotifyOfPropertyChange(() => LayerSelected); - } - } + #region Properties + + public ProfileEditorModel ProfileEditorModel { get; } public ObservableCollection ProfileNames { @@ -125,6 +126,29 @@ namespace Artemis.ViewModels.Profiles } } + + public ImageSource KeyboardPreview + { + get { return _keyboardPreview; } + set + { + if (Equals(value, _keyboardPreview)) return; + _keyboardPreview = value; + NotifyOfPropertyChange(() => KeyboardPreview); + } + } + + public bool ShowAll + { + get { return _showAll; } + set + { + if (value == _showAll) return; + _showAll = value; + NotifyOfPropertyChange(); + } + } + public string SelectedProfileName { get { return SelectedProfile?.Name; } @@ -138,22 +162,542 @@ namespace Artemis.ViewModels.Profiles } } - public ImageSource KeyboardPreview + public LayerModel SelectedLayer { - get { return _keyboardPreview; } + get { return _selectedLayer; } set { - if (Equals(value, _keyboardPreview)) return; - _keyboardPreview = value; - NotifyOfPropertyChange(() => KeyboardPreview); + if (Equals(value, _selectedLayer)) + return; + + _selectedLayer = value; + NotifyOfPropertyChange(() => SelectedLayer); + NotifyOfPropertyChange(() => LayerSelected); } } - public PreviewSettings? PreviewSettings => _deviceManager.ActiveKeyboard?.PreviewSettings; + public ImageSource KeyboardImage => ImageUtilities.BitmapToBitmapImage( + _deviceManager.ActiveKeyboard?.PreviewSettings.Image ?? Resources.none); + public ProfileModel SelectedProfile => _moduleModel?.ProfileModel; + public PreviewSettings? PreviewSettings => _deviceManager.ActiveKeyboard?.PreviewSettings; public bool ProfileSelected => SelectedProfile != null; public bool LayerSelected => SelectedProfile != null && SelectedLayer != null; + public bool EditorEnabled => SelectedProfile != null && !SelectedProfile.IsDefault && + _deviceManager.ActiveKeyboard != null; + + #endregion + + #region Layers + + public void EditLayerFromDoubleClick() + { + if (SelectedLayer?.LayerType is FolderType) + return; + + EditLayer(); + } + + public void EditLayer() + { + if (SelectedLayer == null) + return; + + ProfileEditorModel.EditLayer(SelectedLayer, _moduleModel.DataModel); + UpdateLayerList(SelectedLayer); + } + + public LayerModel AddLayer() + { + if (SelectedProfile == null) + return null; + + var layer = SelectedProfile.AddLayer(SelectedLayer); + UpdateLayerList(layer); + + return layer; + } + + public LayerModel AddFolder() + { + if (SelectedProfile == null) + return null; + + var layer = AddLayer(); + if (layer == null) + return null; + + layer.Name = "New folder"; + layer.LayerType = new FolderType(); + layer.LayerType.SetupProperties(layer); + + return layer; + } + + public void RemoveLayer() + { + RemoveLayer(SelectedLayer); + } + + public void RemoveLayer(LayerModel layer) + { + ProfileEditorModel.RemoveLayer(layer, SelectedProfile); + UpdateLayerList(null); + } + + public async void RenameLayer(LayerModel layer) + { + if (layer == null) + return; + + var newName = await _dialogService.ShowInputDialog("Rename layer", "Please enter a name for the layer", + new MetroDialogSettings {DefaultText = layer.Name}); + + // Null when the user cancelled + if (string.IsNullOrEmpty(newName)) + return; + + layer.Name = newName; + UpdateLayerList(layer); + } + + /// + /// Clones the currently selected layer and adds it to the profile, after the original + /// + public void CloneLayer() + { + if (SelectedLayer == null) + return; + + CloneLayer(SelectedLayer); + } + + /// + /// Clones the given layer and adds it to the profile, after the original + /// + /// + public void CloneLayer(LayerModel layer) + { + var clone = GeneralHelpers.Clone(layer); + layer.InsertAfter(clone); + + UpdateLayerList(clone); + } + + private void UpdateLayerList(LayerModel selectModel) + { + // Update the UI + Layers.Clear(); + SelectedLayer = null; + + if (SelectedProfile != null) + Layers.AddRange(SelectedProfile.Layers); + + if (selectModel == null) + return; + + // A small delay to allow the profile list to rebuild + Task.Factory.StartNew(() => + { + Thread.Sleep(100); + SelectedLayer = selectModel; + }); + } + + #endregion + + #region Profiles + + /// + /// Loads all profiles for the current game and keyboard + /// + private void LoadProfiles() + { + Execute.OnUIThread(() => + { + ProfileNames.Clear(); + if (_moduleModel != null && _deviceManager.ActiveKeyboard != null) + ProfileNames.AddRange(ProfileProvider.GetProfileNames(_deviceManager.ActiveKeyboard, _moduleModel)); + + NotifyOfPropertyChange(() => SelectedProfile); + }); + } + + public void SaveSelectedProfile() + { + if (_saving || SelectedProfile == null || _deviceManager.ChangingKeyboard) + return; + + _saving = true; + try + { + ProfileProvider.AddOrUpdate(SelectedProfile); + } + catch (Exception) + { + // ignored + } + _saving = false; + } + + public async void AddProfile() + { + if (_deviceManager.ActiveKeyboard == null) + { + _dialogService.ShowMessageBox("Cannot add profile.", + "To add a profile, please select a keyboard in the options menu first."); + return; + } + + var profile = await ProfileEditorModel.AddProfile(_moduleModel); + if (profile == null) + return; + + LoadProfiles(); + } + + public async void RenameProfile() + { + if (SelectedProfile == null) + return; + + await ProfileEditorModel.RenameProfile(SelectedProfile); + LoadProfiles(); + } + + public async void DuplicateProfile() + { + if (SelectedProfile == null) + return; + + await ProfileEditorModel.DuplicateProfile(SelectedProfile); + LoadProfiles(); + } + + public async void DeleteProfile() + { + if (SelectedProfile == null) + return; + + await ProfileEditorModel.DeleteProfile(SelectedProfile, _moduleModel); + LoadProfiles(); + } + + public async void ImportProfile() + { + if (_deviceManager.ActiveKeyboard == null) + { + _dialogService.ShowMessageBox("Cannot import profile.", + "To import a profile, please select a keyboard in the options menu first."); + return; + } + + await ProfileEditorModel.ImportProfile(_moduleModel); + LoadProfiles(); + } + + public void ExportProfile() + { + if (SelectedProfile == null) + return; + + var dialog = new SaveFileDialog {Filter = "Artemis profile (*.json)|*.json"}; + var result = dialog.ShowDialog(); + if (result != DialogResult.OK) + return; + + ProfileProvider.ExportProfile(SelectedProfile, dialog.FileName); + } + + #endregion + + #region Rendering + + private void LoopManagerOnRenderCompleted(object sender, EventArgs eventArgs) + { + // Besides the usual checks, also check if the ActiveKeyboard isn't the NoneKeyboard + if (SelectedProfile == null || _deviceManager.ActiveKeyboard == null || + _deviceManager.ActiveKeyboard.Slug == "none") + { + KeyboardPreview = null; + + // Setup layers for the next frame + if (_moduleModel.IsInitialized && ActiveWindowHelper.MainWindowActive) + _moduleModel.PreviewLayers = new List(); + + return; + } + + var renderLayers = GetRenderLayers(); + // Draw the current frame to the preview + var keyboardRect = _deviceManager.ActiveKeyboard.KeyboardRectangle(); + var visual = new DrawingVisual(); + using (var drawingContext = visual.RenderOpen()) + { + // Setup the DrawingVisual's size + drawingContext.PushClip(new RectangleGeometry(keyboardRect)); + drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)), null, keyboardRect); + + // Draw the layers + foreach (var layer in renderLayers) + { + layer.Update(null, true, false); + if (layer.LayerType.ShowInEdtor) + layer.Draw(null, drawingContext, true, false); + } + + // Get the selection color + var accentColor = ThemeManager.DetectAppStyle(Application.Current)?.Item2?.Resources["AccentColor"]; + if (accentColor == null) + { + var preview = new DrawingImage(); + preview.Freeze(); + KeyboardPreview = preview; + return; + } + + var pen = new Pen(new SolidColorBrush((Color) accentColor), 0.4); + + // Draw the selection outline and resize indicator + if (SelectedLayer != null && SelectedLayer.MustDraw()) + { + var layerRect = SelectedLayer.Properties.PropertiesRect(); + // Deflate the rect so that the border is drawn on the inside + layerRect.Inflate(-0.2, -0.2); + + // Draw an outline around the selected layer + drawingContext.DrawRectangle(null, pen, layerRect); + // Draw a resize indicator in the bottom-right + drawingContext.DrawLine(pen, + new Point(layerRect.BottomRight.X - 1, layerRect.BottomRight.Y - 0.5), + new Point(layerRect.BottomRight.X - 1.2, layerRect.BottomRight.Y - 0.7)); + drawingContext.DrawLine(pen, + new Point(layerRect.BottomRight.X - 0.5, layerRect.BottomRight.Y - 1), + new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 1.2)); + drawingContext.DrawLine(pen, + new Point(layerRect.BottomRight.X - 0.5, layerRect.BottomRight.Y - 0.5), + new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 0.7)); + } + + SelectedProfile?.RaiseDeviceDrawnEvent(new ProfileDeviceEventsArg(DrawType.Preview, null, true, + drawingContext)); + + // Remove the clip + drawingContext.Pop(); + } + var drawnPreview = new DrawingImage(visual.Drawing); + drawnPreview.Freeze(); + KeyboardPreview = drawnPreview; + + // Setup layers for the next frame + if (_moduleModel.IsInitialized && ActiveWindowHelper.MainWindowActive) + _moduleModel.PreviewLayers = renderLayers; + } + + public List GetRenderLayers() + { + // Get the layers that must be drawn + List drawLayers; + if (ShowAll) + return SelectedProfile.GetRenderLayers(null, false, true); + + if (SelectedLayer == null || !SelectedLayer.Enabled) + return new EditableList(); + + if (SelectedLayer.LayerType is FolderType) + drawLayers = SelectedLayer.GetRenderLayers(null, false, true); + else + drawLayers = new List {SelectedLayer}; + + return drawLayers; + } + + #endregion + + #region Mouse actions + + private DateTime _downTime; + private LayerModel _draggingLayer; + private Point? _draggingLayerOffset; + private Cursor _keyboardPreviewCursor; + private bool _resizing; + + /// + /// Handler for clicking + /// + /// + public void MouseDownKeyboardPreview(MouseButtonEventArgs e) + { + if (e.LeftButton == MouseButtonState.Pressed) + _downTime = DateTime.Now; + } + + /// + /// Second handler for clicking, selects a the layer the user clicked on. + /// + /// + public void MouseUpKeyboardPreview(MouseButtonEventArgs e) + { + if (SelectedProfile == null || SelectedProfile.IsDefault) + return; + + var timeSinceDown = DateTime.Now - _downTime; + if (!(timeSinceDown.TotalMilliseconds < 500)) + return; + if (_draggingLayer != null) + return; + + var keyboard = _deviceManager.ActiveKeyboard; + var pos = e.GetPosition((Image) e.OriginalSource); + var x = pos.X / ((double) keyboard.PreviewSettings.Width / keyboard.Width); + var y = pos.Y / ((double) keyboard.PreviewSettings.Height / keyboard.Height); + + var hoverLayer = GetLayers().Where(l => l.MustDraw()) + .FirstOrDefault(l => l.Properties.PropertiesRect(1).Contains(x, y)); + + if (hoverLayer != null) + SelectedLayer = hoverLayer; + } + + /// + /// Handler for resizing and moving the currently selected layer + /// + /// + public void MouseMoveKeyboardPreview(MouseEventArgs e) + { + if (SelectedProfile == null) + return; + + var pos = e.GetPosition((Image) e.OriginalSource); + var keyboard = _deviceManager.ActiveKeyboard; + var x = pos.X / ((double) keyboard.PreviewSettings.Width / keyboard.Width); + var y = pos.Y / ((double) keyboard.PreviewSettings.Height / keyboard.Height); + var hoverLayer = GetLayers().Where(l => l.MustDraw()) + .FirstOrDefault(l => l.Properties.PropertiesRect(1).Contains(x, y)); + + HandleDragging(e, x, y, hoverLayer); + + if (hoverLayer == null) + { + KeyboardPreviewCursor = Cursors.Arrow; + return; + } + + // Turn the mouse pointer into a hand if hovering over an active layer + if (hoverLayer == SelectedLayer) + { + var rect = hoverLayer.Properties.PropertiesRect(1); + KeyboardPreviewCursor = + Math.Sqrt(Math.Pow(x - rect.BottomRight.X, 2) + Math.Pow(y - rect.BottomRight.Y, 2)) < 0.6 + ? Cursors.SizeNWSE + : Cursors.SizeAll; + } + else + { + KeyboardPreviewCursor = Cursors.Hand; + } + } + + public Cursor KeyboardPreviewCursor + { + get { return _keyboardPreviewCursor; } + set + { + if (Equals(value, _keyboardPreviewCursor)) return; + _keyboardPreviewCursor = value; + NotifyOfPropertyChange(() => KeyboardPreviewCursor); + } + } + + /// + /// Handles dragging the given layer + /// + /// + /// + /// + /// + private void HandleDragging(MouseEventArgs e, double x, double y, LayerModel hoverLayer) + { + // Reset the dragging state on mouse release + if (e.LeftButton == MouseButtonState.Released || + _draggingLayer != null && SelectedLayer != _draggingLayer) + { + _draggingLayerOffset = null; + _draggingLayer = null; + return; + } + + if (SelectedLayer == null || SelectedLayer.LayerType != null && !SelectedLayer.LayerType.ShowInEdtor) + return; + + // Setup the dragging state on mouse press + if (_draggingLayerOffset == null && hoverLayer != null && e.LeftButton == MouseButtonState.Pressed) + { + var layerRect = hoverLayer.Properties.PropertiesRect(1); + + _draggingLayerOffset = new Point(x - SelectedLayer.Properties.X, y - SelectedLayer.Properties.Y); + _draggingLayer = hoverLayer; + // Detect dragging if cursor is in the bottom right + _resizing = Math.Sqrt(Math.Pow(x - layerRect.BottomRight.X, 2) + + Math.Pow(y - layerRect.BottomRight.Y, 2)) < 0.6; + } + + if (_draggingLayerOffset == null || _draggingLayer == null || _draggingLayer != SelectedLayer) + return; + + var draggingProps = _draggingLayer.Properties; + // If no setup or reset was done, handle the actual dragging action + if (_resizing) + { + var newWidth = Math.Round(x - draggingProps.X); + var newHeight = Math.Round(y - draggingProps.Y); + + // Ensure the layer doesn't leave the canvas + if (newWidth < 1 || draggingProps.X + newWidth <= 0) + newWidth = draggingProps.Width; + if (newHeight < 1 || draggingProps.Y + newHeight <= 0) + newHeight = draggingProps.Height; + + draggingProps.Width = newWidth; + draggingProps.Height = newHeight; + } + else + { + var newX = Math.Round(x - _draggingLayerOffset.Value.X); + var newY = Math.Round(y - _draggingLayerOffset.Value.Y); + + // Ensure the layer doesn't leave the canvas + if (newX >= SelectedProfile.Width || newX + draggingProps.Width <= 0) + newX = draggingProps.X; + if (newY >= SelectedProfile.Height || newY + draggingProps.Height <= 0) + newY = draggingProps.Y; + + draggingProps.X = newX; + draggingProps.Y = newY; + } + } + + private List GetLayers() + { + if (ShowAll) + return SelectedProfile.GetLayers(); + if (SelectedLayer == null) + return new List(); + + lock (SelectedLayer) + { + // Get the layers that must be drawn + if (SelectedLayer.LayerType is FolderType) + return SelectedLayer.GetLayers().ToList(); + return new List {SelectedLayer}; + } + } + + #endregion + + #region Event handles + public void DragOver(IDropInfo dropInfo) { var source = dropInfo.Data as LayerModel; @@ -226,7 +770,6 @@ namespace Artemis.ViewModels.Profiles { NotifyOfPropertyChange(() => SelectedProfileName); NotifyOfPropertyChange(() => SelectedProfile); - NotifyOfPropertyChange(() => ProfileViewModel.SelectedProfile); } /// @@ -235,461 +778,10 @@ namespace Artemis.ViewModels.Profiles private void DeviceManagerOnOnKeyboardChanged(object sender, KeyboardChangedEventArgs e) { NotifyOfPropertyChange(() => PreviewSettings); + NotifyOfPropertyChange(() => KeyboardImage); LoadProfiles(); } - /// - /// Loads all profiles for the current game and keyboard - /// - private void LoadProfiles() - { - Execute.OnUIThread(() => - { - ProfileNames.Clear(); - if (_moduleModel != null && _deviceManager.ActiveKeyboard != null) - ProfileNames.AddRange(ProfileProvider.GetProfileNames(_deviceManager.ActiveKeyboard, _moduleModel)); - - NotifyOfPropertyChange(() => SelectedProfile); - }); - } - - - public void EditLayerFromDoubleClick() - { - if (SelectedLayer?.LayerType is FolderType) - return; - - EditLayer(); - } - - public void EditLayer() - { - if (SelectedLayer == null) - return; - - var selectedLayer = SelectedLayer; - EditLayer(selectedLayer); - } - - /// - /// Opens a new LayerEditorView for the given layer - /// - /// The layer to open the view for - public void EditLayer(LayerModel layer) - { - if (layer == null) - return; - - IParameter[] args = - { - new ConstructorArgument("dataModel", _moduleModel.DataModel), - new ConstructorArgument("layer", layer) - }; - _windowService.ShowDialog(args); - - // If the layer was a folder, but isn't anymore, assign it's children to it's parent. - if (!(layer.LayerType is FolderType) && layer.Children.Any()) - while (layer.Children.Any()) - { - var child = layer.Children[0]; - layer.Children.Remove(child); - if (layer.Parent != null) - { - layer.Parent.Children.Add(child); - layer.Parent.FixOrder(); - } - else - { - layer.Profile.Layers.Add(child); - layer.Profile.FixOrder(); - } - } - - UpdateLayerList(layer); - } - - /// - /// Adds a new layer to the profile and selects it - /// - public LayerModel AddLayer() - { - if (SelectedProfile == null) - return null; - - var layer = SelectedProfile.AddLayer(SelectedLayer); - UpdateLayerList(layer); - - return layer; - } - - public LayerModel AddFolder() - { - var layer = AddLayer(); - if (layer == null) - return null; - - layer.Name = "New folder"; - layer.LayerType = new FolderType(); - layer.LayerType.SetupProperties(layer); - - return layer; - } - - /// - /// Removes the currently selected layer from the profile - /// - public void RemoveLayer() - { - RemoveLayer(SelectedLayer); - } - - /// - /// Removes the given layer from the profile - /// - /// - public void RemoveLayer(LayerModel layer) - { - if (layer == null) - return; - - if (layer.Parent != null) - { - var parent = layer.Parent; - layer.Parent.Children.Remove(layer); - parent.FixOrder(); - } - else if (layer.Profile != null) - { - var profile = layer.Profile; - layer.Profile.Layers.Remove(layer); - profile.FixOrder(); - } - - // Extra cleanup in case of a wonky layer that has no parent - if (SelectedProfile.Layers.Contains(layer)) - SelectedProfile.Layers.Remove(layer); - - UpdateLayerList(null); - } - - public async void RenameLayer(LayerModel layer) - { - if (layer == null) - return; - - var newName = - await - _dialogService.ShowInputDialog("Rename layer", "Please enter a name for the layer", - new MetroDialogSettings {DefaultText = layer.Name}); - // Null when the user cancelled - if (string.IsNullOrEmpty(newName)) - return; - - layer.Name = newName; - UpdateLayerList(layer); - } - - /// - /// Clones the currently selected layer and adds it to the profile, after the original - /// - public void CloneLayer() - { - if (SelectedLayer == null) - return; - - CloneLayer(SelectedLayer); - } - - /// - /// Clones the given layer and adds it to the profile, after the original - /// - /// - public void CloneLayer(LayerModel layer) - { - var clone = GeneralHelpers.Clone(layer); - layer.InsertAfter(clone); - - UpdateLayerList(clone); - } - - private void UpdateLayerList(LayerModel selectModel) - { - // Update the UI - Layers.Clear(); - SelectedLayer = null; - - if (SelectedProfile != null) - Layers.AddRange(SelectedProfile.Layers); - - if (selectModel == null) - return; - - // A small delay to allow the profile list to rebuild - Task.Factory.StartNew(() => - { - Thread.Sleep(100); - SelectedLayer = selectModel; - }); - } - - /// - /// Handler for clicking - /// - /// - public void MouseDownKeyboardPreview(MouseButtonEventArgs e) - { - ProfileViewModel.MouseDownKeyboardPreview(e); - } - - /// - /// Second handler for clicking, selects a the layer the user clicked on - /// if the used clicked on an empty spot, deselects the current layer - /// - /// - public void MouseUpKeyboardPreview(MouseButtonEventArgs e) - { - ProfileViewModel.MouseUpKeyboardPreview(e); - } - - /// - /// Handler for resizing and moving the currently selected layer - /// - /// - public void MouseMoveKeyboardPreview(MouseEventArgs e) - { - ProfileViewModel.MouseMoveKeyboardPreview(e); - } - - /// - /// Adds a new profile to the current game and keyboard - /// - public async void AddProfile() - { - if (_deviceManager.ActiveKeyboard == null) - { - _dialogService.ShowMessageBox("Cannot add profile.", - "To add a profile, please select a keyboard in the options menu first."); - return; - } - - var name = await _dialogService.ShowInputDialog("Add new profile", - "Please provide a profile name unique to this game and keyboard."); - - // Null when the user cancelled - if (name == null) - return; - - if (name.Length < 2) - { - _dialogService.ShowMessageBox("Invalid profile name", "Please provide a valid profile name"); - return; - } - - var profile = new ProfileModel - { - Name = name, - KeyboardSlug = _deviceManager.ActiveKeyboard.Slug, - Width = _deviceManager.ActiveKeyboard.Width, - Height = _deviceManager.ActiveKeyboard.Height, - GameName = _moduleModel.Name - }; - - if (!ProfileProvider.IsProfileUnique(profile)) - { - var overwrite = await _dialogService.ShowQuestionMessageBox("Overwrite existing profile", - "A profile with this name already exists for this game. Would you like to overwrite it?"); - if (!overwrite.Value) - return; - } - - ProfileProvider.AddOrUpdate(profile); - - LoadProfiles(); - } - - public async void RenameProfile() - { - if (SelectedProfile == null) - return; - - var oldName = SelectedProfile.Name; - var name = await _dialogService.ShowInputDialog("Rename profile", "Please enter a unique new profile name"); - - // Null when the user cancelled - if (string.IsNullOrEmpty(name) || name.Length < 2) - return; - - SelectedProfile.Name = name; - - // Verify the name - while (!ProfileProvider.IsProfileUnique(SelectedProfile)) - { - name = await _dialogService - .ShowInputDialog("Name already in use", "Please enter a unique new profile name"); - - // Null when the user cancelled - if (string.IsNullOrEmpty(name) || name.Length < 2) - { - SelectedProfile.Name = oldName; - return; - } - SelectedProfile.Name = name; - } - - var profile = SelectedProfile; - ProfileProvider.RenameProfile(profile, name); - - LoadProfiles(); - } - - public async void DuplicateProfile() - { - if (SelectedProfile == null) - return; - - var newProfile = GeneralHelpers.Clone(SelectedProfile); - newProfile.Name = await _dialogService - .ShowInputDialog("Duplicate profile", "Please enter a unique profile name"); - - // Null when the user cancelled - if (string.IsNullOrEmpty(newProfile.Name)) - return; - - // Verify the name - while (!ProfileProvider.IsProfileUnique(newProfile)) - { - newProfile.Name = await _dialogService - .ShowInputDialog("Name already in use", "Please enter a unique profile name"); - - // Null when the user cancelled - if (string.IsNullOrEmpty(newProfile.Name)) - return; - } - - newProfile.IsDefault = false; - ProfileProvider.AddOrUpdate(newProfile); - LoadProfiles(); - } - - public async void DeleteProfile() - { - if (SelectedProfile == null) - return; - - var confirm = await - _dialogService.ShowQuestionMessageBox("Delete profile", - $"Are you sure you want to delete the profile named: {SelectedProfile.Name}?\n\n" + - "This cannot be undone."); - if (!confirm.Value) - return; - - var defaultProfile = ProfileProvider.GetProfile(_deviceManager.ActiveKeyboard, _moduleModel, "Default"); - var deleteProfile = SelectedProfile; - - _moduleModel.ChangeProfile(defaultProfile); - ProfileProvider.DeleteProfile(deleteProfile); - - LoadProfiles(); - } - - public async void ImportProfile() - { - if (_deviceManager.ActiveKeyboard == null) - { - _dialogService.ShowMessageBox("Cannot import profile.", - "To import a profile, please select a keyboard in the options menu first."); - return; - } - var dialog = new OpenFileDialog {Filter = "Artemis profile (*.json)|*.json"}; - var result = dialog.ShowDialog(); - if (result != DialogResult.OK) - return; - - var profile = ProfileProvider.LoadProfileIfValid(dialog.FileName); - if (profile == null) - { - _dialogService.ShowErrorMessageBox("Oh noes, the profile you provided is invalid. " + - "If this keeps happening, please make an issue on GitHub and provide the profile."); - return; - } - - // Verify the game - if (profile.GameName != _moduleModel.Name) - { - _dialogService.ShowErrorMessageBox( - $"Oh oops! This profile is ment for {profile.GameName}, not {_moduleModel.Name} :c"); - return; - } - - // Verify the keyboard - var deviceManager = _deviceManager; - if (profile.KeyboardSlug != deviceManager.ActiveKeyboard.Slug) - { - var adjustKeyboard = await _dialogService.ShowQuestionMessageBox( - "Profile not inteded for this keyboard", - $"Watch out, this profile wasn't ment for this keyboard, but for the {profile.KeyboardSlug}. " + - "You can still import it but you'll probably have to do some adjusting\n\n" + - "Continue?"); - if (!adjustKeyboard.Value) - return; - - // Resize layers that are on the full keyboard width - profile.ResizeLayers(deviceManager.ActiveKeyboard); - // Put layers back into the canvas if they fell outside it - profile.FixBoundaries(deviceManager.ActiveKeyboard.KeyboardRectangle(1)); - - // Setup profile metadata to match the new keyboard - profile.KeyboardSlug = deviceManager.ActiveKeyboard.Slug; - profile.Width = deviceManager.ActiveKeyboard.Width; - profile.Height = deviceManager.ActiveKeyboard.Height; - } - - profile.IsDefault = false; - - // Verify the name - while (!ProfileProvider.IsProfileUnique(profile)) - { - profile.Name = await _dialogService.ShowInputDialog("Rename imported profile", - "A profile with this name already exists for this game. Please enter a new name"); - - // Null when the user cancelled - if (string.IsNullOrEmpty(profile.Name)) - return; - } - - ProfileProvider.AddOrUpdate(profile); - LoadProfiles(); - } - - public void ExportProfile() - { - if (SelectedProfile == null) - return; - - var dialog = new SaveFileDialog {Filter = "Artemis profile (*.json)|*.json"}; - var result = dialog.ShowDialog(); - if (result != DialogResult.OK) - return; - - ProfileProvider.ExportProfile(SelectedProfile, dialog.FileName); - } - - public void EditLua() - { - if (SelectedProfile == null) - return; - try - { - OpenEditor(); - } - catch (Exception e) - { - _dialogService.ShowMessageBox("Couldn't open LUA file", - "Please make sure you have a text editor associated with the .lua extension.\n\n" + - "Windows error message: \n" + e.Message); - } - } - private void EditorStateHandler(object sender, PropertyChangedEventArgs e) { if (e.PropertyName != "SelectedProfile") @@ -706,90 +798,6 @@ namespace Artemis.ViewModels.Profiles NotifyOfPropertyChange(() => ProfileSelected); } - public void SaveSelectedProfile() - { - if (_saving || SelectedProfile == null || _deviceManager.ChangingKeyboard) - return; - - _saving = true; - try - { - ProfileProvider.AddOrUpdate(SelectedProfile); - } - catch (Exception) - { - // ignored - } - _saving = false; - } - - #region LUA Editor - - public void OpenEditor() - { - if (SelectedProfile == null) - return; - - // Create a temp file - var fileName = Guid.NewGuid() + ".lua"; - var file = File.Create(Path.GetTempPath() + fileName); - file.Dispose(); - - // Add instructions to LUA script if it's a new file - if (string.IsNullOrEmpty(SelectedProfile.LuaScript)) - SelectedProfile.LuaScript = Encoding.UTF8.GetString(Resources.lua_placeholder); - File.WriteAllText(Path.GetTempPath() + fileName, SelectedProfile.LuaScript); - - // Watch the file for changes - SetupWatcher(Path.GetTempPath(), fileName); - - // Open the temp file with the default editor - System.Diagnostics.Process.Start(Path.GetTempPath() + fileName); - } - - private void SetupWatcher(string path, string fileName) - { - if (_watcher == null) - { - _watcher = new FileSystemWatcher(Path.GetTempPath(), fileName); - _watcher.Changed += LuaFileChanged; - _watcher.EnableRaisingEvents = true; - } - - _watcher.Path = path; - _watcher.Filter = fileName; - } - - private void LuaFileChanged(object sender, FileSystemEventArgs args) - { - if (args.ChangeType != WatcherChangeTypes.Changed) - return; - - if (SelectedProfile == null) - return; - - lock (SelectedProfile) - { - using (var fs = new FileStream(args.FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - using (var sr = new StreamReader(fs)) - { - SelectedProfile.LuaScript = sr.ReadToEnd(); - } - } - - ProfileProvider.AddOrUpdate(SelectedProfile); - _luaManager.SetupLua(SelectedProfile); - } - } - - public void Dispose() - { - SaveSelectedProfile(); - ProfileViewModel.Dispose(); - _watcher?.Dispose(); - } - #endregion } } \ No newline at end of file diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs deleted file mode 100644 index 9487b026a..000000000 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs +++ /dev/null @@ -1,382 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; -using Artemis.Events; -using Artemis.Managers; -using Artemis.Modules.Abstract; -using Artemis.Profiles; -using Artemis.Profiles.Layers.Interfaces; -using Artemis.Profiles.Layers.Models; -using Artemis.Profiles.Layers.Types.Folder; -using Artemis.Properties; -using Artemis.Utilities; -using Caliburn.Micro; -using Castle.Components.DictionaryAdapter; -using MahApps.Metro; - -namespace Artemis.ViewModels.Profiles -{ - public class ProfileViewModel : PropertyChangedBase, IDisposable - { - private readonly DeviceManager _deviceManager; - private readonly LoopManager _loopManager; - private double _blurProgress; - private double _blurRadius; - private DateTime _downTime; - private LayerModel _draggingLayer; - private Point? _draggingLayerOffset; - private DrawingImage _keyboardPreview; - private Cursor _keyboardPreviewCursor; - private bool _resizing; - private bool _showAll; - - public ProfileViewModel(DeviceManager deviceManager, LoopManager loopManager) - { - _deviceManager = deviceManager; - _loopManager = loopManager; - - ShowAll = false; - - _loopManager.RenderCompleted += LoopManagerOnRenderCompleted; - _deviceManager.OnKeyboardChanged += DeviceManagerOnOnKeyboardChanged; - } - - public ModuleModel ModuleModel { get; set; } - public LayerModel SelectedLayer { get; set; } - - public ProfileModel SelectedProfile => ModuleModel?.ProfileModel; - - public DrawingImage KeyboardPreview - { - get { return _keyboardPreview; } - set - { - if (Equals(value, _keyboardPreview)) return; - _keyboardPreview = value; - NotifyOfPropertyChange(() => KeyboardPreview); - } - } - - public double BlurRadius - { - get { return _blurRadius; } - set - { - if (value.Equals(_blurRadius)) return; - _blurRadius = value; - NotifyOfPropertyChange(() => BlurRadius); - } - } - - public bool ShowAll - { - get { return _showAll; } - set - { - if (value == _showAll) return; - _showAll = value; - NotifyOfPropertyChange(() => ShowAll); - } - } - - public ImageSource KeyboardImage => ImageUtilities - .BitmapToBitmapImage(_deviceManager.ActiveKeyboard?.PreviewSettings.Image ?? Resources.none); - - private void LoopManagerOnRenderCompleted(object sender, EventArgs eventArgs) - { - // Update the glowing effect around the keyboard - if (_blurProgress > 2) - _blurProgress = 0; - _blurProgress = _blurProgress + 0.025; - BlurRadius = (Math.Sin(_blurProgress * Math.PI) + 1) * 10 + 10; - - // Besides the usual checks, also check if the ActiveKeyboard isn't the NoneKeyboard - if (SelectedProfile == null || _deviceManager.ActiveKeyboard == null || _deviceManager.ActiveKeyboard.Slug == "none") - { - KeyboardPreview = null; - - // Setup layers for the next frame - if (ModuleModel.IsInitialized && ActiveWindowHelper.MainWindowActive) - ModuleModel.PreviewLayers = new List(); - - return; - } - - var renderLayers = GetRenderLayers(); - // Draw the current frame to the preview - var keyboardRect = _deviceManager.ActiveKeyboard.KeyboardRectangle(); - var visual = new DrawingVisual(); - using (var drawingContext = visual.RenderOpen()) - { - // Setup the DrawingVisual's size - drawingContext.PushClip(new RectangleGeometry(keyboardRect)); - drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)), null, keyboardRect); - - // Draw the layers - foreach (var layer in renderLayers) - { - layer.Update(null, true, false); - if (layer.LayerType.ShowInEdtor) - layer.Draw(null, drawingContext, true, false); - } - - // Get the selection color - var accentColor = ThemeManager.DetectAppStyle(Application.Current)?.Item2?.Resources["AccentColor"]; - if (accentColor == null) - { - var preview = new DrawingImage(); - preview.Freeze(); - KeyboardPreview = preview; - return; - } - - var pen = new Pen(new SolidColorBrush((Color) accentColor), 0.4); - - // Draw the selection outline and resize indicator - if (SelectedLayer != null && SelectedLayer.MustDraw()) - { - var layerRect = SelectedLayer.Properties.PropertiesRect(); - // Deflate the rect so that the border is drawn on the inside - layerRect.Inflate(-0.2, -0.2); - - // Draw an outline around the selected layer - drawingContext.DrawRectangle(null, pen, layerRect); - // Draw a resize indicator in the bottom-right - drawingContext.DrawLine(pen, - new Point(layerRect.BottomRight.X - 1, layerRect.BottomRight.Y - 0.5), - new Point(layerRect.BottomRight.X - 1.2, layerRect.BottomRight.Y - 0.7)); - drawingContext.DrawLine(pen, - new Point(layerRect.BottomRight.X - 0.5, layerRect.BottomRight.Y - 1), - new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 1.2)); - drawingContext.DrawLine(pen, - new Point(layerRect.BottomRight.X - 0.5, layerRect.BottomRight.Y - 0.5), - new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 0.7)); - } - - SelectedProfile?.RaiseDeviceDrawnEvent(new ProfileDeviceEventsArg(DrawType.Preview, null, true, drawingContext)); - - // Remove the clip - drawingContext.Pop(); - } - var drawnPreview = new DrawingImage(visual.Drawing); - drawnPreview.Freeze(); - KeyboardPreview = drawnPreview; - - // Setup layers for the next frame - if (ModuleModel.IsInitialized && ActiveWindowHelper.MainWindowActive) - ModuleModel.PreviewLayers = renderLayers; - } - - private void DeviceManagerOnOnKeyboardChanged(object sender, KeyboardChangedEventArgs e) - { - NotifyOfPropertyChange(() => KeyboardImage); - } - - #region Processing - - /// - /// Handler for clicking - /// - /// - public void MouseDownKeyboardPreview(MouseButtonEventArgs e) - { - if (e.LeftButton == MouseButtonState.Pressed) - _downTime = DateTime.Now; - } - - /// - /// Second handler for clicking, selects a the layer the user clicked on. - /// - /// - public void MouseUpKeyboardPreview(MouseButtonEventArgs e) - { - if (SelectedProfile == null || SelectedProfile.IsDefault) - return; - - var timeSinceDown = DateTime.Now - _downTime; - if (!(timeSinceDown.TotalMilliseconds < 500)) - return; - if (_draggingLayer != null) - return; - - var keyboard = _deviceManager.ActiveKeyboard; - var pos = e.GetPosition((Image) e.OriginalSource); - var x = pos.X / ((double) keyboard.PreviewSettings.Width / keyboard.Width); - var y = pos.Y / ((double) keyboard.PreviewSettings.Height / keyboard.Height); - - var hoverLayer = GetLayers().Where(l => l.MustDraw()) - .FirstOrDefault(l => l.Properties.PropertiesRect(1).Contains(x, y)); - - if (hoverLayer != null) - SelectedLayer = hoverLayer; - } - - /// - /// Handler for resizing and moving the currently selected layer - /// - /// - public void MouseMoveKeyboardPreview(MouseEventArgs e) - { - if (SelectedProfile == null) - return; - - var pos = e.GetPosition((Image) e.OriginalSource); - var keyboard = _deviceManager.ActiveKeyboard; - var x = pos.X / ((double) keyboard.PreviewSettings.Width / keyboard.Width); - var y = pos.Y / ((double) keyboard.PreviewSettings.Height / keyboard.Height); - var hoverLayer = GetLayers().Where(l => l.MustDraw()) - .FirstOrDefault(l => l.Properties.PropertiesRect(1).Contains(x, y)); - - HandleDragging(e, x, y, hoverLayer); - - if (hoverLayer == null) - { - KeyboardPreviewCursor = Cursors.Arrow; - return; - } - - // Turn the mouse pointer into a hand if hovering over an active layer - if (hoverLayer == SelectedLayer) - { - var rect = hoverLayer.Properties.PropertiesRect(1); - KeyboardPreviewCursor = - Math.Sqrt(Math.Pow(x - rect.BottomRight.X, 2) + Math.Pow(y - rect.BottomRight.Y, 2)) < 0.6 - ? Cursors.SizeNWSE - : Cursors.SizeAll; - } - else - { - KeyboardPreviewCursor = Cursors.Hand; - } - } - - public Cursor KeyboardPreviewCursor - { - get { return _keyboardPreviewCursor; } - set - { - if (Equals(value, _keyboardPreviewCursor)) return; - _keyboardPreviewCursor = value; - NotifyOfPropertyChange(() => KeyboardPreviewCursor); - } - } - - /// - /// Handles dragging the given layer - /// - /// - /// - /// - /// - private void HandleDragging(MouseEventArgs e, double x, double y, LayerModel hoverLayer) - { - // Reset the dragging state on mouse release - if (e.LeftButton == MouseButtonState.Released || - _draggingLayer != null && SelectedLayer != _draggingLayer) - { - _draggingLayerOffset = null; - _draggingLayer = null; - return; - } - - if (SelectedLayer == null || SelectedLayer.LayerType != null && !SelectedLayer.LayerType.ShowInEdtor) - return; - - // Setup the dragging state on mouse press - if (_draggingLayerOffset == null && hoverLayer != null && e.LeftButton == MouseButtonState.Pressed) - { - var layerRect = hoverLayer.Properties.PropertiesRect(1); - - _draggingLayerOffset = new Point(x - SelectedLayer.Properties.X, y - SelectedLayer.Properties.Y); - _draggingLayer = hoverLayer; - // Detect dragging if cursor is in the bottom right - _resizing = Math.Sqrt(Math.Pow(x - layerRect.BottomRight.X, 2) + - Math.Pow(y - layerRect.BottomRight.Y, 2)) < 0.6; - } - - if (_draggingLayerOffset == null || _draggingLayer == null || _draggingLayer != SelectedLayer) - return; - - var draggingProps = _draggingLayer.Properties; - // If no setup or reset was done, handle the actual dragging action - if (_resizing) - { - var newWidth = Math.Round(x - draggingProps.X); - var newHeight = Math.Round(y - draggingProps.Y); - - // Ensure the layer doesn't leave the canvas - if (newWidth < 1 || draggingProps.X + newWidth <= 0) - newWidth = draggingProps.Width; - if (newHeight < 1 || draggingProps.Y + newHeight <= 0) - newHeight = draggingProps.Height; - - draggingProps.Width = newWidth; - draggingProps.Height = newHeight; - } - else - { - var newX = Math.Round(x - _draggingLayerOffset.Value.X); - var newY = Math.Round(y - _draggingLayerOffset.Value.Y); - - // Ensure the layer doesn't leave the canvas - if (newX >= SelectedProfile.Width || newX + draggingProps.Width <= 0) - newX = draggingProps.X; - if (newY >= SelectedProfile.Height || newY + draggingProps.Height <= 0) - newY = draggingProps.Y; - - draggingProps.X = newX; - draggingProps.Y = newY; - } - } - - public List GetRenderLayers() - { - // Get the layers that must be drawn - List drawLayers; - if (ShowAll) - return SelectedProfile.GetRenderLayers(null, false, true); - - if (SelectedLayer == null || !SelectedLayer.Enabled) - return new EditableList(); - - if (SelectedLayer.LayerType is FolderType) - drawLayers = SelectedLayer.GetRenderLayers(null, false, true); - else - drawLayers = new List {SelectedLayer}; - - return drawLayers; - } - - - private List GetLayers() - { - if (ShowAll) - return SelectedProfile.GetLayers(); - if (SelectedLayer == null) - return new List(); - - lock (SelectedLayer) - { - // Get the layers that must be drawn - if (SelectedLayer.LayerType is FolderType) - return SelectedLayer.GetLayers().ToList(); - return new List {SelectedLayer}; - } - } - - public void Dispose() - { - Disposed = true; - _loopManager.RenderCompleted -= LoopManagerOnRenderCompleted; - _deviceManager.OnKeyboardChanged -= DeviceManagerOnOnKeyboardChanged; - } - - public bool Disposed { get; set; } - - #endregion - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Views/Profiles/LayerEditorView.xaml b/Artemis/Artemis/Views/LayerEditorView.xaml similarity index 99% rename from Artemis/Artemis/Views/Profiles/LayerEditorView.xaml rename to Artemis/Artemis/Views/LayerEditorView.xaml index dabe59270..8c7adca13 100644 --- a/Artemis/Artemis/Views/Profiles/LayerEditorView.xaml +++ b/Artemis/Artemis/Views/LayerEditorView.xaml @@ -1,4 +1,4 @@ - /// Interaction logic for LayerEditorView.xaml diff --git a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml b/Artemis/Artemis/Views/ProfileEditorView.xaml similarity index 93% rename from Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml rename to Artemis/Artemis/Views/ProfileEditorView.xaml index 4a3c9e8b7..7c138d830 100644 --- a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml +++ b/Artemis/Artemis/Views/ProfileEditorView.xaml @@ -1,288 +1,297 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml.cs b/Artemis/Artemis/Views/ProfileEditorView.xaml.cs similarity index 89% rename from Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml.cs rename to Artemis/Artemis/Views/ProfileEditorView.xaml.cs index b2abd5ee2..35d68c535 100644 --- a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml.cs +++ b/Artemis/Artemis/Views/ProfileEditorView.xaml.cs @@ -1,6 +1,6 @@ using System.Windows.Controls; -namespace Artemis.Views.Profiles +namespace Artemis.Views { /// /// Interaction logic for ProfileEditorView.xaml From bc2508e4ac55a48a6ed54c2206dab1f27f1863f9 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sat, 14 Jan 2017 13:08:48 +0100 Subject: [PATCH 06/32] Profile editor tweaks and fixes Select profile after duplicate/add Select default profile after delete --- Artemis/Artemis/Models/ProfileEditorModel.cs | 39 +++++++++----- .../Modules/Abstract/ModuleViewModel.cs | 39 +++++++------- .../ViewModels/ProfileEditorViewModel.cs | 54 ++++++++++++++----- 3 files changed, 87 insertions(+), 45 deletions(-) diff --git a/Artemis/Artemis/Models/ProfileEditorModel.cs b/Artemis/Artemis/Models/ProfileEditorModel.cs index caefa2926..ebc55d2be 100644 --- a/Artemis/Artemis/Models/ProfileEditorModel.cs +++ b/Artemis/Artemis/Models/ProfileEditorModel.cs @@ -153,50 +153,54 @@ namespace Artemis.Models ProfileProvider.RenameProfile(profileModel, profileModel.Name); } - public async Task DuplicateProfile(ProfileModel selectedProfile) + public async Task DuplicateProfile(ProfileModel selectedProfile) { var newProfile = GeneralHelpers.Clone(selectedProfile); - var name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name"); + var name = await GetValidProfileName("Duplicate profile", "Please enter a unique new profile name"); // User cancelled if (name == null) - return; + return null; var doRename = await MakeProfileUnique(newProfile, name, newProfile.Name); if (!doRename) - return; + return null; // Make sure it's not default, in case of copying a default profile newProfile.IsDefault = false; ProfileProvider.AddOrUpdate(newProfile); + + return newProfile; } - public async Task DeleteProfile(ProfileModel selectedProfile, ModuleModel moduleModel) + public async Task DeleteProfile(ProfileModel selectedProfile, ModuleModel moduleModel) { var confirm = await _dialogService.ShowQuestionMessageBox("Delete profile", $"Are you sure you want to delete the profile named: {selectedProfile.Name}?\n\n" + "This cannot be undone."); if (!confirm.Value) - return; + return false; var defaultProfile = ProfileProvider.GetProfile(_deviceManager.ActiveKeyboard, moduleModel, "Default"); var deleteProfile = selectedProfile; moduleModel.ChangeProfile(defaultProfile); ProfileProvider.DeleteProfile(deleteProfile); + + return true; } - public async Task ImportProfile(ModuleModel moduleModel) + public async Task ImportProfile(ModuleModel moduleModel) { var dialog = new OpenFileDialog {Filter = "Artemis profile (*.json)|*.json"}; var result = dialog.ShowDialog(); if (result != DialogResult.OK) - return; + return null; var profileModel = ProfileProvider.LoadProfileIfValid(dialog.FileName); if (profileModel == null) { _dialogService.ShowErrorMessageBox("Oh noes, the profile you provided is invalid. " + "If this keeps happening, please make an issue on GitHub and provide the profile."); - return; + return null; } // Verify the game @@ -204,7 +208,7 @@ namespace Artemis.Models { _dialogService.ShowErrorMessageBox( $"Oh oops! This profile is ment for {profileModel.GameName}, not {moduleModel.Name} :c"); - return; + return null; } // Verify the keyboard @@ -216,7 +220,7 @@ namespace Artemis.Models "You can still import it but you'll probably have to do some adjusting\n\n" + "Continue?"); if (!adjustKeyboard.Value) - return; + return null; // Resize layers that are on the full keyboard width profileModel.ResizeLayers(deviceManager.ActiveKeyboard); @@ -232,13 +236,22 @@ namespace Artemis.Models var name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name"); // User cancelled if (name == null) - return; + return null; var doRename = await MakeProfileUnique(profileModel, name, profileModel.Name); if (!doRename) - return; + return null; profileModel.IsDefault = false; ProfileProvider.AddOrUpdate(profileModel); + return profileModel; + } + + public void ChangeProfileByName(ModuleModel moduleModel, string profileName) + { + if (string.IsNullOrEmpty(profileName)) + profileName = "Default"; + + moduleModel.ChangeProfile(ProfileProvider.GetProfile(_deviceManager.ActiveKeyboard, moduleModel, profileName)); } private async Task GetValidProfileName(string title, string text) diff --git a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs index fa0eeaca5..e20501ac8 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs @@ -1,9 +1,9 @@ -using Artemis.Events; +using Artemis.DAL; +using Artemis.Events; using Artemis.Managers; using Artemis.Services; using Artemis.Settings; using Artemis.ViewModels; -using Artemis.ViewModels.Profiles; using Caliburn.Micro; using Ninject; using Ninject.Extensions.Logging; @@ -13,23 +13,33 @@ namespace Artemis.Modules.Abstract { public abstract class ModuleViewModel : Screen { - private readonly ModuleManager _moduleManager; private readonly MainManager _mainManager; - private readonly IKernel _kernel; + private readonly ModuleManager _moduleManager; + private readonly GeneralSettings _generalSettings; private ModuleSettings _settings; - private GeneralSettings _generalSettings; public ModuleViewModel(MainManager mainManager, ModuleModel moduleModel, IKernel kernel) { _mainManager = mainManager; - _kernel = kernel; _moduleManager = mainManager.ModuleManager; - _generalSettings = DAL.SettingsProvider.Load(); + _generalSettings = SettingsProvider.Load(); ModuleModel = moduleModel; Settings = moduleModel.Settings; _mainManager.EnabledChanged += MainManagerOnEnabledChanged; _moduleManager.EffectChanged += ModuleManagerOnModuleChanged; + + // ReSharper disable once VirtualMemberCallInConstructor + if (!UsesProfileEditor) + return; + + IParameter[] args = + { + new ConstructorArgument("mainManager", _mainManager), + new ConstructorArgument("moduleModel", ModuleModel), + new ConstructorArgument("lastProfile", Settings.LastProfile) + }; + ProfileEditor = kernel.Get(args); } public ProfileEditorViewModel ProfileEditor { get; set; } @@ -142,24 +152,13 @@ namespace Artemis.Modules.Abstract protected override void OnActivate() { base.OnActivate(); - - if (!UsesProfileEditor) - return; - - IParameter[] args = - { - new ConstructorArgument("mainManager", _mainManager), - new ConstructorArgument("moduleModel", ModuleModel), - new ConstructorArgument("lastProfile", Settings.LastProfile) - }; - ProfileEditor = _kernel.Get(args); + ProfileEditor?.OnActivate(); } protected override void OnDeactivate(bool close) { base.OnDeactivate(close); - ProfileEditor?.Dispose(); - ProfileEditor = null; + ProfileEditor?.OnDeactivate(close); } } } \ No newline at end of file diff --git a/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs index 2b1da49ae..bfd4f67b2 100644 --- a/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs @@ -40,7 +40,7 @@ using Screen = Caliburn.Micro.Screen; namespace Artemis.ViewModels { - public sealed class ProfileEditorViewModel : Screen, IDropTarget, IDisposable + public sealed class ProfileEditorViewModel : Screen, IDropTarget { private readonly DeviceManager _deviceManager; private readonly MetroDialogService _dialogService; @@ -68,16 +68,22 @@ namespace Artemis.ViewModels PropertyChanged += EditorStateHandler; _deviceManager.OnKeyboardChanged += DeviceManagerOnOnKeyboardChanged; _moduleModel.ProfileChanged += ModuleModelOnProfileChanged; - _loopManager.RenderCompleted += LoopManagerOnRenderCompleted; LoadProfiles(); } - public void Dispose() + public new void OnActivate() { + base.OnActivate(); + + _loopManager.RenderCompleted += LoopManagerOnRenderCompleted; + } + + public new void OnDeactivate(bool close) + { + base.OnDeactivate(close); + SaveSelectedProfile(); - ProfileEditorModel.Dispose(); _loopManager.RenderCompleted -= LoopManagerOnRenderCompleted; - _deviceManager.OnKeyboardChanged -= DeviceManagerOnOnKeyboardChanged; } #region LUA @@ -154,11 +160,10 @@ namespace Artemis.ViewModels get { return SelectedProfile?.Name; } set { - if (value == SelectedProfile?.Name) - return; - - _moduleModel.ChangeProfile(ProfileProvider.GetProfile(_deviceManager.ActiveKeyboard, _moduleModel, value)); + SaveSelectedProfile(); NotifyOfPropertyChange(() => SelectedProfileName); + if (value != null) + ProfileEditorModel.ChangeProfileByName(_moduleModel, value); } } @@ -208,6 +213,15 @@ namespace Artemis.ViewModels UpdateLayerList(SelectedLayer); } + public void EditLayer(LayerModel layerModel) + { + if (layerModel == null) + return; + + ProfileEditorModel.EditLayer(layerModel, _moduleModel.DataModel); + UpdateLayerList(layerModel); + } + public LayerModel AddLayer() { if (SelectedProfile == null) @@ -355,6 +369,7 @@ namespace Artemis.ViewModels return; LoadProfiles(); + _moduleModel.ChangeProfile(profile); } public async void RenameProfile() @@ -362,8 +377,11 @@ namespace Artemis.ViewModels if (SelectedProfile == null) return; + var renameProfile = SelectedProfile; await ProfileEditorModel.RenameProfile(SelectedProfile); + LoadProfiles(); + _moduleModel.ChangeProfile(renameProfile); } public async void DuplicateProfile() @@ -371,8 +389,12 @@ namespace Artemis.ViewModels if (SelectedProfile == null) return; - await ProfileEditorModel.DuplicateProfile(SelectedProfile); + var newProfle = await ProfileEditorModel.DuplicateProfile(SelectedProfile); + if (newProfle == null) + return; + LoadProfiles(); + _moduleModel.ChangeProfile(newProfle); } public async void DeleteProfile() @@ -380,8 +402,12 @@ namespace Artemis.ViewModels if (SelectedProfile == null) return; - await ProfileEditorModel.DeleteProfile(SelectedProfile, _moduleModel); + var confirmed = await ProfileEditorModel.DeleteProfile(SelectedProfile, _moduleModel); + if (!confirmed) + return; + LoadProfiles(); + ProfileEditorModel.ChangeProfileByName(_moduleModel, null); } public async void ImportProfile() @@ -393,8 +419,12 @@ namespace Artemis.ViewModels return; } - await ProfileEditorModel.ImportProfile(_moduleModel); + var importProfile = await ProfileEditorModel.ImportProfile(_moduleModel); + if (importProfile == null) + return; + LoadProfiles(); + _moduleModel.ChangeProfile(importProfile); } public void ExportProfile() From 4e8be36174af2271bcf1562acb02093964079e88 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 14 Jan 2017 16:47:52 +0100 Subject: [PATCH 07/32] Changed the update-loop to be a single thread instead of a timer --- Artemis/Artemis/Managers/LoopManager.cs | 55 +++++++++++++++++-------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/Artemis/Artemis/Managers/LoopManager.cs b/Artemis/Artemis/Managers/LoopManager.cs index cb1493b10..06256803a 100644 --- a/Artemis/Artemis/Managers/LoopManager.cs +++ b/Artemis/Artemis/Managers/LoopManager.cs @@ -3,13 +3,10 @@ using System.Drawing; using System.Linq; using System.Threading; using System.Threading.Tasks; -using System.Timers; -using System.Windows.Media; using Artemis.DeviceProviders; using Artemis.ViewModels; using Ninject.Extensions.Logging; using Color = System.Drawing.Color; -using Timer = System.Timers.Timer; namespace Artemis.Managers { @@ -21,7 +18,8 @@ namespace Artemis.Managers private readonly DebugViewModel _debugViewModel; private readonly DeviceManager _deviceManager; private readonly ILogger _logger; - private readonly Timer _loopTimer; + //private readonly Timer _loopTimer; + private readonly Task _loopTask; private readonly ModuleManager _moduleManager; public LoopManager(ILogger logger, ModuleManager moduleManager, DeviceManager deviceManager, @@ -33,10 +31,10 @@ namespace Artemis.Managers _debugViewModel = debugViewModel; // Setup timers - _loopTimer = new Timer(40); - _loopTimer.Elapsed += LoopTimerOnElapsed; - _loopTimer.Start(); - + //_loopTimer = new Timer(40); + //_loopTimer.Elapsed += LoopTimerOnElapsed; + //_loopTimer.Start(); + _loopTask = Task.Factory.StartNew(ProcessLoop); _logger.Info("Intialized LoopManager"); } @@ -49,22 +47,45 @@ namespace Artemis.Managers public void Dispose() { - _loopTimer.Stop(); - _loopTimer.Dispose(); + _loopTask.Dispose(); + //_loopTimer.Stop(); + //_loopTimer.Dispose(); } - private void LoopTimerOnElapsed(object sender, ElapsedEventArgs elapsedEventArgs) + private void ProcessLoop() { - try + //TODO DarthAffe 14.01.2017: A stop-condition and a real cleanup instead of just aborting might be better + while (true) { - Render(); - } - catch (Exception e) - { - _logger.Warn(e, "Exception in render loop"); + try + { + long preUpdateTicks = DateTime.Now.Ticks; + + Render(); + + int sleep = (int)(40f - ((DateTime.Now.Ticks - preUpdateTicks) / 10000f)); + if (sleep > 0) + Thread.Sleep(sleep); + } + catch (Exception e) + { + _logger.Warn(e, "Exception in render loop"); + } } } + //private void LoopTimerOnElapsed(object sender, ElapsedEventArgs elapsedEventArgs) + //{ + // try + // { + // Render(); + // } + // catch (Exception e) + // { + // _logger.Warn(e, "Exception in render loop"); + // } + //} + public Task StartAsync() { return Task.Run(() => Start()); From e9d3040b649c45edba22f757ba917d8b2c34f4b9 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 14 Jan 2017 16:48:06 +0100 Subject: [PATCH 08/32] Added AngularBrush --- Artemis/Artemis/Artemis.csproj | 13 ++ .../AngularBrushPropertiesModel.cs | 28 ++++ .../AngularBrushPropertiesView.xaml | 80 ++++++++++ .../AngularBrushPropertiesView.xaml.cs | 12 ++ .../AngularBrushPropertiesViewModel.cs | 90 +++++++++++ .../Types/AngularBrush/AngularBrushType.cs | 110 +++++++++++++ .../AngularBrush/Drawing/GradientDrawer.cs | 149 ++++++++++++++++++ 7 files changed, 482 insertions(+) create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesViewModel.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 6452856de..c2b02415d 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -56,6 +56,7 @@ prompt 4 false + true x64 @@ -65,6 +66,7 @@ TRACE prompt 4 + true EAC088BE27A2DE790AE6F37A020409F4A1B5EC0E @@ -491,6 +493,13 @@ + + + AngularBrushPropertiesView.xaml + + + + @@ -861,6 +870,10 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + MSBuild:Compile Designer diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs new file mode 100644 index 000000000..58fdce801 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Windows.Media; +using Artemis.Profiles.Layers.Models; + +namespace Artemis.Profiles.Layers.Types.AngularBrush +{ + public class AngularBrushPropertiesModel : LayerPropertiesModel + { + #region Properties & Fields + + public IList> GradientStops { get; set; } + + #endregion + + #region Constructors + + public AngularBrushPropertiesModel(LayerPropertiesModel properties = null) + : base(properties) + { } + + #endregion + + #region Methods + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml new file mode 100644 index 000000000..b93018706 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml.cs new file mode 100644 index 000000000..e5edeea15 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml.cs @@ -0,0 +1,12 @@ +using System.Windows.Controls; + +namespace Artemis.Profiles.Layers.Types.AngularBrush +{ + public partial class AngularBrushPropertiesView : UserControl + { + public AngularBrushPropertiesView() + { + InitializeComponent(); + } + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesViewModel.cs new file mode 100644 index 000000000..0b4b792d7 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesViewModel.cs @@ -0,0 +1,90 @@ +using System; +using System.Linq; +using System.Windows.Media; +using Artemis.Profiles.Layers.Abstract; +using Artemis.Profiles.Layers.Interfaces; +using Artemis.Utilities; +using Artemis.ViewModels; +using Artemis.ViewModels.Profiles; +using Caliburn.Micro; + +namespace Artemis.Profiles.Layers.Types.AngularBrush +{ + public class AngularBrushPropertiesViewModel : LayerPropertiesViewModel + { + #region Properties & Fields + + private ILayerAnimation _selectedLayerAnimation; + + public BindableCollection DataModelProps { get; set; } + public BindableCollection LayerAnimations { get; set; } + public LayerDynamicPropertiesViewModel HeightProperties { get; set; } + public LayerDynamicPropertiesViewModel WidthProperties { get; set; } + public LayerDynamicPropertiesViewModel OpacityProperties { get; set; } + public LayerTweenViewModel LayerTweenViewModel { get; set; } + + public ILayerAnimation SelectedLayerAnimation + { + get { return _selectedLayerAnimation; } + set + { + if (Equals(value, _selectedLayerAnimation)) return; + _selectedLayerAnimation = value; + NotifyOfPropertyChange(() => SelectedLayerAnimation); + } + } + + #endregion + + #region Constructors + + public AngularBrushPropertiesViewModel(LayerEditorViewModel editorVm) + : base(editorVm) + { + LayerAnimations = new BindableCollection(editorVm.LayerAnimations); + + HeightProperties = new LayerDynamicPropertiesViewModel("Height", editorVm); + WidthProperties = new LayerDynamicPropertiesViewModel("Width", editorVm); + OpacityProperties = new LayerDynamicPropertiesViewModel("Opacity", editorVm); + LayerTweenViewModel = new LayerTweenViewModel(editorVm); + + SelectedLayerAnimation = + LayerAnimations.FirstOrDefault(l => l.Name == editorVm.ProposedLayer.LayerAnimation?.Name) ?? + LayerAnimations.First(l => l.Name == "None"); + } + + #endregion + + #region Methods + + public override void ApplyProperties() + { + HeightProperties.Apply(LayerModel); + WidthProperties.Apply(LayerModel); + OpacityProperties.Apply(LayerModel); + + ((AngularBrushPropertiesModel)LayerModel.Properties).GradientStops = GetGradientStops().Select(x => new Tuple(x.Offset, x.Color)).ToList(); + + LayerModel.LayerAnimation = SelectedLayerAnimation; + } + + private GradientStopCollection GetGradientStops() + { + LinearGradientBrush linearBrush = Brush as LinearGradientBrush; + if (linearBrush != null) + return linearBrush.GradientStops; + + RadialGradientBrush radialBrush = Brush as RadialGradientBrush; + if (radialBrush != null) + return radialBrush.GradientStops; + + SolidColorBrush solidBrush = Brush as SolidColorBrush; + if (solidBrush != null) + return new GradientStopCollection(new[] { new GradientStop(solidBrush.Color, 0), new GradientStop(solidBrush.Color, 1) }); + + return null; + } + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs new file mode 100644 index 000000000..6fc65b2ca --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs @@ -0,0 +1,110 @@ +using System.Windows; +using System.Windows.Media; +using Artemis.Modules.Abstract; +using Artemis.Profiles.Layers.Abstract; +using Artemis.Profiles.Layers.Animations; +using Artemis.Profiles.Layers.Interfaces; +using Artemis.Profiles.Layers.Models; +using Artemis.Profiles.Layers.Types.AngularBrush.Drawing; +using Artemis.ViewModels; + +namespace Artemis.Profiles.Layers.Types.AngularBrush +{ + public class AngularBrushType : ILayerType + { + #region Properties & Fields + + private GradientDrawer _gradientDrawer; + + public string Name => "Angular Brush"; + public bool ShowInEdtor => true; + public DrawType DrawType => DrawType.Keyboard; + + #endregion + + public AngularBrushType() + { + _gradientDrawer = new GradientDrawer(); + } + + #region Methods + + public ImageSource DrawThumbnail(LayerModel layer) + { + //TODO DarthAffe 14.01.2017: This could be replaced with the real brush but it complaints about the thread too + Rect thumbnailRect = new Rect(0, 0, 18, 18); + DrawingVisual visual = new DrawingVisual(); + using (DrawingContext c = visual.RenderOpen()) + if (layer.Properties.Brush != null) + c.DrawRectangle(layer.Properties.Brush, + new Pen(new SolidColorBrush(Colors.White), 1), + thumbnailRect); + + DrawingImage image = new DrawingImage(visual.Drawing); + return image; + } + + public void Draw(LayerModel layerModel, DrawingContext c) + { + AngularBrushPropertiesModel properties = layerModel.Properties as AngularBrushPropertiesModel; + if (properties == null) return; + + Brush origBrush = layerModel.Brush; + + _gradientDrawer.GradientStops = properties.GradientStops; + _gradientDrawer.Update(); + layerModel.Brush = _gradientDrawer.Brush.Clone(); + + // If an animation is present, let it handle the drawing + if (layerModel.LayerAnimation != null && !(layerModel.LayerAnimation is NoneAnimation)) + { + layerModel.LayerAnimation.Draw(layerModel, c); + return; + } + + // Otherwise draw the rectangle with its layer.AppliedProperties dimensions and brush + Rect rect = layerModel.Properties.Contain + ? layerModel.LayerRect() + : new Rect(layerModel.Properties.X * 4, layerModel.Properties.Y * 4, + layerModel.Properties.Width * 4, layerModel.Properties.Height * 4); + + Rect clip = layerModel.LayerRect(); + + // Can't meddle with the original brush because it's frozen. + Brush brush = layerModel.Brush.Clone(); + brush.Opacity = layerModel.Opacity; + + c.PushClip(new RectangleGeometry(clip)); + c.DrawRectangle(brush, null, rect); + c.Pop(); + + layerModel.Brush = origBrush; + } + + public void Update(LayerModel layerModel, ModuleDataModel dataModel, bool isPreview = false) + { + layerModel.ApplyProperties(true); + if (isPreview || dataModel == null) + return; + + // If not previewing, apply dynamic properties according to datamodel + foreach (DynamicPropertiesModel dynamicProperty in layerModel.Properties.DynamicProperties) + dynamicProperty.ApplyProperty(dataModel, layerModel); + } + + public void SetupProperties(LayerModel layerModel) + { + if (layerModel.Properties is AngularBrushPropertiesModel) + return; + + layerModel.Properties = new AngularBrushPropertiesModel(layerModel.Properties); + } + + public LayerPropertiesViewModel SetupViewModel(LayerEditorViewModel layerEditorViewModel, LayerPropertiesViewModel layerPropertiesViewModel) + { + return (layerPropertiesViewModel as AngularBrushPropertiesViewModel) ?? new AngularBrushPropertiesViewModel(layerEditorViewModel); + } + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs new file mode 100644 index 000000000..42dd4df72 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace Artemis.Profiles.Layers.Types.AngularBrush.Drawing +{ + public class GradientDrawer + { + #region Constants + + private static readonly double ORIGIN = Math.Atan2(-1, 0); + + #endregion + + #region Properties & Fields + + private WriteableBitmap _bitmap; + + private IList> _gradientStops; + public IList> GradientStops + { + set { _gradientStops = FixGradientStops(value); } + } + + public Brush Brush { get; private set; } + + #endregion + + #region Methods + + public void Update() + { + if (_bitmap == null) + CreateBrush(); + + unsafe + { + _bitmap.Lock(); + byte* buffer = (byte*)_bitmap.BackBuffer.ToPointer(); + + int width = _bitmap.PixelWidth; + double widthHalf = width / 2.0; + + int height = _bitmap.PixelHeight; + double heightHalf = height / 2.0; + + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + { + int offset = (((y * width) + x) * 4); + + double gradientOffset = CalculateGradientOffset(x, y, widthHalf, heightHalf); + GetColor(_gradientStops, gradientOffset, + ref buffer[offset + 3], ref buffer[offset + 2], + ref buffer[offset + 1], ref buffer[offset]); + } + + _bitmap.AddDirtyRect(new Int32Rect(0, 0, width, height)); + _bitmap.Unlock(); + } + } + + private void CreateBrush() + { + _bitmap = new WriteableBitmap(10, 10, 96, 96, PixelFormats.Bgra32, null); + Brush = new ImageBrush(_bitmap) { Stretch = Stretch.UniformToFill }; + } + + private double CalculateGradientOffset(double x, double y, double centerX, double centerY) + { + double angle = Math.Atan2(y - centerY, x - centerX) - ORIGIN; + if (angle < 0) angle += Math.PI * 2; + return angle / (Math.PI * 2); + } + + private static void GetColor(IList> gradientStops, double offset, ref byte colA, ref byte colR, ref byte colG, ref byte colB) + { + if (gradientStops.Count == 0) + { + colA = 0; + colR = 0; + colG = 0; + colB = 0; + return; + } + if (gradientStops.Count == 1) + { + Color color = gradientStops.First().Item2; + colA = color.A; + colR = color.R; + colG = color.G; + colB = color.B; + return; + } + + Tuple beforeStop = null; + double afterOffset = -1; + Color afterColor = default(Color); + + for (int i = 0; i < gradientStops.Count; i++) + { + Tuple gradientStop = gradientStops[i]; + double o = gradientStop.Item1; + if (o <= offset) + beforeStop = gradientStop; + + if (o >= offset) + { + afterOffset = gradientStop.Item1; + afterColor = gradientStop.Item2; + break; + } + } + double beforeOffset = beforeStop.Item1; + Color beforeColor = beforeStop.Item2; + + double blendFactor = 0f; + if (beforeOffset != afterOffset) + blendFactor = ((offset - beforeOffset) / (afterOffset - beforeOffset)); + + colA = (byte)((afterColor.A - beforeColor.A) * blendFactor + beforeColor.A); + colR = (byte)((afterColor.R - beforeColor.R) * blendFactor + beforeColor.R); + colG = (byte)((afterColor.G - beforeColor.G) * blendFactor + beforeColor.G); + colB = (byte)((afterColor.B - beforeColor.B) * blendFactor + beforeColor.B); + } + + private IList> FixGradientStops(IList> gradientStops) + { + if (gradientStops == null) return new List>(); + + List> stops = gradientStops.OrderBy(x => x.Item1).ToList(); + + Tuple firstStop = stops.First(); + if (firstStop.Item1 > 0) + stops.Insert(0, new Tuple(0, firstStop.Item2)); + + Tuple lastStop = stops.Last(); + if (lastStop.Item1 < 1) + stops.Add(new Tuple(1, lastStop.Item2)); + + return stops; + } + + #endregion + } +} From ce90dda807e294ec132d1bd4b2057e8f619e0d16 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sat, 14 Jan 2017 16:49:26 +0100 Subject: [PATCH 09/32] Added default device change detection to audio layer --- Artemis/Artemis/Artemis.csproj | 1 + .../Events/AudioDeviceChangedEventArgs.cs | 17 +++++++++ .../AudioCapturing/AudioCaptureManager.cs | 36 +++++++++++++++++-- .../Types/Audio/AudioPropertiesModel.cs | 18 ++++++++-- .../Profiles/Layers/Types/Audio/AudioType.cs | 32 ++++++++++++++--- 5 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 Artemis/Artemis/Events/AudioDeviceChangedEventArgs.cs diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 6452856de..c18676f4b 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -493,6 +493,7 @@ + diff --git a/Artemis/Artemis/Events/AudioDeviceChangedEventArgs.cs b/Artemis/Artemis/Events/AudioDeviceChangedEventArgs.cs new file mode 100644 index 000000000..05977f452 --- /dev/null +++ b/Artemis/Artemis/Events/AudioDeviceChangedEventArgs.cs @@ -0,0 +1,17 @@ +using System; +using CSCore.CoreAudioAPI; + +namespace Artemis.Events +{ + public class AudioDeviceChangedEventArgs : EventArgs + { + public AudioDeviceChangedEventArgs(MMDevice defaultPlayback, MMDevice defaultRecording) + { + DefaultPlayback = defaultPlayback; + DefaultRecording = defaultRecording; + } + + public MMDevice DefaultPlayback { get; } + public MMDevice DefaultRecording { get; } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs index cece1ddb0..cf7d34ee5 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs @@ -1,5 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using System.Timers; +using Artemis.Events; using CSCore.CoreAudioAPI; using Ninject.Extensions.Logging; @@ -8,17 +11,41 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing public class AudioCaptureManager { private readonly List _audioCaptures; + private MMDevice _lastDefaultPlayback; + private MMDevice _lastDefaultRecording; public AudioCaptureManager(ILogger logger) { Logger = logger; _audioCaptures = new List(); + _lastDefaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + _lastDefaultRecording = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); + + var defaultDeviceTimer = new Timer(1000); + defaultDeviceTimer.Elapsed += DefaultDeviceTimerOnElapsed; + defaultDeviceTimer.Start(); + } + + public event EventHandler AudioDeviceChanged; + + private void DefaultDeviceTimerOnElapsed(object sender, ElapsedEventArgs e) + { + var defaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + var defaultRecording = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); + + if (defaultPlayback.DeviceID == _lastDefaultPlayback.DeviceID && + defaultRecording.DeviceID == _lastDefaultRecording.DeviceID) + return; + + _lastDefaultPlayback = defaultPlayback; + _lastDefaultRecording = defaultRecording; + OnAudioDeviceChanged(new AudioDeviceChangedEventArgs(_lastDefaultPlayback, _lastDefaultRecording)); } public AudioCapture GetAudioCapture(MMDevice device) { // Return existing audio capture if found - var audioCapture = _audioCaptures.FirstOrDefault(a => a.Device == device); + var audioCapture = _audioCaptures.FirstOrDefault(a => a.Device.DeviceID == device.DeviceID); if (audioCapture != null) return audioCapture; @@ -29,5 +56,10 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing } public ILogger Logger { get; set; } + + protected virtual void OnAudioDeviceChanged(AudioDeviceChangedEventArgs e) + { + AudioDeviceChanged?.Invoke(this, e); + } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesModel.cs index 1c2b2b9be..f7768e3ff 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesModel.cs @@ -9,9 +9,23 @@ namespace Artemis.Profiles.Layers.Types.Audio { } - public int Sensitivity { get; set; } - public double FadeSpeed { get; set; } + [DefaultValue(MmDeviceType.Ouput)] + public MmDeviceType DeviceType { get; set; } + + [DefaultValue("Default")] + public string Device { get; set; } + + [DefaultValue(Direction.BottomToTop)] public Direction Direction { get; set; } + + [DefaultValue(ScalingStrategy.Decibel)] + public ScalingStrategy ScalingStrategy { get; set; } + } + + public enum MmDeviceType + { + [Description("Ouput")] Ouput, + [Description("Input")] Input } public enum Direction diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index a115f9bcb..2e52bff3c 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -1,6 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; using System.Windows; using System.Windows.Media; +using Artemis.Events; using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Animations; @@ -11,20 +15,35 @@ using Artemis.Properties; using Artemis.Utilities; using Artemis.ViewModels; using Artemis.ViewModels.Profiles; +using CSCore.CoreAudioAPI; namespace Artemis.Profiles.Layers.Types.Audio { public class AudioType : ILayerType { + private readonly AudioCaptureManager _audioCaptureManager; private const GeometryCombineMode CombineMode = GeometryCombineMode.Union; - private readonly AudioCapture _audioCapture; + private AudioCapture _audioCapture; private int _lines; private LineSpectrum _lineSpectrum; private List _lineValues; + private int _drawCount; public AudioType(AudioCaptureManager audioCaptureManager) { - _audioCapture = audioCaptureManager.GetAudioCapture(null); + _audioCaptureManager = audioCaptureManager; + + // TODO: Setup according to settings + _audioCapture = _audioCaptureManager.GetAudioCapture(MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia)); + _audioCaptureManager.AudioDeviceChanged += OnAudioDeviceChanged; + } + + private void OnAudioDeviceChanged(object sender, AudioDeviceChangedEventArgs e) + { + // TODO: Check if layer must use default + // TODO: Check recording type + _audioCapture = _audioCaptureManager.GetAudioCapture(e.DefaultPlayback); + _lines = 0; } public string Name => "Keyboard - Audio visualization"; @@ -46,6 +65,7 @@ namespace Artemis.Profiles.Layers.Types.Audio public void Draw(LayerModel layerModel, DrawingContext c) { + _drawCount++; if (_lineValues == null) return; @@ -136,8 +156,10 @@ namespace Artemis.Profiles.Layers.Types.Audio layerModel.Properties = new AudioPropertiesModel(layerModel.Properties) { - FadeSpeed = 0.2, - Sensitivity = 2 + DeviceType = MmDeviceType.Ouput, + Device = "Default", + Direction = Direction.BottomToTop, + ScalingStrategy = ScalingStrategy.Decibel }; } From d9d828ab21d4d1c7cbc55d9db9ee63689b67760b Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 14 Jan 2017 17:45:41 +0100 Subject: [PATCH 10/32] Fixed brush-save and adjusted render-resolution --- .../AngularBrushPropertiesViewModel.cs | 20 +--------------- .../Types/AngularBrush/AngularBrushType.cs | 24 +++++++++++++++++-- .../AngularBrush/Drawing/GradientDrawer.cs | 2 +- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesViewModel.cs index 0b4b792d7..e964b72fc 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesViewModel.cs @@ -63,28 +63,10 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush WidthProperties.Apply(LayerModel); OpacityProperties.Apply(LayerModel); - ((AngularBrushPropertiesModel)LayerModel.Properties).GradientStops = GetGradientStops().Select(x => new Tuple(x.Offset, x.Color)).ToList(); - + LayerModel.Properties.Brush = Brush; LayerModel.LayerAnimation = SelectedLayerAnimation; } - private GradientStopCollection GetGradientStops() - { - LinearGradientBrush linearBrush = Brush as LinearGradientBrush; - if (linearBrush != null) - return linearBrush.GradientStops; - - RadialGradientBrush radialBrush = Brush as RadialGradientBrush; - if (radialBrush != null) - return radialBrush.GradientStops; - - SolidColorBrush solidBrush = Brush as SolidColorBrush; - if (solidBrush != null) - return new GradientStopCollection(new[] { new GradientStop(solidBrush.Color, 0), new GradientStop(solidBrush.Color, 1) }); - - return null; - } - #endregion } } diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs index 6fc65b2ca..0bdf2775e 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs @@ -1,4 +1,6 @@ -using System.Windows; +using System; +using System.Linq; +using System.Windows; using System.Windows.Media; using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Abstract; @@ -51,7 +53,8 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush Brush origBrush = layerModel.Brush; - _gradientDrawer.GradientStops = properties.GradientStops; + //TODO DarthAffe 14.01.2017: Check if an update is needed + _gradientDrawer.GradientStops = GetGradientStops(layerModel.Brush).Select(x => new Tuple(x.Offset, x.Color)).ToList(); _gradientDrawer.Update(); layerModel.Brush = _gradientDrawer.Brush.Clone(); @@ -105,6 +108,23 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush return (layerPropertiesViewModel as AngularBrushPropertiesViewModel) ?? new AngularBrushPropertiesViewModel(layerEditorViewModel); } + private GradientStopCollection GetGradientStops(Brush brush) + { + LinearGradientBrush linearBrush = brush as LinearGradientBrush; + if (linearBrush != null) + return linearBrush.GradientStops; + + RadialGradientBrush radialBrush = brush as RadialGradientBrush; + if (radialBrush != null) + return radialBrush.GradientStops; + + SolidColorBrush solidBrush = brush as SolidColorBrush; + if (solidBrush != null) + return new GradientStopCollection(new[] { new GradientStop(solidBrush.Color, 0), new GradientStop(solidBrush.Color, 1) }); + + return null; + } + #endregion } } diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs index 42dd4df72..fd3d7e8c2 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs @@ -65,7 +65,7 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush.Drawing private void CreateBrush() { - _bitmap = new WriteableBitmap(10, 10, 96, 96, PixelFormats.Bgra32, null); + _bitmap = new WriteableBitmap(100, 100, 96, 96, PixelFormats.Bgra32, null); Brush = new ImageBrush(_bitmap) { Stretch = Stretch.UniformToFill }; } From 0db7448f1514e94f8c566eb4f8e1980f048c21ff Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 14 Jan 2017 23:18:43 +0100 Subject: [PATCH 11/32] Fixed Thumbnail, prevented rendering without changes to improve performance, made render-center-point potential adjustable --- .../AngularBrushPropertiesModel.cs | 4 -- .../Types/AngularBrush/AngularBrushType.cs | 15 +++-- .../AngularBrush/Drawing/GradientDrawer.cs | 64 ++++++++++++++----- 3 files changed, 56 insertions(+), 27 deletions(-) diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs index 58fdce801..7dcced567 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs @@ -20,9 +20,5 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush { } #endregion - - #region Methods - - #endregion } } diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs index 0bdf2775e..0a562b8f3 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs @@ -17,6 +17,7 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush #region Properties & Fields private GradientDrawer _gradientDrawer; + private GradientDrawer _gradientDrawerThumbnail; public string Name => "Angular Brush"; public bool ShowInEdtor => true; @@ -27,20 +28,21 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush public AngularBrushType() { _gradientDrawer = new GradientDrawer(); + _gradientDrawerThumbnail = new GradientDrawer(18, 18); } #region Methods public ImageSource DrawThumbnail(LayerModel layer) { - //TODO DarthAffe 14.01.2017: This could be replaced with the real brush but it complaints about the thread too + _gradientDrawerThumbnail.GradientStops = GetGradientStops(layer.Brush).Select(x => new Tuple(x.Offset, x.Color)).ToList(); + _gradientDrawerThumbnail.Update(); + Rect thumbnailRect = new Rect(0, 0, 18, 18); DrawingVisual visual = new DrawingVisual(); using (DrawingContext c = visual.RenderOpen()) - if (layer.Properties.Brush != null) - c.DrawRectangle(layer.Properties.Brush, - new Pen(new SolidColorBrush(Colors.White), 1), - thumbnailRect); + if (_gradientDrawerThumbnail.Brush != null) + c.DrawRectangle(_gradientDrawerThumbnail.Brush.Clone(), new Pen(new SolidColorBrush(Colors.White), 1), thumbnailRect); DrawingImage image = new DrawingImage(visual.Drawing); return image; @@ -53,10 +55,9 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush Brush origBrush = layerModel.Brush; - //TODO DarthAffe 14.01.2017: Check if an update is needed _gradientDrawer.GradientStops = GetGradientStops(layerModel.Brush).Select(x => new Tuple(x.Offset, x.Color)).ToList(); _gradientDrawer.Update(); - layerModel.Brush = _gradientDrawer.Brush.Clone(); + layerModel.Brush = _gradientDrawer.Brush; // If an animation is present, let it handle the drawing if (layerModel.LayerAnimation != null && !(layerModel.LayerAnimation is NoneAnimation)) diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs index fd3d7e8c2..dd41a7a0c 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; +using Brush = System.Windows.Media.Brush; +using Color = System.Windows.Media.Color; namespace Artemis.Profiles.Layers.Types.AngularBrush.Drawing { @@ -13,26 +16,59 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush.Drawing private static readonly double ORIGIN = Math.Atan2(-1, 0); - #endregion + #endregion #region Properties & Fields + private int _width; + private int _height; + + private bool _isDirty = true; + private int _lastGradientHash; + private WriteableBitmap _bitmap; private IList> _gradientStops; public IList> GradientStops { - set { _gradientStops = FixGradientStops(value); } + set + { + int hash = value.GetHashCode(); + if (_lastGradientHash != hash) + { + _gradientStops = FixGradientStops(value); + _lastGradientHash = hash; + _isDirty = true; + } + } } + public PointF Center { get; set; } = new PointF(0.5f, 0.5f); + public Brush Brush { get; private set; } #endregion + #region Constructors + + public GradientDrawer() + : this(100, 100) + { } + + public GradientDrawer(int width, int height) + { + this._width = width; + this._height = height; + } + + #endregion + #region Methods - public void Update() + public void Update(bool force = false) { + if (!_isDirty && !force) return; + if (_bitmap == null) CreateBrush(); @@ -41,31 +77,27 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush.Drawing _bitmap.Lock(); byte* buffer = (byte*)_bitmap.BackBuffer.ToPointer(); - int width = _bitmap.PixelWidth; - double widthHalf = width / 2.0; - - int height = _bitmap.PixelHeight; - double heightHalf = height / 2.0; - - for (int y = 0; y < height; y++) - for (int x = 0; x < width; x++) + for (int y = 0; y < _height; y++) + for (int x = 0; x < _width; x++) { - int offset = (((y * width) + x) * 4); + int offset = (((y * _width) + x) * 4); - double gradientOffset = CalculateGradientOffset(x, y, widthHalf, heightHalf); + double gradientOffset = CalculateGradientOffset(x, y, _width * Center.X, _height * Center.Y); GetColor(_gradientStops, gradientOffset, ref buffer[offset + 3], ref buffer[offset + 2], ref buffer[offset + 1], ref buffer[offset]); } - _bitmap.AddDirtyRect(new Int32Rect(0, 0, width, height)); + _bitmap.AddDirtyRect(new Int32Rect(0, 0, _width, _height)); _bitmap.Unlock(); } + + _isDirty = false; } private void CreateBrush() { - _bitmap = new WriteableBitmap(100, 100, 96, 96, PixelFormats.Bgra32, null); + _bitmap = new WriteableBitmap(_width, _height, 96, 96, PixelFormats.Bgra32, null); Brush = new ImageBrush(_bitmap) { Stretch = Stretch.UniformToFill }; } @@ -127,7 +159,7 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush.Drawing colB = (byte)((afterColor.B - beforeColor.B) * blendFactor + beforeColor.B); } - private IList> FixGradientStops(IList> gradientStops) + private static IList> FixGradientStops(IList> gradientStops) { if (gradientStops == null) return new List>(); From f7b945cc65c12203ce802cebcdbc3c6bd330cb9a Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 14 Jan 2017 23:21:06 +0100 Subject: [PATCH 12/32] Fixed hash-calculation --- .../Types/AngularBrush/Drawing/GradientDrawer.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs index dd41a7a0c..36ea1fe51 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs @@ -33,7 +33,7 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush.Drawing { set { - int hash = value.GetHashCode(); + int hash = GetHash(value); if (_lastGradientHash != hash) { _gradientStops = FixGradientStops(value); @@ -176,6 +176,14 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush.Drawing return stops; } + private static int GetHash(IList> sequence) + { + unchecked + { + return sequence.Aggregate(487, (current, item) => (((current * 31) + item.Item1.GetHashCode()) * 31) + item.Item2.GetHashCode()); + } + } + #endregion } } From 04c28e2951fc176f18ca9544add5a3194013bb7a Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 14 Jan 2017 23:23:10 +0100 Subject: [PATCH 13/32] Updated dirty-flag if center is changed --- .../Types/AngularBrush/Drawing/GradientDrawer.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs index 36ea1fe51..112af1048 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs @@ -43,7 +43,19 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush.Drawing } } - public PointF Center { get; set; } = new PointF(0.5f, 0.5f); + private PointF _center = new PointF(0.5f, 0.5f); + public PointF Center + { + get { return _center; } + set + { + if (_center != value) + { + _center = value; + _isDirty = true; + } + } + } public Brush Brush { get; private set; } From e028467bbb004a7642a4e03086e4d0f9a41cd74b Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 15 Jan 2017 13:22:25 +0100 Subject: [PATCH 14/32] Renamed AngularBrush to ConicalBrush --- Artemis/Artemis/Artemis.csproj | 14 +++---- .../AngularBrushPropertiesView.xaml.cs | 12 ------ .../ConicalBrushPropertiesModel.cs} | 6 +-- .../ConicalBrushPropertiesView.xaml} | 2 +- .../ConicalBrushPropertiesView.xaml.cs | 12 ++++++ .../ConicalBrushPropertiesViewModel.cs} | 10 ++--- .../ConicalBrushType.cs} | 40 +++++++++---------- .../Drawing/ConicalGradientDrawer.cs} | 8 ++-- 8 files changed, 51 insertions(+), 53 deletions(-) delete mode 100644 Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml.cs rename Artemis/Artemis/Profiles/Layers/Types/{AngularBrush/AngularBrushPropertiesModel.cs => ConicalBrush/ConicalBrushPropertiesModel.cs} (67%) rename Artemis/Artemis/Profiles/Layers/Types/{AngularBrush/AngularBrushPropertiesView.xaml => ConicalBrush/ConicalBrushPropertiesView.xaml} (97%) create mode 100644 Artemis/Artemis/Profiles/Layers/Types/ConicalBrush/ConicalBrushPropertiesView.xaml.cs rename Artemis/Artemis/Profiles/Layers/Types/{AngularBrush/AngularBrushPropertiesViewModel.cs => ConicalBrush/ConicalBrushPropertiesViewModel.cs} (90%) rename Artemis/Artemis/Profiles/Layers/Types/{AngularBrush/AngularBrushType.cs => ConicalBrush/ConicalBrushType.cs} (69%) rename Artemis/Artemis/Profiles/Layers/Types/{AngularBrush/Drawing/GradientDrawer.cs => ConicalBrush/Drawing/ConicalGradientDrawer.cs} (96%) diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 46f1173c5..492b48c98 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -493,13 +493,13 @@ - - - AngularBrushPropertiesView.xaml + + + ConicalBrushPropertiesView.xaml - - - + + + @@ -871,7 +871,7 @@ MSBuild:Compile Designer - + Designer MSBuild:Compile diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml.cs deleted file mode 100644 index e5edeea15..000000000 --- a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Windows.Controls; - -namespace Artemis.Profiles.Layers.Types.AngularBrush -{ - public partial class AngularBrushPropertiesView : UserControl - { - public AngularBrushPropertiesView() - { - InitializeComponent(); - } - } -} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs b/Artemis/Artemis/Profiles/Layers/Types/ConicalBrush/ConicalBrushPropertiesModel.cs similarity index 67% rename from Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs rename to Artemis/Artemis/Profiles/Layers/Types/ConicalBrush/ConicalBrushPropertiesModel.cs index 7dcced567..a7a63c3db 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/ConicalBrush/ConicalBrushPropertiesModel.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using System.Windows.Media; using Artemis.Profiles.Layers.Models; -namespace Artemis.Profiles.Layers.Types.AngularBrush +namespace Artemis.Profiles.Layers.Types.ConicalBrush { - public class AngularBrushPropertiesModel : LayerPropertiesModel + public class ConicalBrushPropertiesModel : LayerPropertiesModel { #region Properties & Fields @@ -15,7 +15,7 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush #region Constructors - public AngularBrushPropertiesModel(LayerPropertiesModel properties = null) + public ConicalBrushPropertiesModel(LayerPropertiesModel properties = null) : base(properties) { } diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml b/Artemis/Artemis/Profiles/Layers/Types/ConicalBrush/ConicalBrushPropertiesView.xaml similarity index 97% rename from Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml rename to Artemis/Artemis/Profiles/Layers/Types/ConicalBrush/ConicalBrushPropertiesView.xaml index b93018706..d2a9ad61b 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml +++ b/Artemis/Artemis/Profiles/Layers/Types/ConicalBrush/ConicalBrushPropertiesView.xaml @@ -1,4 +1,4 @@ -(editorVm.LayerAnimations); diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs b/Artemis/Artemis/Profiles/Layers/Types/ConicalBrush/ConicalBrushType.cs similarity index 69% rename from Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs rename to Artemis/Artemis/Profiles/Layers/Types/ConicalBrush/ConicalBrushType.cs index 0a562b8f3..347f56c59 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/ConicalBrush/ConicalBrushType.cs @@ -7,42 +7,42 @@ using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Animations; using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; -using Artemis.Profiles.Layers.Types.AngularBrush.Drawing; +using Artemis.Profiles.Layers.Types.ConicalBrush.Drawing; using Artemis.ViewModels; -namespace Artemis.Profiles.Layers.Types.AngularBrush +namespace Artemis.Profiles.Layers.Types.ConicalBrush { - public class AngularBrushType : ILayerType + public class ConicalBrushType : ILayerType { #region Properties & Fields - private GradientDrawer _gradientDrawer; - private GradientDrawer _gradientDrawerThumbnail; + private ConicalGradientDrawer _conicalGradientDrawer; + private ConicalGradientDrawer _conicalGradientDrawerThumbnail; - public string Name => "Angular Brush"; + public string Name => "Conical Brush"; public bool ShowInEdtor => true; public DrawType DrawType => DrawType.Keyboard; #endregion - public AngularBrushType() + public ConicalBrushType() { - _gradientDrawer = new GradientDrawer(); - _gradientDrawerThumbnail = new GradientDrawer(18, 18); + _conicalGradientDrawer = new ConicalGradientDrawer(); + _conicalGradientDrawerThumbnail = new ConicalGradientDrawer(18, 18); } #region Methods public ImageSource DrawThumbnail(LayerModel layer) { - _gradientDrawerThumbnail.GradientStops = GetGradientStops(layer.Brush).Select(x => new Tuple(x.Offset, x.Color)).ToList(); - _gradientDrawerThumbnail.Update(); + _conicalGradientDrawerThumbnail.GradientStops = GetGradientStops(layer.Brush).Select(x => new Tuple(x.Offset, x.Color)).ToList(); + _conicalGradientDrawerThumbnail.Update(); Rect thumbnailRect = new Rect(0, 0, 18, 18); DrawingVisual visual = new DrawingVisual(); using (DrawingContext c = visual.RenderOpen()) - if (_gradientDrawerThumbnail.Brush != null) - c.DrawRectangle(_gradientDrawerThumbnail.Brush.Clone(), new Pen(new SolidColorBrush(Colors.White), 1), thumbnailRect); + if (_conicalGradientDrawerThumbnail.Brush != null) + c.DrawRectangle(_conicalGradientDrawerThumbnail.Brush.Clone(), new Pen(new SolidColorBrush(Colors.White), 1), thumbnailRect); DrawingImage image = new DrawingImage(visual.Drawing); return image; @@ -50,14 +50,14 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush public void Draw(LayerModel layerModel, DrawingContext c) { - AngularBrushPropertiesModel properties = layerModel.Properties as AngularBrushPropertiesModel; + ConicalBrushPropertiesModel properties = layerModel.Properties as ConicalBrushPropertiesModel; if (properties == null) return; Brush origBrush = layerModel.Brush; - _gradientDrawer.GradientStops = GetGradientStops(layerModel.Brush).Select(x => new Tuple(x.Offset, x.Color)).ToList(); - _gradientDrawer.Update(); - layerModel.Brush = _gradientDrawer.Brush; + _conicalGradientDrawer.GradientStops = GetGradientStops(layerModel.Brush).Select(x => new Tuple(x.Offset, x.Color)).ToList(); + _conicalGradientDrawer.Update(); + layerModel.Brush = _conicalGradientDrawer.Brush; // If an animation is present, let it handle the drawing if (layerModel.LayerAnimation != null && !(layerModel.LayerAnimation is NoneAnimation)) @@ -98,15 +98,15 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush public void SetupProperties(LayerModel layerModel) { - if (layerModel.Properties is AngularBrushPropertiesModel) + if (layerModel.Properties is ConicalBrushPropertiesModel) return; - layerModel.Properties = new AngularBrushPropertiesModel(layerModel.Properties); + layerModel.Properties = new ConicalBrushPropertiesModel(layerModel.Properties); } public LayerPropertiesViewModel SetupViewModel(LayerEditorViewModel layerEditorViewModel, LayerPropertiesViewModel layerPropertiesViewModel) { - return (layerPropertiesViewModel as AngularBrushPropertiesViewModel) ?? new AngularBrushPropertiesViewModel(layerEditorViewModel); + return (layerPropertiesViewModel as ConicalBrushPropertiesViewModel) ?? new ConicalBrushPropertiesViewModel(layerEditorViewModel); } private GradientStopCollection GetGradientStops(Brush brush) diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs b/Artemis/Artemis/Profiles/Layers/Types/ConicalBrush/Drawing/ConicalGradientDrawer.cs similarity index 96% rename from Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs rename to Artemis/Artemis/Profiles/Layers/Types/ConicalBrush/Drawing/ConicalGradientDrawer.cs index 112af1048..a68d93028 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/ConicalBrush/Drawing/ConicalGradientDrawer.cs @@ -8,9 +8,9 @@ using System.Windows.Media.Imaging; using Brush = System.Windows.Media.Brush; using Color = System.Windows.Media.Color; -namespace Artemis.Profiles.Layers.Types.AngularBrush.Drawing +namespace Artemis.Profiles.Layers.Types.ConicalBrush.Drawing { - public class GradientDrawer + public class ConicalGradientDrawer { #region Constants @@ -63,11 +63,11 @@ namespace Artemis.Profiles.Layers.Types.AngularBrush.Drawing #region Constructors - public GradientDrawer() + public ConicalGradientDrawer() : this(100, 100) { } - public GradientDrawer(int width, int height) + public ConicalGradientDrawer(int width, int height) { this._width = width; this._height = height; From e451a039966407798ad4bd523caf928f697fdf42 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sun, 15 Jan 2017 17:16:06 +0100 Subject: [PATCH 15/32] Add audio values to general profile datamodel --- Artemis/Artemis/Artemis.csproj | 1 + Artemis/Artemis/Models/ProfileEditorModel.cs | 22 +-- .../GeneralProfile/GeneralProfileDataModel.cs | 25 ++++ .../GeneralProfile/GeneralProfileModel.cs | 66 ++++++++- .../AudioCapturing/AudioCaptureManager.cs | 126 +++++++++--------- Artemis/Artemis/Profiles/ProfileModel.cs | 3 +- .../ViewModels/ProfileEditorViewModel.cs | 2 +- 7 files changed, 164 insertions(+), 81 deletions(-) diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 46f1173c5..ae98da40c 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -110,6 +110,7 @@ x64 prompt MinimumRecommendedRules.ruleset + true bin\x64\Release\ diff --git a/Artemis/Artemis/Models/ProfileEditorModel.cs b/Artemis/Artemis/Models/ProfileEditorModel.cs index ebc55d2be..050530bae 100644 --- a/Artemis/Artemis/Models/ProfileEditorModel.cs +++ b/Artemis/Artemis/Models/ProfileEditorModel.cs @@ -25,7 +25,7 @@ namespace Artemis.Models private readonly DialogService _dialogService; private readonly WindowService _windowService; private FileSystemWatcher _watcher; - private ProfileModel _luaProfile; + private ModuleModel _luaModule; public ProfileEditorModel(WindowService windowService, MetroDialogService dialogService, DeviceManager deviceManager, LuaManager luaManager) @@ -290,7 +290,7 @@ namespace Artemis.Models #region LUA - public void OpenLuaEditor(ProfileModel profileModel) + public void OpenLuaEditor(ModuleModel moduleModel) { // Clean up old environment DisposeLuaWatcher(); @@ -301,12 +301,12 @@ namespace Artemis.Models file.Dispose(); // Add instructions to LUA script if it's a new file - if (string.IsNullOrEmpty(profileModel.LuaScript)) - profileModel.LuaScript = Encoding.UTF8.GetString(Resources.lua_placeholder); - File.WriteAllText(Path.GetTempPath() + fileName, profileModel.LuaScript); + if (string.IsNullOrEmpty(moduleModel.ProfileModel.LuaScript)) + moduleModel.ProfileModel.LuaScript = Encoding.UTF8.GetString(Resources.lua_placeholder); + File.WriteAllText(Path.GetTempPath() + fileName, moduleModel.ProfileModel.LuaScript); // Watch the file for changes - _luaProfile = profileModel; + _luaModule = moduleModel; _watcher = new FileSystemWatcher(Path.GetTempPath(), fileName); _watcher.Changed += LuaFileChanged; _watcher.EnableRaisingEvents = true; @@ -319,7 +319,7 @@ namespace Artemis.Models private void LuaFileChanged(object sender, FileSystemEventArgs args) { - if (_luaProfile == null) + if (_luaModule == null) { DisposeLuaWatcher(); return; @@ -328,18 +328,18 @@ namespace Artemis.Models if (args.ChangeType != WatcherChangeTypes.Changed) return; - lock (_luaProfile) + lock (_luaModule) { using (var fs = new FileStream(args.FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { using (var sr = new StreamReader(fs)) { - _luaProfile.LuaScript = sr.ReadToEnd(); + _luaModule.ProfileModel.LuaScript = sr.ReadToEnd(); } } - ProfileProvider.AddOrUpdate(_luaProfile); - _luaManager.SetupLua(_luaProfile); + ProfileProvider.AddOrUpdate(_luaModule.ProfileModel); + _luaManager.SetupLua(_luaModule.ProfileModel); } } diff --git a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileDataModel.cs b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileDataModel.cs index 7400ad81f..95d252fd4 100644 --- a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileDataModel.cs +++ b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileDataModel.cs @@ -15,6 +15,7 @@ namespace Artemis.Modules.General.GeneralProfile CurrentTime = new CurrentTime(); Keyboard = new KbDataModel(); ActiveWindow = new ActiveWindow(); + Audio = new Audio(); } public CpuDataModel Cpu { get; set; } @@ -24,6 +25,30 @@ namespace Artemis.Modules.General.GeneralProfile public CurrentTime CurrentTime { get; set; } public KbDataModel Keyboard { get; set; } public ActiveWindow ActiveWindow { get; set; } + public Audio Audio { get; set; } + } + + [MoonSharpUserData] + public class Audio + { + public double Volume { get; set; } + public double Peak { get; set; } + public AudioDevice Recording { get; set; } = new AudioDevice(); + public AudioDevice Playback { get; set; } = new AudioDevice(); + } + + [MoonSharpUserData] + public class AudioDevice + { + public float OverallPeak { get; set; } + public float Channel1Peak { get; set; } + public float Channel2Peak { get; set; } + public float Channel3Peak { get; set; } + public float Channel4Peak { get; set; } + public float Channel5Peak { get; set; } + public float Channel6Peak { get; set; } + public float Channel7Peak { get; set; } + public float Channel8Peak { get; set; } } [MoonSharpUserData] diff --git a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs index 73162b267..b407cc8d0 100644 --- a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs +++ b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs @@ -7,9 +7,12 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Artemis.DAL; +using Artemis.Events; using Artemis.Managers; using Artemis.Modules.Abstract; +using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; using Artemis.Utilities; +using CSCore.CoreAudioAPI; using Newtonsoft.Json; using SpotifyAPI.Local; @@ -17,19 +20,21 @@ namespace Artemis.Modules.General.GeneralProfile { public class GeneralProfileModel : ModuleModel { - private List _cores; - private int _cpuFrames; + private readonly AudioCaptureManager _audioCaptureManager; private DateTime _lastMusicUpdate; - private PerformanceCounter _overallCpu; private SpotifyLocalAPI _spotify; private bool _spotifySetupBusy; - public GeneralProfileModel(DeviceManager deviceManager, LuaManager luaManager) : base(deviceManager, luaManager) + public GeneralProfileModel(DeviceManager deviceManager, LuaManager luaManager, + AudioCaptureManager audioCaptureManager) : base(deviceManager, luaManager) { + _audioCaptureManager = audioCaptureManager; _lastMusicUpdate = DateTime.Now; Settings = SettingsProvider.Load(); DataModel = new GeneralProfileDataModel(); + + audioCaptureManager.AudioDeviceChanged += AudioDeviceChanged; } public override string Name => "GeneralProfile"; @@ -40,6 +45,7 @@ namespace Artemis.Modules.General.GeneralProfile { SetupCpu(); SetupSpotify(); + SetupAudio(); base.Enable(); } @@ -52,6 +58,7 @@ namespace Artemis.Modules.General.GeneralProfile UpdateDay(dataModel); UpdateKeyStates(dataModel); UpdateActiveWindow(dataModel); + UpdateAudio(dataModel); } #region Current Time @@ -67,8 +74,59 @@ namespace Artemis.Modules.General.GeneralProfile #endregion + #region Audio + + private MMDevice _defaultRecording; + private MMDevice _defaultPlayback; + + private void SetupAudio() + { + _defaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + _defaultRecording = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); + } + + private void AudioDeviceChanged(object sender, AudioDeviceChangedEventArgs e) + { + _defaultRecording = e.DefaultRecording; + _defaultPlayback = e.DefaultPlayback; + } + + private void UpdateAudio(GeneralProfileDataModel dataModel) + { + var recording = AudioMeterInformation.FromDevice(_defaultRecording); + var playback = AudioMeterInformation.FromDevice(_defaultPlayback); + + dataModel.Audio.Recording.OverallPeak = recording.PeakValue; + for (var i = 0; i < recording.GetChannelsPeakValues(recording.MeteringChannelCount).Length; i++) + { + // Only support up to 8 channels until lists are supported natively + if (i > 7) + break; + + var peakValue = recording.GetChannelsPeakValues(recording.MeteringChannelCount)[i]; + typeof(AudioDevice).GetProperty($"Channel{i + 1}Peak").SetValue(dataModel.Audio.Recording, peakValue); + } + + dataModel.Audio.Playback.OverallPeak = playback.PeakValue; + for (var i = 0; i < playback.GetChannelsPeakValues(playback.MeteringChannelCount).Length; i++) + { + // Only support up to 8 channels until lists are supported natively + if (i > 7) + break; + + var peakValue = playback.GetChannelsPeakValues(playback.MeteringChannelCount)[i]; + typeof(AudioDevice).GetProperty($"Channel{i + 1}Peak").SetValue(dataModel.Audio.Playback, peakValue); + } + } + + #endregion + #region CPU + private List _cores; + private int _cpuFrames; + private PerformanceCounter _overallCpu; + private void SetupCpu() { try diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs index cf7d34ee5..de8ccfa2a 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs @@ -1,65 +1,65 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Timers; -using Artemis.Events; -using CSCore.CoreAudioAPI; -using Ninject.Extensions.Logging; - -namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing -{ - public class AudioCaptureManager - { - private readonly List _audioCaptures; - private MMDevice _lastDefaultPlayback; - private MMDevice _lastDefaultRecording; - - public AudioCaptureManager(ILogger logger) - { - Logger = logger; - _audioCaptures = new List(); - _lastDefaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - _lastDefaultRecording = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); - - var defaultDeviceTimer = new Timer(1000); - defaultDeviceTimer.Elapsed += DefaultDeviceTimerOnElapsed; - defaultDeviceTimer.Start(); - } - - public event EventHandler AudioDeviceChanged; - - private void DefaultDeviceTimerOnElapsed(object sender, ElapsedEventArgs e) - { - var defaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - var defaultRecording = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); - - if (defaultPlayback.DeviceID == _lastDefaultPlayback.DeviceID && +using System; +using System.Collections.Generic; +using System.Linq; +using System.Timers; +using Artemis.Events; +using CSCore.CoreAudioAPI; +using Ninject.Extensions.Logging; + +namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing +{ + public class AudioCaptureManager + { + private readonly List _audioCaptures; + private MMDevice _lastDefaultPlayback; + private MMDevice _lastDefaultRecording; + + public AudioCaptureManager(ILogger logger) + { + Logger = logger; + _audioCaptures = new List(); + _lastDefaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + _lastDefaultRecording = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); + + var defaultDeviceTimer = new Timer(1000); + defaultDeviceTimer.Elapsed += DefaultDeviceTimerOnElapsed; + defaultDeviceTimer.Start(); + } + + public event EventHandler AudioDeviceChanged; + + private void DefaultDeviceTimerOnElapsed(object sender, ElapsedEventArgs e) + { + var defaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + var defaultRecording = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); + + if (defaultPlayback.DeviceID == _lastDefaultPlayback.DeviceID && defaultRecording.DeviceID == _lastDefaultRecording.DeviceID) - return; - - _lastDefaultPlayback = defaultPlayback; - _lastDefaultRecording = defaultRecording; - OnAudioDeviceChanged(new AudioDeviceChangedEventArgs(_lastDefaultPlayback, _lastDefaultRecording)); - } - - public AudioCapture GetAudioCapture(MMDevice device) - { - // Return existing audio capture if found - var audioCapture = _audioCaptures.FirstOrDefault(a => a.Device.DeviceID == device.DeviceID); - if (audioCapture != null) - return audioCapture; - - // Else create a new one and return that - var newAudioCapture = new AudioCapture(Logger, device); - _audioCaptures.Add(newAudioCapture); - return newAudioCapture; - } - - public ILogger Logger { get; set; } - - protected virtual void OnAudioDeviceChanged(AudioDeviceChangedEventArgs e) - { - AudioDeviceChanged?.Invoke(this, e); - } - } + return; + + _lastDefaultPlayback = defaultPlayback; + _lastDefaultRecording = defaultRecording; + OnAudioDeviceChanged(new AudioDeviceChangedEventArgs(_lastDefaultPlayback, _lastDefaultRecording)); + } + + public AudioCapture GetAudioCapture(MMDevice device) + { + // Return existing audio capture if found + var audioCapture = _audioCaptures.FirstOrDefault(a => a.Device.DeviceID == device.DeviceID); + if (audioCapture != null) + return audioCapture; + + // Else create a new one and return that + var newAudioCapture = new AudioCapture(Logger, device); + _audioCaptures.Add(newAudioCapture); + return newAudioCapture; + } + + public ILogger Logger { get; set; } + + protected virtual void OnAudioDeviceChanged(AudioDeviceChangedEventArgs e) + { + AudioDeviceChanged?.Invoke(this, e); + } + } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/ProfileModel.cs b/Artemis/Artemis/Profiles/ProfileModel.cs index 5dd3cbfed..75635ea2f 100644 --- a/Artemis/Artemis/Profiles/ProfileModel.cs +++ b/Artemis/Artemis/Profiles/ProfileModel.cs @@ -192,8 +192,7 @@ namespace Artemis.Profiles public void Activate(LuaManager luaManager) { - if (!Equals(luaManager.ProfileModel, this) || luaManager.ProfileModel.LuaScript != LuaScript) - luaManager.SetupLua(this); + luaManager.SetupLua(this); } public void Deactivate(LuaManager luaManager) diff --git a/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs index bfd4f67b2..7145f682a 100644 --- a/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs @@ -94,7 +94,7 @@ namespace Artemis.ViewModels return; try { - ProfileEditorModel.OpenLuaEditor(SelectedProfile); + ProfileEditorModel.OpenLuaEditor(_moduleModel); } catch (Exception e) { From 0cdf8242b785c16d5c9badc8bab36a8348add2eb Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sun, 15 Jan 2017 17:39:15 +0100 Subject: [PATCH 16/32] Update default profiles --- .../Resources/Keyboards/default-profiles.zip | Bin 422211 -> 434888 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Artemis/Artemis/Resources/Keyboards/default-profiles.zip b/Artemis/Artemis/Resources/Keyboards/default-profiles.zip index 4341a3e496637f49cd0d6e4dc31398283278297a..a675632a6fb9d7f03d39c03073bb0a56ddaff366 100644 GIT binary patch delta 190128 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 z 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⋙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

eiP#!-8Hr-KtL=qC7yD7a= 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

K?s2HNh4&CH7r$cjUUP=eGawv+9&I)c+*+IF9&^ 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 delta 177647 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

BL!%zRRs>*l5C@ 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 From 7323c657df7428cbd34dc1f132ab59da3cdb6fd7 Mon Sep 17 00:00:00 2001 From: Robert Beekman Date: Sun, 15 Jan 2017 17:44:00 +0100 Subject: [PATCH 17/32] Update FlyoutSettingsViewModel.cs --- .../Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Artemis/Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs b/Artemis/Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs index 1f9e8c4ad..fbce0198c 100644 --- a/Artemis/Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs +++ b/Artemis/Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs @@ -72,7 +72,11 @@ namespace Artemis.ViewModels.Flyouts { get { - return new BindableCollection(MainManager.DeviceManager.KeyboardProviders.Select(k => k.Name)); + return new BindableCollection(MainManager.DeviceManager.KeyboardProviders + .OrderBy(k => k.Name != "None") + .ThenBy(k => k.Name != "Corsair RGB Keyboard") + .ThenBy(k => k.Name) + .Select(k => k.Name)); } } @@ -244,4 +248,4 @@ namespace Artemis.ViewModels.Flyouts : GeneralSettings.LastKeyboard; } } -} \ No newline at end of file +} From e5f1a940de06ba2c177ef943ca3a1932196890a1 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sun, 15 Jan 2017 17:51:48 +0100 Subject: [PATCH 18/32] Show all layers in editor by default --- Artemis/Artemis/Artemis.csproj | 1 + Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 16e9663df..bc09abb0e 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -121,6 +121,7 @@ prompt MinimumRecommendedRules.ruleset true + true diff --git a/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs index 7145f682a..3a899343e 100644 --- a/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs @@ -64,6 +64,7 @@ namespace Artemis.ViewModels ProfileNames = new ObservableCollection(); Layers = new ObservableCollection(); ProfileEditorModel = profileEditorModel; + ShowAll = true; PropertyChanged += EditorStateHandler; _deviceManager.OnKeyboardChanged += DeviceManagerOnOnKeyboardChanged; From 6f3d00c8d43c0ed1b4bb626286766d17229412fb Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Tue, 17 Jan 2017 19:25:07 +0100 Subject: [PATCH 19/32] Further audio improvements, needs some trimming on CPU-usage --- Artemis/Artemis/Managers/LoopManager.cs | 18 +----------------- Artemis/Artemis/Managers/MainManager.cs | 10 ++++++++++ .../Artemis/Modules/Abstract/ModuleModel.cs | 3 ++- .../GeneralProfile/GeneralProfileDataModel.cs | 3 +-- .../GeneralProfile/GeneralProfileModel.cs | 3 ++- .../OverlayProfile/OverlayProfileModel.cs | 14 ++++++++------ .../Types/Audio/AudioCapturing/AudioCapture.cs | 3 +-- 7 files changed, 25 insertions(+), 29 deletions(-) diff --git a/Artemis/Artemis/Managers/LoopManager.cs b/Artemis/Artemis/Managers/LoopManager.cs index 06256803a..0066a1394 100644 --- a/Artemis/Artemis/Managers/LoopManager.cs +++ b/Artemis/Artemis/Managers/LoopManager.cs @@ -31,9 +31,6 @@ namespace Artemis.Managers _debugViewModel = debugViewModel; // Setup timers - //_loopTimer = new Timer(40); - //_loopTimer.Elapsed += LoopTimerOnElapsed; - //_loopTimer.Start(); _loopTask = Task.Factory.StartNew(ProcessLoop); _logger.Info("Intialized LoopManager"); } @@ -48,8 +45,6 @@ namespace Artemis.Managers public void Dispose() { _loopTask.Dispose(); - //_loopTimer.Stop(); - //_loopTimer.Dispose(); } private void ProcessLoop() @@ -72,20 +67,9 @@ namespace Artemis.Managers _logger.Warn(e, "Exception in render loop"); } } + // ReSharper disable once FunctionNeverReturns } - //private void LoopTimerOnElapsed(object sender, ElapsedEventArgs elapsedEventArgs) - //{ - // try - // { - // Render(); - // } - // catch (Exception e) - // { - // _logger.Warn(e, "Exception in render loop"); - // } - //} - public Task StartAsync() { return Task.Run(() => Start()); diff --git a/Artemis/Artemis/Managers/MainManager.cs b/Artemis/Artemis/Managers/MainManager.cs index f4de0de5f..7a3fc7635 100644 --- a/Artemis/Artemis/Managers/MainManager.cs +++ b/Artemis/Artemis/Managers/MainManager.cs @@ -110,6 +110,11 @@ namespace Artemis.Managers Logger.Debug("Enabling program"); ProgramEnabled = true; LoopManager.StartAsync(); + foreach (var overlayModule in ModuleManager.OverlayModules) + { + if (overlayModule.Settings.IsEnabled) + overlayModule.Enable(); + } RaiseEnabledChangedEvent(new EnabledChangedEventArgs(ProgramEnabled)); } @@ -119,6 +124,11 @@ namespace Artemis.Managers public void DisableProgram() { Logger.Debug("Disabling program"); + foreach (var overlayModule in ModuleManager.OverlayModules) + { + if (overlayModule.Settings.IsEnabled) + overlayModule.Dispose(); + } LoopManager.Stop(); ProgramEnabled = false; RaiseEnabledChangedEvent(new EnabledChangedEventArgs(ProgramEnabled)); diff --git a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs index 6734de719..7f4209637 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs @@ -98,7 +98,8 @@ namespace Artemis.Modules.Abstract return; ProfileModel = profileModel; - ProfileModel?.Activate(_luaManager); + if (!IsOverlay) + ProfileModel?.Activate(_luaManager); if (ProfileModel != null) { Settings.LastProfile = ProfileModel.Name; diff --git a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileDataModel.cs b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileDataModel.cs index 95d252fd4..70e9dcd46 100644 --- a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileDataModel.cs +++ b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileDataModel.cs @@ -31,8 +31,7 @@ namespace Artemis.Modules.General.GeneralProfile [MoonSharpUserData] public class Audio { - public double Volume { get; set; } - public double Peak { get; set; } + public float Volume { get; set; } public AudioDevice Recording { get; set; } = new AudioDevice(); public AudioDevice Playback { get; set; } = new AudioDevice(); } diff --git a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs index b407cc8d0..587741268 100644 --- a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs +++ b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs @@ -93,9 +93,10 @@ namespace Artemis.Modules.General.GeneralProfile private void UpdateAudio(GeneralProfileDataModel dataModel) { + var recording = AudioMeterInformation.FromDevice(_defaultRecording); var playback = AudioMeterInformation.FromDevice(_defaultPlayback); - + dataModel.Audio.Volume = AudioEndpointVolume.FromDevice(_defaultPlayback).GetMasterVolumeLevelScalar(); dataModel.Audio.Recording.OverallPeak = recording.PeakValue; for (var i = 0; i < recording.GetChannelsPeakValues(recording.MeteringChannelCount).Length; i++) { diff --git a/Artemis/Artemis/Modules/Overlays/OverlayProfile/OverlayProfileModel.cs b/Artemis/Artemis/Modules/Overlays/OverlayProfile/OverlayProfileModel.cs index c9882a1b5..349c7ed6a 100644 --- a/Artemis/Artemis/Modules/Overlays/OverlayProfile/OverlayProfileModel.cs +++ b/Artemis/Artemis/Modules/Overlays/OverlayProfile/OverlayProfileModel.cs @@ -8,12 +8,12 @@ namespace Artemis.Modules.Overlays.OverlayProfile { public class OverlayProfileModel : ModuleModel { - private readonly GeneralProfileModel _generalProfileModel; + private readonly GeneralProfileModel _generalProfileModule; public OverlayProfileModel(DeviceManager deviceManager, LuaManager luaManager, - [Named(nameof(GeneralProfileModel))] ModuleModel generalProfileModel) : base(deviceManager, luaManager) + [Named(nameof(GeneralProfileModel))] ModuleModel generalProfileModule) : base(deviceManager, luaManager) { - _generalProfileModel = (GeneralProfileModel) generalProfileModel; + _generalProfileModule = (GeneralProfileModel) generalProfileModule; Settings = SettingsProvider.Load(); DataModel = new OverlayProfileDataModel(); } @@ -24,9 +24,11 @@ namespace Artemis.Modules.Overlays.OverlayProfile public override void Update() { - // TODO: Find a clean way to update the parent profile model - ((OverlayProfileDataModel) DataModel).GeneralDataModel = - (GeneralProfileDataModel) _generalProfileModel.DataModel; + var dataModel = (OverlayProfileDataModel) DataModel; + + if (!_generalProfileModule.IsInitialized) + _generalProfileModule.Update(); + dataModel.GeneralDataModel = (GeneralProfileDataModel) _generalProfileModule.DataModel; } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs index 0cdeafc7c..179bd584e 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs @@ -32,10 +32,9 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing _volumeIndex = 0; _volumeTimer = new Timer(200); _volumeTimer.Elapsed += VolumeTimerOnElapsed; - Start(); } - + public ILogger Logger { get; } public MMDevice Device { get; } public double DesiredAverage { get; set; } From 201e65b3b535763e68764a083851e1e00775e9b4 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Wed, 18 Jan 2017 19:44:39 +0100 Subject: [PATCH 20/32] Performance improvements --- Artemis/Artemis/Artemis.csproj | 2 +- .../DeviceProviders/KeyboardProvider.cs | 8 +- .../Artemis/InjectionModules/BaseModules.cs | 4 +- .../InjectionModules/ManagerModules.cs | 1 + .../AudioCaptureManager.cs | 3 +- Artemis/Artemis/Managers/LoopManager.cs | 24 +++--- .../Artemis/Modules/Abstract/ModuleModel.cs | 4 +- .../GeneralProfile/GeneralProfileDataModel.cs | 10 +-- .../GeneralProfile/GeneralProfileModel.cs | 42 +++++----- .../Profiles/Layers/Models/TweenModel.cs | 76 ++++++++++++++----- .../Audio/AudioCapturing/AudioCapture.cs | 60 ++++++++++++++- .../Profiles/Layers/Types/Audio/AudioType.cs | 2 + Artemis/Artemis/Utilities/ImageUtilities.cs | 33 +++++--- 13 files changed, 175 insertions(+), 94 deletions(-) rename Artemis/Artemis/{Profiles/Layers/Types/Audio/AudioCapturing => Managers}/AudioCaptureManager.cs (94%) diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index bc09abb0e..4642e480e 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -503,7 +503,7 @@ - + diff --git a/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs b/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs index 59b4889fa..60b146fdb 100644 --- a/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs +++ b/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs @@ -27,17 +27,11 @@ namespace Artemis.DeviceProviders public abstract void Enable(); public abstract void DrawBitmap(Bitmap bitmap); - ///

- /// Returns a bitmap matching the keyboard's dimensions - /// - /// - public Bitmap KeyboardBitmap() => new Bitmap(Width, Height); - /// /// Returns a bitmap matching the keyboard's dimensions using the provided scale /// /// - public Bitmap KeyboardBitmap(int scale) => new Bitmap(Width*scale, Height*scale); + public Bitmap KeyboardBitmap(int scale = 4) => new Bitmap(Width*scale, Height*scale); public Rect KeyboardRectangle(int scale = 4) => new Rect(new Size(Width*scale, Height*scale)); diff --git a/Artemis/Artemis/InjectionModules/BaseModules.cs b/Artemis/Artemis/InjectionModules/BaseModules.cs index eb4bc0bad..7447fb67d 100644 --- a/Artemis/Artemis/InjectionModules/BaseModules.cs +++ b/Artemis/Artemis/InjectionModules/BaseModules.cs @@ -1,4 +1,5 @@ using Artemis.DeviceProviders; +using Artemis.Managers; using Artemis.Models; using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Interfaces; @@ -104,9 +105,6 @@ namespace Artemis.InjectionModules .InheritedFrom() .BindToSelf()); - // Type helpers - Bind().ToSelf().InSingletonScope(); - #endregion #region Lua diff --git a/Artemis/Artemis/InjectionModules/ManagerModules.cs b/Artemis/Artemis/InjectionModules/ManagerModules.cs index b01f7fab9..6f6b1c1b3 100644 --- a/Artemis/Artemis/InjectionModules/ManagerModules.cs +++ b/Artemis/Artemis/InjectionModules/ManagerModules.cs @@ -13,6 +13,7 @@ namespace Artemis.InjectionModules Bind().ToSelf().InSingletonScope(); Bind().ToSelf().InSingletonScope(); Bind().ToSelf().InSingletonScope(); + Bind().ToSelf().InSingletonScope(); } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs b/Artemis/Artemis/Managers/AudioCaptureManager.cs similarity index 94% rename from Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs rename to Artemis/Artemis/Managers/AudioCaptureManager.cs index de8ccfa2a..52e063838 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs +++ b/Artemis/Artemis/Managers/AudioCaptureManager.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using System.Linq; using System.Timers; using Artemis.Events; +using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; using CSCore.CoreAudioAPI; using Ninject.Extensions.Logging; -namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing +namespace Artemis.Managers { public class AudioCaptureManager { diff --git a/Artemis/Artemis/Managers/LoopManager.cs b/Artemis/Artemis/Managers/LoopManager.cs index 0066a1394..10500499a 100644 --- a/Artemis/Artemis/Managers/LoopManager.cs +++ b/Artemis/Artemis/Managers/LoopManager.cs @@ -52,8 +52,8 @@ namespace Artemis.Managers //TODO DarthAffe 14.01.2017: A stop-condition and a real cleanup instead of just aborting might be better while (true) { - try - { +// try +// { long preUpdateTicks = DateTime.Now.Ticks; Render(); @@ -61,11 +61,11 @@ namespace Artemis.Managers int sleep = (int)(40f - ((DateTime.Now.Ticks - preUpdateTicks) / 10000f)); if (sleep > 0) Thread.Sleep(sleep); - } - catch (Exception e) - { - _logger.Warn(e, "Exception in render loop"); - } +// } +// catch (Exception e) +// { +// _logger.Warn(e, "Exception in render loop"); +// } } // ReSharper disable once FunctionNeverReturns } @@ -207,19 +207,19 @@ namespace Artemis.Managers if (keyboard == null) return; - KeyboardBitmap = keyboard.KeyboardBitmap(4); + KeyboardBitmap = keyboard.KeyboardBitmap(); KeyboardBitmap.SetResolution(96, 96); - MouseBitmap = new Bitmap(40, 40); + MouseBitmap = new Bitmap(10, 10); MouseBitmap.SetResolution(96, 96); - HeadsetBitmap = new Bitmap(40, 40); + HeadsetBitmap = new Bitmap(10, 10); HeadsetBitmap.SetResolution(96, 96); - GenericBitmap = new Bitmap(40, 40); + GenericBitmap = new Bitmap(10, 10); GenericBitmap.SetResolution(96, 96); - MousematBitmap = new Bitmap(40, 40); + MousematBitmap = new Bitmap(10, 10); MousematBitmap.SetResolution(96, 96); using (var g = Graphics.FromImage(KeyboardBitmap)) diff --git a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs index 7f4209637..9320684b6 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs @@ -163,7 +163,7 @@ namespace Artemis.Modules.Abstract ProfileModel?.DrawLayers(g, layers, DrawType.Keyboard, DataModel, keyboardRect, preview); } // Render mice layer-by-layer - var devRec = new Rect(0, 0, 40, 40); + var devRec = new Rect(0, 0, 10, 10); using (var g = Graphics.FromImage(frame.MouseBitmap)) { ProfileModel?.DrawLayers(g, layers, DrawType.Mouse, DataModel, devRec, preview); @@ -183,7 +183,7 @@ namespace Artemis.Modules.Abstract { ProfileModel?.DrawLayers(g, layers, DrawType.Mousemat, DataModel, devRec, preview); } - + // Trace debugging if (DateTime.Now.AddSeconds(-2) <= _lastTrace || Logger == null) return; diff --git a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileDataModel.cs b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileDataModel.cs index 70e9dcd46..443be1c79 100644 --- a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileDataModel.cs +++ b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileDataModel.cs @@ -40,14 +40,8 @@ namespace Artemis.Modules.General.GeneralProfile public class AudioDevice { public float OverallPeak { get; set; } - public float Channel1Peak { get; set; } - public float Channel2Peak { get; set; } - public float Channel3Peak { get; set; } - public float Channel4Peak { get; set; } - public float Channel5Peak { get; set; } - public float Channel6Peak { get; set; } - public float Channel7Peak { get; set; } - public float Channel8Peak { get; set; } + public float LeftPeak { get; set; } + public float RightPeak { get; set; } } [MoonSharpUserData] diff --git a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs index 587741268..e0c0a5e60 100644 --- a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs +++ b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs @@ -10,7 +10,6 @@ using Artemis.DAL; using Artemis.Events; using Artemis.Managers; using Artemis.Modules.Abstract; -using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; using Artemis.Utilities; using CSCore.CoreAudioAPI; using Newtonsoft.Json; @@ -20,7 +19,6 @@ namespace Artemis.Modules.General.GeneralProfile { public class GeneralProfileModel : ModuleModel { - private readonly AudioCaptureManager _audioCaptureManager; private DateTime _lastMusicUpdate; private SpotifyLocalAPI _spotify; private bool _spotifySetupBusy; @@ -28,7 +26,6 @@ namespace Artemis.Modules.General.GeneralProfile public GeneralProfileModel(DeviceManager deviceManager, LuaManager luaManager, AudioCaptureManager audioCaptureManager) : base(deviceManager, luaManager) { - _audioCaptureManager = audioCaptureManager; _lastMusicUpdate = DateTime.Now; Settings = SettingsProvider.Load(); @@ -93,31 +90,26 @@ namespace Artemis.Modules.General.GeneralProfile private void UpdateAudio(GeneralProfileDataModel dataModel) { - - var recording = AudioMeterInformation.FromDevice(_defaultRecording); - var playback = AudioMeterInformation.FromDevice(_defaultPlayback); + // Update microphone, only bother with OverallPeak + if (_defaultRecording != null) + { + var recording = AudioMeterInformation.FromDevice(_defaultRecording); + dataModel.Audio.Recording.OverallPeak = recording.PeakValue; + } + + if (_defaultPlayback == null) + return; + + // Update volume if a default device is found dataModel.Audio.Volume = AudioEndpointVolume.FromDevice(_defaultPlayback).GetMasterVolumeLevelScalar(); - dataModel.Audio.Recording.OverallPeak = recording.PeakValue; - for (var i = 0; i < recording.GetChannelsPeakValues(recording.MeteringChannelCount).Length; i++) - { - // Only support up to 8 channels until lists are supported natively - if (i > 7) - break; - - var peakValue = recording.GetChannelsPeakValues(recording.MeteringChannelCount)[i]; - typeof(AudioDevice).GetProperty($"Channel{i + 1}Peak").SetValue(dataModel.Audio.Recording, peakValue); - } + // Update speakers, only do overall, left and right for now + // TODO: When adding list support lets do all channels + var playback = AudioMeterInformation.FromDevice(_defaultPlayback); + var peakValues = playback.GetChannelsPeakValues(); dataModel.Audio.Playback.OverallPeak = playback.PeakValue; - for (var i = 0; i < playback.GetChannelsPeakValues(playback.MeteringChannelCount).Length; i++) - { - // Only support up to 8 channels until lists are supported natively - if (i > 7) - break; - - var peakValue = playback.GetChannelsPeakValues(playback.MeteringChannelCount)[i]; - typeof(AudioDevice).GetProperty($"Channel{i + 1}Peak").SetValue(dataModel.Audio.Playback, peakValue); - } + dataModel.Audio.Playback.LeftPeak = peakValues[0]; + dataModel.Audio.Playback.LeftPeak = peakValues[1]; } #endregion diff --git a/Artemis/Artemis/Profiles/Layers/Models/TweenModel.cs b/Artemis/Artemis/Profiles/Layers/Models/TweenModel.cs index 29e679164..70bb2b63f 100644 --- a/Artemis/Artemis/Profiles/Layers/Models/TweenModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Models/TweenModel.cs @@ -24,51 +24,87 @@ namespace Artemis.Profiles.Layers.Models _yTweener = new Tweener((float) layerModel.Y, (float) layerModel.Y, 0); _widthTweener = new Tweener((float) layerModel.Width, (float) layerModel.Width, 0); _heightTweener = new Tweener((float) layerModel.Height, (float) layerModel.Height, 0); - _opacityTweener = new Tweener((float) layerModel.Opacity, (float) layerModel.Opacity, 0); - - StoreCurrentValues(); + _opacityTweener = new Tweener((float) layerModel.Opacity, (float) layerModel.Opacity, 0); + + _x = (float)_layerModel.X; + _y = (float)_layerModel.Y; + _width = (float)_layerModel.Width; + _height = (float)_layerModel.Height; + _opacity = (float)_layerModel.Opacity; } public void Update() { - // Width + UpdateWidth(); + UpdateHeight(); + UpdateOpacity(); + } + + private void UpdateWidth() + { + if (Math.Abs(_layerModel.Properties.WidthEaseTime) < 0.001) + return; + + // Width if (Math.Abs(_layerModel.Width - _width) > 0.001) { var widthFunc = GetEaseFunction(_layerModel.Properties.WidthEase); var widthSpeed = _layerModel.Properties.WidthEaseTime; - _xTweener = new Tweener(_xTweener.Value, (float) _layerModel.X, widthSpeed, widthFunc); - _widthTweener = new Tweener(_widthTweener.Value, (float) _layerModel.Width, widthSpeed, widthFunc); - } + _xTweener = new Tweener(_xTweener.Value, (float)_layerModel.X, widthSpeed, widthFunc); + _widthTweener = new Tweener(_widthTweener.Value, (float)_layerModel.Width, widthSpeed, widthFunc); + } + + _xTweener.Update(40); + _widthTweener.Update(40); + + _x = (float) _layerModel.X; + _width = (float)_layerModel.Width; + + _layerModel.X = _xTweener.Value; + _layerModel.Width = _widthTweener.Value; + } + + private void UpdateHeight() + { + if (Math.Abs(_layerModel.Properties.HeightEaseTime) < 0.001) + return; // Height if (Math.Abs(_layerModel.Height - _height) > 0.001) { var heightFunc = GetEaseFunction(_layerModel.Properties.HeightEase); var heightSpeed = _layerModel.Properties.HeightEaseTime; - _yTweener = new Tweener(_y, (float) _layerModel.Y, heightSpeed, heightFunc); - _heightTweener = new Tweener(_height, (float) _layerModel.Height, heightSpeed, heightFunc); - } + _yTweener = new Tweener(_y, (float)_layerModel.Y, heightSpeed, heightFunc); + _heightTweener = new Tweener(_height, (float)_layerModel.Height, heightSpeed, heightFunc); + } + + _yTweener.Update(40); + _heightTweener.Update(40); + + _y = (float)_layerModel.Y; + _height = (float)_layerModel.Height; + + _layerModel.Y = _yTweener.Value; + _layerModel.Height = _heightTweener.Value; + } + + private void UpdateOpacity() + { + if (Math.Abs(_layerModel.Properties.OpacityEaseTime) < 0.001) + return; // Opacity if (Math.Abs(_layerModel.Opacity - _opacity) > 0.001) { - _opacityTweener = new Tweener(_opacity, (float) _layerModel.Opacity, + _opacityTweener = new Tweener(_opacity, (float)_layerModel.Opacity, _layerModel.Properties.OpacityEaseTime, GetEaseFunction(_layerModel.Properties.OpacityEase)); } - _xTweener.Update(40); - _yTweener.Update(40); - _widthTweener.Update(40); - _heightTweener.Update(40); _opacityTweener.Update(40); - StoreCurrentValues(); + _opacity = (float)_layerModel.Opacity; - _layerModel.X = _xTweener.Value; - _layerModel.Y = _yTweener.Value; - _layerModel.Width = _widthTweener.Value; - _layerModel.Height = _heightTweener.Value; _layerModel.Opacity = _opacityTweener.Value; } diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs index 179bd584e..29d488632 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs @@ -12,9 +12,11 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing { public class AudioCapture { - private const FftSize FftSize = CSCore.DSP.FftSize.Fft4096; + private const FftSize FftSize = CSCore.DSP.FftSize.Fft1024; private readonly Timer _volumeTimer; private readonly double[] _volumeValues; + private readonly Timer _disableTimer; + private bool _mayStop; private SingleSpectrum _singleSpectrum; private WasapiLoopbackCapture _soundIn; private GainSource _source; @@ -30,11 +32,12 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing _volumeValues = new double[5]; _volumeIndex = 0; + _disableTimer = new Timer(1000); + _disableTimer.Elapsed += CheckStop; _volumeTimer = new Timer(200); _volumeTimer.Elapsed += VolumeTimerOnElapsed; - Start(); } - + public ILogger Logger { get; } public MMDevice Device { get; } public double DesiredAverage { get; set; } @@ -45,6 +48,8 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing set { _volume.Volume = value; } } + public bool Running { get; set; } + private void VolumeTimerOnElapsed(object sender, ElapsedEventArgs e) { if (Volume <= 0) @@ -100,12 +105,36 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing }; } + /// + /// Keeps the audio capture active, when not called for longer than 1 sec the capture will + /// stop capturing until Pulse is called again + /// + public void Pulse() + { + _mayStop = false; + if (!Running) + Start(); + } + + private void CheckStop(object sender, ElapsedEventArgs e) + { + if (_mayStop) + { + Logger.Debug("Stopping idle audio capture for device: {0}", Device?.FriendlyName ?? "default"); + Stop(); + } + else + _mayStop = true; + } + private void Start() { Logger.Debug("Starting audio capture for device: {0}", Device?.FriendlyName ?? "default"); try { + Stop(); + _soundIn = new WasapiLoopbackCapture(); _soundIn.Initialize(); // Not sure if this null check is needed but doesnt hurt @@ -135,14 +164,39 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing }; _singleSpectrum = new SingleSpectrum(FftSize, _spectrumProvider); + _mayStop = false; + _disableTimer.Start(); _volumeTimer.Start(); _soundIn.Start(); + + Running = true; } catch (Exception e) { Logger.Warn(e, "Failed to start WASAPI audio capture"); } } + + private void Stop() + { + Running = false; + + + if (_soundIn != null) + { + _soundIn.Stop(); + _soundIn.Dispose(); + _soundIn = null; + } + if (_source != null) + { + _source.Dispose(); + _source = null; + } + + _disableTimer.Stop(); + _volumeTimer.Stop(); + } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index 2e52bff3c..f7e0a5c16 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Windows; using System.Windows.Media; using Artemis.Events; +using Artemis.Managers; using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Animations; @@ -122,6 +123,7 @@ namespace Artemis.Profiles.Layers.Types.Audio public void Update(LayerModel layerModel, ModuleDataModel dataModel, bool isPreview = false) { layerModel.ApplyProperties(true); + _audioCapture.Pulse(); var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; diff --git a/Artemis/Artemis/Utilities/ImageUtilities.cs b/Artemis/Artemis/Utilities/ImageUtilities.cs index d8ac1122d..ad33435cf 100644 --- a/Artemis/Artemis/Utilities/ImageUtilities.cs +++ b/Artemis/Artemis/Utilities/ImageUtilities.cs @@ -4,11 +4,15 @@ using System.IO; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; +using PixelFormat = System.Drawing.Imaging.PixelFormat; +using Point = System.Drawing.Point; namespace Artemis.Utilities { public class ImageUtilities { + private static RenderTargetBitmap _rBmp; + /// /// Resize the image to the specified width and height. /// @@ -56,21 +60,26 @@ namespace Artemis.Utilities public static Bitmap DrawingVisualToBitmap(DrawingVisual visual, Rect rect) { - // TODO: Improve performance by dividing by 4 here - var bmp = new RenderTargetBitmap((int) rect.Width, (int) rect.Height, 96, 96, PixelFormats.Pbgra32); - bmp.Render(visual); + var width = (int) rect.Width; + var height = (int) rect.Height; - var encoder = new BmpBitmapEncoder(); - encoder.Frames.Add(BitmapFrame.Create(bmp)); + // RenderTargetBitmap construction is expensive, only do it when needed + if (_rBmp?.PixelHeight != height || _rBmp?.PixelWidth != width) + _rBmp = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32); - Bitmap bitmap; - using (var stream = new MemoryStream()) - { - encoder.Save(stream); - bitmap = new Bitmap(stream); - } + _rBmp.Render(visual); + return GetBitmap(_rBmp); + } - return bitmap; + private static Bitmap GetBitmap(BitmapSource source) + { + var bmp = new Bitmap(source.PixelWidth, source.PixelHeight, PixelFormat.Format32bppPArgb); + bmp.SetResolution(96, 96); + var data = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.WriteOnly, + PixelFormat.Format32bppPArgb); + source.CopyPixels(Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride); + bmp.UnlockBits(data); + return bmp; } /// From 039593c6fe10245142639cd02af194f1f4167a70 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Thu, 19 Jan 2017 12:54:48 +0100 Subject: [PATCH 21/32] Clean up tweening --- .../Profiles/Layers/Models/TweenModel.cs | 233 +++++++++--------- 1 file changed, 112 insertions(+), 121 deletions(-) diff --git a/Artemis/Artemis/Profiles/Layers/Models/TweenModel.cs b/Artemis/Artemis/Profiles/Layers/Models/TweenModel.cs index 70bb2b63f..9f180fb87 100644 --- a/Artemis/Artemis/Profiles/Layers/Models/TweenModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Models/TweenModel.cs @@ -1,135 +1,126 @@ -using System; -using Betwixt; - -namespace Artemis.Profiles.Layers.Models -{ - public class TweenModel - { - private readonly LayerModel _layerModel; - private Tweener _xTweener; - private Tweener _yTweener; - private float _width; - private Tweener _widthTweener; - private float _height; - private Tweener _heightTweener; - private float _opacity; - private Tweener _opacityTweener; - private float _x; - private float _y; - - public TweenModel(LayerModel layerModel) - { - _layerModel = layerModel; - _xTweener = new Tweener((float) layerModel.X, (float) layerModel.X, 0); - _yTweener = new Tweener((float) layerModel.Y, (float) layerModel.Y, 0); - _widthTweener = new Tweener((float) layerModel.Width, (float) layerModel.Width, 0); - _heightTweener = new Tweener((float) layerModel.Height, (float) layerModel.Height, 0); +using System; +using Betwixt; + +namespace Artemis.Profiles.Layers.Models +{ + public class TweenModel + { + private readonly LayerModel _layerModel; + private Tweener _xTweener; + private Tweener _yTweener; + private float _width; + private Tweener _widthTweener; + private float _height; + private Tweener _heightTweener; + private float _opacity; + private Tweener _opacityTweener; + private float _x; + private float _y; + + public TweenModel(LayerModel layerModel) + { + _layerModel = layerModel; + _xTweener = new Tweener((float) layerModel.X, (float) layerModel.X, 0); + _yTweener = new Tweener((float) layerModel.Y, (float) layerModel.Y, 0); + _widthTweener = new Tweener((float) layerModel.Width, (float) layerModel.Width, 0); + _heightTweener = new Tweener((float) layerModel.Height, (float) layerModel.Height, 0); _opacityTweener = new Tweener((float) layerModel.Opacity, (float) layerModel.Opacity, 0); - _x = (float)_layerModel.X; - _y = (float)_layerModel.Y; - _width = (float)_layerModel.Width; - _height = (float)_layerModel.Height; - _opacity = (float)_layerModel.Opacity; - } - - public void Update() - { - UpdateWidth(); - UpdateHeight(); - UpdateOpacity(); - } - - private void UpdateWidth() + _x = (float)_layerModel.X; + _y = (float)_layerModel.Y; + _width = (float)_layerModel.Width; + _height = (float)_layerModel.Height; + _opacity = (float)_layerModel.Opacity; + } + + public void Update() { - if (Math.Abs(_layerModel.Properties.WidthEaseTime) < 0.001) + UpdateWidth(); + UpdateHeight(); + UpdateOpacity(); + } + + private void UpdateWidth() + { + if (_layerModel.Properties.WidthEaseTime < 0.001) return; // Width - if (Math.Abs(_layerModel.Width - _width) > 0.001) - { - var widthFunc = GetEaseFunction(_layerModel.Properties.WidthEase); - var widthSpeed = _layerModel.Properties.WidthEaseTime; - - _xTweener = new Tweener(_xTweener.Value, (float)_layerModel.X, widthSpeed, widthFunc); - _widthTweener = new Tweener(_widthTweener.Value, (float)_layerModel.Width, widthSpeed, widthFunc); + if (Math.Abs(_layerModel.Width - _width) > 0.001) + { + var widthFunc = GetEaseFunction(_layerModel.Properties.WidthEase); + var widthSpeed = _layerModel.Properties.WidthEaseTime; + + _xTweener = new Tweener(_xTweener.Value, (float)_layerModel.X, widthSpeed, widthFunc); + _widthTweener = new Tweener(_widthTweener.Value, (float)_layerModel.Width, widthSpeed, widthFunc); } _xTweener.Update(40); - _widthTweener.Update(40); - - _x = (float) _layerModel.X; - _width = (float)_layerModel.Width; - - _layerModel.X = _xTweener.Value; - _layerModel.Width = _widthTweener.Value; - } - - private void UpdateHeight() + _widthTweener.Update(40); + + _x = (float) _layerModel.X; + _width = (float)_layerModel.Width; + + _layerModel.X = _xTweener.Value; + _layerModel.Width = _widthTweener.Value; + } + + private void UpdateHeight() { - if (Math.Abs(_layerModel.Properties.HeightEaseTime) < 0.001) - return; - - // Height - if (Math.Abs(_layerModel.Height - _height) > 0.001) - { - var heightFunc = GetEaseFunction(_layerModel.Properties.HeightEase); - var heightSpeed = _layerModel.Properties.HeightEaseTime; - _yTweener = new Tweener(_y, (float)_layerModel.Y, heightSpeed, heightFunc); - _heightTweener = new Tweener(_height, (float)_layerModel.Height, heightSpeed, heightFunc); + if (_layerModel.Properties.HeightEaseTime < 0.001) + return; + + // Height + if (Math.Abs(_layerModel.Height - _height) > 0.001) + { + var heightFunc = GetEaseFunction(_layerModel.Properties.HeightEase); + var heightSpeed = _layerModel.Properties.HeightEaseTime; + _yTweener = new Tweener(_y, (float)_layerModel.Y, heightSpeed, heightFunc); + _heightTweener = new Tweener(_height, (float)_layerModel.Height, heightSpeed, heightFunc); } - _yTweener.Update(40); - _heightTweener.Update(40); - - _y = (float)_layerModel.Y; - _height = (float)_layerModel.Height; - - _layerModel.Y = _yTweener.Value; - _layerModel.Height = _heightTweener.Value; - } - - private void UpdateOpacity() + _yTweener.Update(40); + _heightTweener.Update(40); + + _y = (float)_layerModel.Y; + _height = (float)_layerModel.Height; + + _layerModel.Y = _yTweener.Value; + _layerModel.Height = _heightTweener.Value; + } + + private void UpdateOpacity() { - if (Math.Abs(_layerModel.Properties.OpacityEaseTime) < 0.001) - return; - - // Opacity - if (Math.Abs(_layerModel.Opacity - _opacity) > 0.001) - { - _opacityTweener = new Tweener(_opacity, (float)_layerModel.Opacity, - _layerModel.Properties.OpacityEaseTime, GetEaseFunction(_layerModel.Properties.OpacityEase)); - } - - _opacityTweener.Update(40); - - _opacity = (float)_layerModel.Opacity; - - _layerModel.Opacity = _opacityTweener.Value; - } - - private void StoreCurrentValues() - { - _x = (float) _layerModel.X; - _y = (float) _layerModel.Y; - _width = (float) _layerModel.Width; - _height = (float) _layerModel.Height; - _opacity = (float) _layerModel.Opacity; - } - - private static EaseFunc GetEaseFunction(string functionName) - { - switch (functionName) - { - case "In": - return Ease.Quint.In; - case "Out": - return Ease.Quint.Out; - case "In/out": - return Ease.Quint.InOut; - default: - return Ease.Linear; - } - } - } + if (_layerModel.Properties.OpacityEaseTime < 0.001) + return; + + // Opacity + if (Math.Abs(_layerModel.Opacity - _opacity) > 0.001) + { + _opacityTweener = new Tweener(_opacity, (float)_layerModel.Opacity, + _layerModel.Properties.OpacityEaseTime, GetEaseFunction(_layerModel.Properties.OpacityEase)); + } + + _opacityTweener.Update(40); + + _opacity = (float)_layerModel.Opacity; + + _layerModel.Opacity = _opacityTweener.Value; + } + + private static EaseFunc GetEaseFunction(string functionName) + { + switch (functionName) + { + case "In": + return Ease.Quint.In; + case "Out": + return Ease.Quint.Out; + case "In/out": + return Ease.Quint.InOut; + default: + return Ease.Linear; + } + } + } } \ No newline at end of file From d7bf26a73844bce3e1c3104703e774aff7e2a03d Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Thu, 19 Jan 2017 14:50:58 +0100 Subject: [PATCH 22/32] Don't render device types that aren't usable --- Artemis/Artemis/Managers/LoopManager.cs | 73 ++-- .../Artemis/Modules/Abstract/ModuleModel.cs | 30 +- .../GeneralProfile/GeneralProfileModel.cs | 17 +- .../Profiles/Layers/Types/Audio/AudioType.cs | 398 +++++++++--------- Artemis/Artemis/ViewModels/DebugViewModel.cs | 25 +- 5 files changed, 286 insertions(+), 257 deletions(-) diff --git a/Artemis/Artemis/Managers/LoopManager.cs b/Artemis/Artemis/Managers/LoopManager.cs index 10500499a..85e66818f 100644 --- a/Artemis/Artemis/Managers/LoopManager.cs +++ b/Artemis/Artemis/Managers/LoopManager.cs @@ -52,8 +52,8 @@ namespace Artemis.Managers //TODO DarthAffe 14.01.2017: A stop-condition and a real cleanup instead of just aborting might be better while (true) { -// try -// { + try + { long preUpdateTicks = DateTime.Now.Ticks; Render(); @@ -61,11 +61,11 @@ namespace Artemis.Managers int sleep = (int)(40f - ((DateTime.Now.Ticks - preUpdateTicks) / 10000f)); if (sleep > 0) Thread.Sleep(sleep); -// } -// catch (Exception e) -// { -// _logger.Warn(e, "Exception in render loop"); -// } + } + catch (Exception e) + { + _logger.Warn(e, "Exception in render loop"); + } } // ReSharper disable once FunctionNeverReturns } @@ -157,10 +157,11 @@ namespace Artemis.Managers var headsets = _deviceManager.HeadsetProviders.Where(m => m.CanUse).ToList(); var generics = _deviceManager.GenericProviders.Where(m => m.CanUse).ToList(); var mousemats = _deviceManager.MousematProviders.Where(m => m.CanUse).ToList(); + var keyboardOnly = !mice.Any() && !headsets.Any() && !generics.Any() && !mousemats.Any(); // Setup the frame for this tick - using (var frame = new RenderFrame(_deviceManager.ActiveKeyboard)) + using (var frame = new RenderFrame(_deviceManager.ActiveKeyboard, mice.Any(), headsets.Any(), generics.Any(), mousemats.Any())) { if (renderModule.IsInitialized) renderModule.Render(frame, keyboardOnly); @@ -202,45 +203,53 @@ namespace Artemis.Managers public class RenderFrame : IDisposable { - public RenderFrame(KeyboardProvider keyboard) - { + public RenderFrame(KeyboardProvider keyboard, bool renderMice, bool renderHeadsets, bool renderGenerics, bool renderMousemats) + { if (keyboard == null) return; KeyboardBitmap = keyboard.KeyboardBitmap(); - KeyboardBitmap.SetResolution(96, 96); - - MouseBitmap = new Bitmap(10, 10); - MouseBitmap.SetResolution(96, 96); - - HeadsetBitmap = new Bitmap(10, 10); - HeadsetBitmap.SetResolution(96, 96); - - GenericBitmap = new Bitmap(10, 10); - GenericBitmap.SetResolution(96, 96); - - MousematBitmap = new Bitmap(10, 10); - MousematBitmap.SetResolution(96, 96); - + KeyboardBitmap.SetResolution(96, 96); using (var g = Graphics.FromImage(KeyboardBitmap)) { g.Clear(Color.Black); } - using (var g = Graphics.FromImage(MouseBitmap)) + + if (renderMice) { - g.Clear(Color.Black); + MouseBitmap = new Bitmap(10, 10); + MouseBitmap.SetResolution(96, 96); + using (var g = Graphics.FromImage(MouseBitmap)) + { + g.Clear(Color.Black); + } } - using (var g = Graphics.FromImage(HeadsetBitmap)) + if (renderHeadsets) { - g.Clear(Color.Black); + HeadsetBitmap = new Bitmap(10, 10); + HeadsetBitmap.SetResolution(96, 96); + using (var g = Graphics.FromImage(HeadsetBitmap)) + { + g.Clear(Color.Black); + } } - using (var g = Graphics.FromImage(GenericBitmap)) + if (renderGenerics) { - g.Clear(Color.Black); + GenericBitmap = new Bitmap(10, 10); + GenericBitmap.SetResolution(96, 96); + using (var g = Graphics.FromImage(GenericBitmap)) + { + g.Clear(Color.Black); + } } - using (var g = Graphics.FromImage(MousematBitmap)) + if (renderMousemats) { - g.Clear(Color.Black); + MousematBitmap = new Bitmap(10, 10); + MousematBitmap.SetResolution(96, 96); + using (var g = Graphics.FromImage(MousematBitmap)) + { + g.Clear(Color.Black); + } } } diff --git a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs index 9320684b6..299421c01 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs @@ -164,26 +164,38 @@ namespace Artemis.Modules.Abstract } // Render mice layer-by-layer var devRec = new Rect(0, 0, 10, 10); - using (var g = Graphics.FromImage(frame.MouseBitmap)) + if (frame.MouseBitmap != null) { - ProfileModel?.DrawLayers(g, layers, DrawType.Mouse, DataModel, devRec, preview); + using (var g = Graphics.FromImage(frame.MouseBitmap)) + { + ProfileModel?.DrawLayers(g, layers, DrawType.Mouse, DataModel, devRec, preview); + } } // Render headsets layer-by-layer - using (var g = Graphics.FromImage(frame.HeadsetBitmap)) + if (frame.HeadsetBitmap != null) { - ProfileModel?.DrawLayers(g, layers, DrawType.Headset, DataModel, devRec, preview); + using (var g = Graphics.FromImage(frame.HeadsetBitmap)) + { + ProfileModel?.DrawLayers(g, layers, DrawType.Headset, DataModel, devRec, preview); + } } // Render generic devices layer-by-layer - using (var g = Graphics.FromImage(frame.GenericBitmap)) + if (frame.GenericBitmap != null) { - ProfileModel?.DrawLayers(g, layers, DrawType.Generic, DataModel, devRec, preview); + using (var g = Graphics.FromImage(frame.GenericBitmap)) + { + ProfileModel?.DrawLayers(g, layers, DrawType.Generic, DataModel, devRec, preview); + } } // Render mousemats layer-by-layer - using (var g = Graphics.FromImage(frame.MousematBitmap)) + if (frame.MousematBitmap != null) { - ProfileModel?.DrawLayers(g, layers, DrawType.Mousemat, DataModel, devRec, preview); + using (var g = Graphics.FromImage(frame.MousematBitmap)) + { + ProfileModel?.DrawLayers(g, layers, DrawType.Mousemat, DataModel, devRec, preview); + } } - + // Trace debugging if (DateTime.Now.AddSeconds(-2) <= _lastTrace || Logger == null) return; diff --git a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs index e0c0a5e60..ecb0e0873 100644 --- a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs +++ b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs @@ -75,17 +75,23 @@ namespace Artemis.Modules.General.GeneralProfile private MMDevice _defaultRecording; private MMDevice _defaultPlayback; + private AudioMeterInformation _recordingInfo; + private AudioMeterInformation _playbackInfo; private void SetupAudio() { - _defaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); _defaultRecording = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); + _recordingInfo = AudioMeterInformation.FromDevice(_defaultRecording); + _defaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + _playbackInfo = AudioMeterInformation.FromDevice(_defaultPlayback); } private void AudioDeviceChanged(object sender, AudioDeviceChangedEventArgs e) { _defaultRecording = e.DefaultRecording; + _recordingInfo = AudioMeterInformation.FromDevice(_defaultRecording); _defaultPlayback = e.DefaultPlayback; + _playbackInfo = AudioMeterInformation.FromDevice(_defaultPlayback); } private void UpdateAudio(GeneralProfileDataModel dataModel) @@ -93,8 +99,8 @@ namespace Artemis.Modules.General.GeneralProfile // Update microphone, only bother with OverallPeak if (_defaultRecording != null) { - var recording = AudioMeterInformation.FromDevice(_defaultRecording); - dataModel.Audio.Recording.OverallPeak = recording.PeakValue; + + dataModel.Audio.Recording.OverallPeak = _recordingInfo.PeakValue; } if (_defaultPlayback == null) @@ -105,9 +111,8 @@ namespace Artemis.Modules.General.GeneralProfile // Update speakers, only do overall, left and right for now // TODO: When adding list support lets do all channels - var playback = AudioMeterInformation.FromDevice(_defaultPlayback); - var peakValues = playback.GetChannelsPeakValues(); - dataModel.Audio.Playback.OverallPeak = playback.PeakValue; + var peakValues = _playbackInfo.GetChannelsPeakValues(); + dataModel.Audio.Playback.OverallPeak = _playbackInfo.PeakValue; dataModel.Audio.Playback.LeftPeak = peakValues[0]; dataModel.Audio.Playback.LeftPeak = peakValues[1]; } diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index f7e0a5c16..56da983ab 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -1,202 +1,202 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using System.Windows; -using System.Windows.Media; -using Artemis.Events; -using Artemis.Managers; -using Artemis.Modules.Abstract; -using Artemis.Profiles.Layers.Abstract; -using Artemis.Profiles.Layers.Animations; -using Artemis.Profiles.Layers.Interfaces; -using Artemis.Profiles.Layers.Models; -using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; -using Artemis.Properties; -using Artemis.Utilities; -using Artemis.ViewModels; -using Artemis.ViewModels.Profiles; -using CSCore.CoreAudioAPI; - -namespace Artemis.Profiles.Layers.Types.Audio -{ - public class AudioType : ILayerType - { - private readonly AudioCaptureManager _audioCaptureManager; - private const GeometryCombineMode CombineMode = GeometryCombineMode.Union; - private AudioCapture _audioCapture; - private int _lines; - private LineSpectrum _lineSpectrum; - private List _lineValues; - private int _drawCount; - - public AudioType(AudioCaptureManager audioCaptureManager) - { - _audioCaptureManager = audioCaptureManager; - - // TODO: Setup according to settings - _audioCapture = _audioCaptureManager.GetAudioCapture(MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia)); - _audioCaptureManager.AudioDeviceChanged += OnAudioDeviceChanged; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Windows; +using System.Windows.Media; +using Artemis.Events; +using Artemis.Managers; +using Artemis.Modules.Abstract; +using Artemis.Profiles.Layers.Abstract; +using Artemis.Profiles.Layers.Animations; +using Artemis.Profiles.Layers.Interfaces; +using Artemis.Profiles.Layers.Models; +using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; +using Artemis.Properties; +using Artemis.Utilities; +using Artemis.ViewModels; +using Artemis.ViewModels.Profiles; +using CSCore.CoreAudioAPI; + +namespace Artemis.Profiles.Layers.Types.Audio +{ + public class AudioType : ILayerType + { + private readonly AudioCaptureManager _audioCaptureManager; + private const GeometryCombineMode CombineMode = GeometryCombineMode.Union; + private AudioCapture _audioCapture; + private int _lines; + private LineSpectrum _lineSpectrum; + private List _lineValues; + private int _drawCount; + + public AudioType(AudioCaptureManager audioCaptureManager) + { + _audioCaptureManager = audioCaptureManager; + + // TODO: Setup according to settings + _audioCapture = _audioCaptureManager.GetAudioCapture(MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia)); + _audioCaptureManager.AudioDeviceChanged += OnAudioDeviceChanged; } - private void OnAudioDeviceChanged(object sender, AudioDeviceChangedEventArgs e) - { - // TODO: Check if layer must use default - // TODO: Check recording type - _audioCapture = _audioCaptureManager.GetAudioCapture(e.DefaultPlayback); - _lines = 0; - } - - public string Name => "Keyboard - Audio visualization"; - public bool ShowInEdtor => true; - public DrawType DrawType => DrawType.Keyboard; - - public ImageSource DrawThumbnail(LayerModel layer) - { - var thumbnailRect = new Rect(0, 0, 18, 18); - var visual = new DrawingVisual(); - using (var c = visual.RenderOpen()) - { - c.DrawImage(ImageUtilities.BitmapToBitmapImage(Resources.audio), thumbnailRect); - } - - var image = new DrawingImage(visual.Drawing); - return image; - } - - public void Draw(LayerModel layerModel, DrawingContext c) - { - _drawCount++; - if (_lineValues == null) - return; - - var parentX = layerModel.X; - var parentY = layerModel.Y; - var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; - - // Create a geometry that will be formed by all the bars - Geometry barGeometry = new RectangleGeometry(); - - switch (direction) - { - case Direction.TopToBottom: - for (var index = 0; index < _lineValues.Count; index++) - { - var clipRect = new Rect((parentX + index)*4, parentY*4, 4, _lineValues[index]*4); - var barRect = new RectangleGeometry(clipRect); - barGeometry = Geometry.Combine(barGeometry, barRect, CombineMode, Transform.Identity); - } - break; - case Direction.BottomToTop: - for (var index = 0; index < _lineValues.Count; index++) - { - var clipRect = new Rect((parentX + index)*4, parentY*4, 4, _lineValues[index]*4); - clipRect.Y = clipRect.Y + layerModel.Height*4 - clipRect.Height; - var barRect = new RectangleGeometry(clipRect); - barGeometry = Geometry.Combine(barGeometry, barRect, CombineMode, Transform.Identity); - } - break; - case Direction.LeftToRight: - for (var index = 0; index < _lineValues.Count; index++) - { - var clipRect = new Rect((parentX + index)*4, parentY*4, 4, _lineValues[index]*4); - var barRect = new RectangleGeometry(clipRect); - barGeometry = Geometry.Combine(barGeometry, barRect, CombineMode, Transform.Identity); - } - break; - default: - for (var index = 0; index < _lineValues.Count; index++) - { - var clipRect = new Rect((parentX + index)*4, parentY*4, 4, _lineValues[index]*4); - var barRect = new RectangleGeometry(clipRect); - barGeometry = Geometry.Combine(barGeometry, barRect, CombineMode, Transform.Identity); - } - break; - } - - // Push the created geometry - c.PushClip(barGeometry); - BrushDraw(layerModel, c); - c.Pop(); - } - - public void Update(LayerModel layerModel, ModuleDataModel dataModel, bool isPreview = false) - { - layerModel.ApplyProperties(true); - _audioCapture.Pulse(); - - var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; - - int currentLines; - double currentHeight; - if (direction == Direction.BottomToTop || direction == Direction.TopToBottom) - { - currentLines = (int) layerModel.Width; - currentHeight = layerModel.Height; - } - else - { - currentLines = (int) layerModel.Height; - currentHeight = layerModel.Width; - } - - if (_lines != currentLines || _lineSpectrum == null) - { - _lines = currentLines; - _lineSpectrum = _audioCapture.GetLineSpectrum(_lines, ScalingStrategy.Decibel); - } - - var newLineValues = _lineSpectrum?.GetLineValues(currentHeight); - if (newLineValues != null) - _lineValues = newLineValues; - } - - public void SetupProperties(LayerModel layerModel) - { - if (layerModel.Properties is AudioPropertiesModel) - return; - - layerModel.Properties = new AudioPropertiesModel(layerModel.Properties) - { - DeviceType = MmDeviceType.Ouput, - Device = "Default", - Direction = Direction.BottomToTop, - ScalingStrategy = ScalingStrategy.Decibel - }; - } - - public LayerPropertiesViewModel SetupViewModel(LayerEditorViewModel layerEditorViewModel, - LayerPropertiesViewModel layerPropertiesViewModel) - { - if (layerPropertiesViewModel is AudioPropertiesViewModel) - return layerPropertiesViewModel; - return new AudioPropertiesViewModel(layerEditorViewModel); - } - - public void BrushDraw(LayerModel layerModel, DrawingContext c) - { - // If an animation is present, let it handle the drawing - if (layerModel.LayerAnimation != null && !(layerModel.LayerAnimation is NoneAnimation)) - { - layerModel.LayerAnimation.Draw(layerModel, c); - return; - } - - // Otherwise draw the rectangle with its layer.AppliedProperties dimensions and brush - var rect = layerModel.Properties.Contain - ? layerModel.LayerRect() - : new Rect(layerModel.Properties.X*4, layerModel.Properties.Y*4, - layerModel.Properties.Width*4, layerModel.Properties.Height*4); - - var clip = layerModel.LayerRect(); - - // Can't meddle with the original brush because it's frozen. - var brush = layerModel.Brush.Clone(); - brush.Opacity = layerModel.Opacity; - - c.PushClip(new RectangleGeometry(clip)); - c.DrawRectangle(brush, null, rect); - c.Pop(); - } - } + private void OnAudioDeviceChanged(object sender, AudioDeviceChangedEventArgs e) + { + // TODO: Check if layer must use default + // TODO: Check recording type + _audioCapture = _audioCaptureManager.GetAudioCapture(e.DefaultPlayback); + _lines = 0; + } + + public string Name => "Keyboard - Audio visualization"; + public bool ShowInEdtor => true; + public DrawType DrawType => DrawType.Keyboard; + + public ImageSource DrawThumbnail(LayerModel layer) + { + var thumbnailRect = new Rect(0, 0, 18, 18); + var visual = new DrawingVisual(); + using (var c = visual.RenderOpen()) + { + c.DrawImage(ImageUtilities.BitmapToBitmapImage(Resources.audio), thumbnailRect); + } + + var image = new DrawingImage(visual.Drawing); + return image; + } + + public void Draw(LayerModel layerModel, DrawingContext c) + { + _drawCount++; + if (_lineValues == null) + return; + + var parentX = layerModel.X; + var parentY = layerModel.Y; + var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; + + // Create a geometry that will be formed by all the bars + GeometryGroup barGeometry = new GeometryGroup(); + + switch (direction) + { + case Direction.TopToBottom: + for (var index = 0; index < _lineValues.Count; index++) + { + var clipRect = new Rect((parentX + index) * 4, parentY * 4, 4, _lineValues[index] * 4); + var barRect = new RectangleGeometry(clipRect); + barGeometry.Children.Add(barRect); + } + break; + case Direction.BottomToTop: + for (var index = 0; index < _lineValues.Count; index++) + { + var clipRect = new Rect((parentX + index) * 4, parentY * 4, 4, _lineValues[index] * 4); + clipRect.Y = clipRect.Y + layerModel.Height * 4 - clipRect.Height; + var barRect = new RectangleGeometry(clipRect); + barGeometry.Children.Add(barRect); + } + break; + case Direction.LeftToRight: + for (var index = 0; index < _lineValues.Count; index++) + { + var clipRect = new Rect((parentX + index) * 4, parentY * 4, 4, _lineValues[index] * 4); + var barRect = new RectangleGeometry(clipRect); + barGeometry.Children.Add(barRect); + } + break; + default: + for (var index = 0; index < _lineValues.Count; index++) + { + var clipRect = new Rect((parentX + index) * 4, parentY * 4, 4, _lineValues[index] * 4); + var barRect = new RectangleGeometry(clipRect); + barGeometry.Children.Add(barRect); + } + break; + } + + // Push the created geometry + c.PushClip(barGeometry); + BrushDraw(layerModel, c); + c.Pop(); + } + + public void Update(LayerModel layerModel, ModuleDataModel dataModel, bool isPreview = false) + { + layerModel.ApplyProperties(true); + _audioCapture.Pulse(); + + var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; + + int currentLines; + double currentHeight; + if (direction == Direction.BottomToTop || direction == Direction.TopToBottom) + { + currentLines = (int) layerModel.Width; + currentHeight = layerModel.Height; + } + else + { + currentLines = (int) layerModel.Height; + currentHeight = layerModel.Width; + } + + if (_lines != currentLines || _lineSpectrum == null) + { + _lines = currentLines; + _lineSpectrum = _audioCapture.GetLineSpectrum(_lines, ScalingStrategy.Decibel); + } + + var newLineValues = _lineSpectrum?.GetLineValues(currentHeight); + if (newLineValues != null) + _lineValues = newLineValues; + } + + public void SetupProperties(LayerModel layerModel) + { + if (layerModel.Properties is AudioPropertiesModel) + return; + + layerModel.Properties = new AudioPropertiesModel(layerModel.Properties) + { + DeviceType = MmDeviceType.Ouput, + Device = "Default", + Direction = Direction.BottomToTop, + ScalingStrategy = ScalingStrategy.Decibel + }; + } + + public LayerPropertiesViewModel SetupViewModel(LayerEditorViewModel layerEditorViewModel, + LayerPropertiesViewModel layerPropertiesViewModel) + { + if (layerPropertiesViewModel is AudioPropertiesViewModel) + return layerPropertiesViewModel; + return new AudioPropertiesViewModel(layerEditorViewModel); + } + + public void BrushDraw(LayerModel layerModel, DrawingContext c) + { + // If an animation is present, let it handle the drawing + if (layerModel.LayerAnimation != null && !(layerModel.LayerAnimation is NoneAnimation)) + { + layerModel.LayerAnimation.Draw(layerModel, c); + return; + } + + // Otherwise draw the rectangle with its layer.AppliedProperties dimensions and brush + var rect = layerModel.Properties.Contain + ? layerModel.LayerRect() + : new Rect(layerModel.Properties.X*4, layerModel.Properties.Y*4, + layerModel.Properties.Width*4, layerModel.Properties.Height*4); + + var clip = layerModel.LayerRect(); + + // Can't meddle with the original brush because it's frozen. + var brush = layerModel.Brush.Clone(); + brush.Opacity = layerModel.Opacity; + + c.PushClip(new RectangleGeometry(clip)); + c.DrawRectangle(brush, null, rect); + c.Pop(); + } + } } \ No newline at end of file diff --git a/Artemis/Artemis/ViewModels/DebugViewModel.cs b/Artemis/Artemis/ViewModels/DebugViewModel.cs index 43cb16d5b..6a27a94a8 100644 --- a/Artemis/Artemis/ViewModels/DebugViewModel.cs +++ b/Artemis/Artemis/ViewModels/DebugViewModel.cs @@ -6,20 +6,19 @@ using System.Windows.Media; using Artemis.Managers; using Artemis.Utilities; using Caliburn.Micro; -using Action = System.Action; namespace Artemis.ViewModels { public class DebugViewModel : Screen { private readonly DeviceManager _deviceManager; - - private DrawingImage _razerDisplay; + private DrawingImage _generic; + private DrawingImage _headset; private DrawingImage _keyboard; private DrawingImage _mouse; - private DrawingImage _headset; private DrawingImage _mousemat; - private DrawingImage _generic; + + private DrawingImage _razerDisplay; public DebugViewModel(DeviceManager deviceManager) { @@ -116,8 +115,8 @@ namespace Artemis.ViewModels { dc.PushClip(new RectangleGeometry(new Rect(0, 0, 22, 6))); for (var y = 0; y < 6; y++) - for (var x = 0; x < 22; x++) - dc.DrawRectangle(new SolidColorBrush(colors[y, x]), null, new Rect(x, y, 1, 1)); + for (var x = 0; x < 22; x++) + dc.DrawRectangle(new SolidColorBrush(colors[y, x]), null, new Rect(x, y, 1, 1)); } var drawnDisplay = new DrawingImage(visual.Drawing); drawnDisplay.Freeze(); @@ -137,10 +136,14 @@ namespace Artemis.ViewModels Keyboard = ImageUtilities.BitmapToDrawingImage(frame.KeyboardBitmap, rect); } - Mouse = ImageUtilities.BitmapToDrawingImage(frame.MouseBitmap, new Rect(0, 0, 10, 10)); - Headset = ImageUtilities.BitmapToDrawingImage(frame.HeadsetBitmap, new Rect(0, 0, 10, 10)); - Mousemat = ImageUtilities.BitmapToDrawingImage(frame.MousematBitmap, new Rect(0, 0, 10, 10)); - Generic = ImageUtilities.BitmapToDrawingImage(frame.GenericBitmap, new Rect(0, 0, 10, 10)); + if (frame.MouseBitmap != null) + Mouse = ImageUtilities.BitmapToDrawingImage(frame.MouseBitmap, new Rect(0, 0, 10, 10)); + if (frame.HeadsetBitmap != null) + Headset = ImageUtilities.BitmapToDrawingImage(frame.HeadsetBitmap, new Rect(0, 0, 10, 10)); + if (frame.MousematBitmap != null) + Mousemat = ImageUtilities.BitmapToDrawingImage(frame.MousematBitmap, new Rect(0, 0, 10, 10)); + if (frame.GenericBitmap != null) + Generic = ImageUtilities.BitmapToDrawingImage(frame.GenericBitmap, new Rect(0, 0, 10, 10)); } } } \ No newline at end of file From 07f7ae263db402b28d6479079640fddc89e05c39 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sun, 22 Jan 2017 00:31:51 +0100 Subject: [PATCH 23/32] Add support for multiple processes per module Add ATS support --- Artemis/Artemis/Managers/LoopManager.cs | 530 +++++++++--------- Artemis/Artemis/Managers/MainManager.cs | 6 +- .../Artemis/Modules/Abstract/ModuleModel.cs | 4 +- .../Games/CounterStrike/CounterStrikeModel.cs | 2 +- .../CounterStrike/CounterStrikeView.xaml | 2 +- .../Artemis/Modules/Games/Dota2/Dota2Model.cs | 2 +- .../Modules/Games/Dota2/Dota2View.xaml | 2 +- .../EurotruckSimulator2Model.cs | 57 +- .../EurotruckSimulator2Settings.cs | 3 +- .../EurotruckSimulator2View.xaml | 52 +- .../EurotruckSimulator2ViewModel.cs | 32 +- .../Artemis/Modules/Games/GtaV/GtaVModel.cs | 2 +- .../Modules/Games/LightFx/LightFxModel.cs | 6 +- .../Modules/Games/Overwatch/OverwatchModel.cs | 2 +- .../Games/Overwatch/OverwatchView.xaml | 2 +- .../Games/ProjectCars/ProjectCarsModel.cs | 2 +- .../Games/RocketLeague/RocketLeagueModel.cs | 4 +- .../RocketLeague/RocketLeagueViewModel.cs | 2 - .../Games/TheDivision/TheDivisionModel.cs | 2 +- .../UnrealTournament/UnrealTournamentModel.cs | 2 +- .../UnrealTournamentView.xaml | 2 +- .../Modules/Games/Witcher3/Witcher3Model.cs | 2 +- Artemis/Artemis/Modules/Games/WoW/WoWModel.cs | 4 +- Artemis/Artemis/Utilities/ImageUtilities.cs | 1 + 24 files changed, 396 insertions(+), 329 deletions(-) diff --git a/Artemis/Artemis/Managers/LoopManager.cs b/Artemis/Artemis/Managers/LoopManager.cs index 85e66818f..b747a8512 100644 --- a/Artemis/Artemis/Managers/LoopManager.cs +++ b/Artemis/Artemis/Managers/LoopManager.cs @@ -1,271 +1,271 @@ -using System; -using System.Drawing; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Artemis.DeviceProviders; -using Artemis.ViewModels; -using Ninject.Extensions.Logging; -using Color = System.Drawing.Color; - -namespace Artemis.Managers -{ - /// - /// Manages the main programn loop - /// - public class LoopManager : IDisposable - { - private readonly DebugViewModel _debugViewModel; - private readonly DeviceManager _deviceManager; - private readonly ILogger _logger; - //private readonly Timer _loopTimer; - private readonly Task _loopTask; - private readonly ModuleManager _moduleManager; - - public LoopManager(ILogger logger, ModuleManager moduleManager, DeviceManager deviceManager, - DebugViewModel debugViewModel) - { - _logger = logger; - _moduleManager = moduleManager; - _deviceManager = deviceManager; - _debugViewModel = debugViewModel; - - // Setup timers - _loopTask = Task.Factory.StartNew(ProcessLoop); - _logger.Info("Intialized LoopManager"); - } - - public DebugViewModel DebugViewModel { get; set; } - - /// - /// Gets whether the loop is running - /// - public bool Running { get; private set; } - - public void Dispose() - { - _loopTask.Dispose(); - } - - private void ProcessLoop() - { - //TODO DarthAffe 14.01.2017: A stop-condition and a real cleanup instead of just aborting might be better - while (true) - { - try - { - long preUpdateTicks = DateTime.Now.Ticks; - - Render(); - - int sleep = (int)(40f - ((DateTime.Now.Ticks - preUpdateTicks) / 10000f)); - if (sleep > 0) - Thread.Sleep(sleep); - } - catch (Exception e) - { - _logger.Warn(e, "Exception in render loop"); - } - } - // ReSharper disable once FunctionNeverReturns - } - - public Task StartAsync() - { - return Task.Run(() => Start()); - } - - private void Start() - { - if (Running) - return; - - _logger.Debug("Starting LoopManager"); - - if (_deviceManager.ActiveKeyboard == null) - _deviceManager.EnableLastKeyboard(); - - while (_deviceManager.ChangingKeyboard) - Thread.Sleep(200); - - // If still null, no last keyboard, so stop. - if (_deviceManager.ActiveKeyboard == null) - { - _logger.Debug("Cancel LoopManager start, no keyboard"); - return; - } - - if (_moduleManager.ActiveModule == null) - { - var lastModule = _moduleManager.GetLastModule(); - if (lastModule == null) - { - _logger.Debug("Cancel LoopManager start, no module"); - return; - } - _moduleManager.ChangeActiveModule(lastModule); - } - - Running = true; - } - - public void Stop() - { - if (!Running) - return; - - _logger.Debug("Stopping LoopManager"); - Running = false; - - _deviceManager.ReleaseActiveKeyboard(); - } - - private void Render() - { - if (!Running || _deviceManager.ChangingKeyboard) - return; - - // Stop if no active module - if (_moduleManager.ActiveModule == null) - { - _logger.Debug("No active module, stopping"); - Stop(); - return; - } - var renderModule = _moduleManager.ActiveModule; - - // Stop if no active keyboard - if (_deviceManager.ActiveKeyboard == null) - { - _logger.Debug("No active keyboard, stopping"); - Stop(); - return; - } - - lock (_deviceManager.ActiveKeyboard) - { - // Skip frame if module is still initializing - if (renderModule.IsInitialized == false) - return; - - // ApplyProperties the current module - if (renderModule.IsInitialized) - renderModule.Update(); - - // Get the devices that must be rendered to - var mice = _deviceManager.MiceProviders.Where(m => m.CanUse).ToList(); - var headsets = _deviceManager.HeadsetProviders.Where(m => m.CanUse).ToList(); - var generics = _deviceManager.GenericProviders.Where(m => m.CanUse).ToList(); - var mousemats = _deviceManager.MousematProviders.Where(m => m.CanUse).ToList(); - - var keyboardOnly = !mice.Any() && !headsets.Any() && !generics.Any() && !mousemats.Any(); - - // Setup the frame for this tick - using (var frame = new RenderFrame(_deviceManager.ActiveKeyboard, mice.Any(), headsets.Any(), generics.Any(), mousemats.Any())) - { - if (renderModule.IsInitialized) - renderModule.Render(frame, keyboardOnly); - - // Draw enabled overlays on top of the renderModule - foreach (var overlayModel in _moduleManager.OverlayModules.Where(o => o.Settings.IsEnabled)) - { - overlayModel.Update(); - overlayModel.Render(frame, keyboardOnly); - } - - // Update the keyboard - _deviceManager.ActiveKeyboard?.DrawBitmap(frame.KeyboardBitmap); - - // Update the other devices - foreach (var mouse in mice) - mouse.UpdateDevice(frame.MouseBitmap); - foreach (var headset in headsets) - headset.UpdateDevice(frame.HeadsetBitmap); - foreach (var generic in generics) - generic.UpdateDevice(frame.GenericBitmap); - foreach (var mousemat in mousemats) - mousemat.UpdateDevice(frame.MousematBitmap); - - _debugViewModel.DrawFrame(frame); - - OnRenderCompleted(); - } - } - } - - public event EventHandler RenderCompleted; - - protected virtual void OnRenderCompleted() - { - RenderCompleted?.Invoke(this, EventArgs.Empty); - } - } - - public class RenderFrame : IDisposable - { - public RenderFrame(KeyboardProvider keyboard, bool renderMice, bool renderHeadsets, bool renderGenerics, bool renderMousemats) +using System; +using System.Drawing; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Artemis.DeviceProviders; +using Artemis.ViewModels; +using Ninject.Extensions.Logging; +using Color = System.Drawing.Color; + +namespace Artemis.Managers +{ + /// + /// Manages the main programn loop + /// + public class LoopManager : IDisposable + { + private readonly DebugViewModel _debugViewModel; + private readonly DeviceManager _deviceManager; + private readonly ILogger _logger; + //private readonly Timer _loopTimer; + private readonly Task _loopTask; + private readonly ModuleManager _moduleManager; + + public LoopManager(ILogger logger, ModuleManager moduleManager, DeviceManager deviceManager, + DebugViewModel debugViewModel) { - if (keyboard == null) - return; - - KeyboardBitmap = keyboard.KeyboardBitmap(); + _logger = logger; + _moduleManager = moduleManager; + _deviceManager = deviceManager; + _debugViewModel = debugViewModel; + + // Setup timers + _loopTask = Task.Factory.StartNew(ProcessLoop); + _logger.Info("Intialized LoopManager"); + } + + public DebugViewModel DebugViewModel { get; set; } + + /// + /// Gets whether the loop is running + /// + public bool Running { get; private set; } + + public void Dispose() + { + _loopTask.Dispose(); + } + + private void ProcessLoop() + { + //TODO DarthAffe 14.01.2017: A stop-condition and a real cleanup instead of just aborting might be better + while (true) + { + try + { + long preUpdateTicks = DateTime.Now.Ticks; + + Render(); + + int sleep = (int)(40f - ((DateTime.Now.Ticks - preUpdateTicks) / 10000f)); + if (sleep > 0) + Thread.Sleep(sleep); + } + catch (Exception e) + { + _logger.Warn(e, "Exception in render loop"); + } + } + // ReSharper disable once FunctionNeverReturns + } + + public Task StartAsync() + { + return Task.Run(() => Start()); + } + + private void Start() + { + if (Running) + return; + + _logger.Debug("Starting LoopManager"); + + if (_deviceManager.ActiveKeyboard == null) + _deviceManager.EnableLastKeyboard(); + + while (_deviceManager.ChangingKeyboard) + Thread.Sleep(200); + + // If still null, no last keyboard, so stop. + if (_deviceManager.ActiveKeyboard == null) + { + _logger.Debug("Cancel LoopManager start, no keyboard"); + return; + } + + if (_moduleManager.ActiveModule == null) + { + var lastModule = _moduleManager.GetLastModule(); + if (lastModule == null) + { + _logger.Debug("Cancel LoopManager start, no module"); + return; + } + _moduleManager.ChangeActiveModule(lastModule); + } + + Running = true; + } + + public void Stop() + { + if (!Running) + return; + + _logger.Debug("Stopping LoopManager"); + Running = false; + + _deviceManager.ReleaseActiveKeyboard(); + } + + private void Render() + { + if (!Running || _deviceManager.ChangingKeyboard) + return; + + // Stop if no active module + if (_moduleManager.ActiveModule == null) + { + _logger.Debug("No active module, stopping"); + Stop(); + return; + } + var renderModule = _moduleManager.ActiveModule; + + // Stop if no active keyboard + if (_deviceManager.ActiveKeyboard == null) + { + _logger.Debug("No active keyboard, stopping"); + Stop(); + return; + } + + lock (_deviceManager.ActiveKeyboard) + { + // Skip frame if module is still initializing + if (renderModule.IsInitialized == false) + return; + + // ApplyProperties the current module + if (renderModule.IsInitialized) + renderModule.Update(); + + // Get the devices that must be rendered to + var mice = _deviceManager.MiceProviders.Where(m => m.CanUse).ToList(); + var headsets = _deviceManager.HeadsetProviders.Where(m => m.CanUse).ToList(); + var generics = _deviceManager.GenericProviders.Where(m => m.CanUse).ToList(); + var mousemats = _deviceManager.MousematProviders.Where(m => m.CanUse).ToList(); + + var keyboardOnly = !mice.Any() && !headsets.Any() && !generics.Any() && !mousemats.Any(); + + // Setup the frame for this tick + using (var frame = new RenderFrame(_deviceManager.ActiveKeyboard, mice.Any(), headsets.Any(), generics.Any(), mousemats.Any())) + { + if (renderModule.IsInitialized) + renderModule.Render(frame, keyboardOnly); + + // Draw enabled overlays on top of the renderModule + foreach (var overlayModel in _moduleManager.OverlayModules.Where(o => o.Settings.IsEnabled)) + { + overlayModel.Update(); + overlayModel.Render(frame, keyboardOnly); + } + + // Update the keyboard + _deviceManager.ActiveKeyboard?.DrawBitmap(frame.KeyboardBitmap); + + // Update the other devices + foreach (var mouse in mice) + mouse.UpdateDevice(frame.MouseBitmap); + foreach (var headset in headsets) + headset.UpdateDevice(frame.HeadsetBitmap); + foreach (var generic in generics) + generic.UpdateDevice(frame.GenericBitmap); + foreach (var mousemat in mousemats) + mousemat.UpdateDevice(frame.MousematBitmap); + + _debugViewModel.DrawFrame(frame); + + OnRenderCompleted(); + } + } + } + + public event EventHandler RenderCompleted; + + protected virtual void OnRenderCompleted() + { + RenderCompleted?.Invoke(this, EventArgs.Empty); + } + } + + public class RenderFrame : IDisposable + { + public RenderFrame(KeyboardProvider keyboard, bool renderMice, bool renderHeadsets, bool renderGenerics, bool renderMousemats) + { + if (keyboard == null) + return; + + KeyboardBitmap = keyboard.KeyboardBitmap(); KeyboardBitmap.SetResolution(96, 96); - using (var g = Graphics.FromImage(KeyboardBitmap)) - { - g.Clear(Color.Black); - } - - if (renderMice) - { - MouseBitmap = new Bitmap(10, 10); - MouseBitmap.SetResolution(96, 96); - using (var g = Graphics.FromImage(MouseBitmap)) - { - g.Clear(Color.Black); - } - } - if (renderHeadsets) - { - HeadsetBitmap = new Bitmap(10, 10); - HeadsetBitmap.SetResolution(96, 96); - using (var g = Graphics.FromImage(HeadsetBitmap)) - { - g.Clear(Color.Black); - } - } - if (renderGenerics) - { - GenericBitmap = new Bitmap(10, 10); - GenericBitmap.SetResolution(96, 96); - using (var g = Graphics.FromImage(GenericBitmap)) - { - g.Clear(Color.Black); - } - } - if (renderMousemats) - { - MousematBitmap = new Bitmap(10, 10); - MousematBitmap.SetResolution(96, 96); + using (var g = Graphics.FromImage(KeyboardBitmap)) + { + g.Clear(Color.Black); + } + + if (renderMice) + { + MouseBitmap = new Bitmap(10, 10); + MouseBitmap.SetResolution(96, 96); + using (var g = Graphics.FromImage(MouseBitmap)) + { + g.Clear(Color.Black); + } + } + if (renderHeadsets) + { + HeadsetBitmap = new Bitmap(10, 10); + HeadsetBitmap.SetResolution(96, 96); + using (var g = Graphics.FromImage(HeadsetBitmap)) + { + g.Clear(Color.Black); + } + } + if (renderGenerics) + { + GenericBitmap = new Bitmap(10, 10); + GenericBitmap.SetResolution(96, 96); + using (var g = Graphics.FromImage(GenericBitmap)) + { + g.Clear(Color.Black); + } + } + if (renderMousemats) + { + MousematBitmap = new Bitmap(10, 10); + MousematBitmap.SetResolution(96, 96); using (var g = Graphics.FromImage(MousematBitmap)) { g.Clear(Color.Black); - } - } - } - - public Bitmap KeyboardBitmap { get; set; } - public Bitmap MouseBitmap { get; set; } - public Bitmap HeadsetBitmap { get; set; } - public Bitmap GenericBitmap { get; set; } - public Bitmap MousematBitmap { get; set; } - - public void Dispose() - { - KeyboardBitmap?.Dispose(); - MouseBitmap?.Dispose(); - HeadsetBitmap?.Dispose(); - GenericBitmap?.Dispose(); - MousematBitmap?.Dispose(); - } - } + } + } + } + + public Bitmap KeyboardBitmap { get; set; } + public Bitmap MouseBitmap { get; set; } + public Bitmap HeadsetBitmap { get; set; } + public Bitmap GenericBitmap { get; set; } + public Bitmap MousematBitmap { get; set; } + + public void Dispose() + { + KeyboardBitmap?.Dispose(); + MouseBitmap?.Dispose(); + HeadsetBitmap?.Dispose(); + GenericBitmap?.Dispose(); + MousematBitmap?.Dispose(); + } + } } \ No newline at end of file diff --git a/Artemis/Artemis/Managers/MainManager.cs b/Artemis/Artemis/Managers/MainManager.cs index 7a3fc7635..bd4c4b00f 100644 --- a/Artemis/Artemis/Managers/MainManager.cs +++ b/Artemis/Artemis/Managers/MainManager.cs @@ -144,7 +144,7 @@ namespace Artemis.Managers if (!ProgramEnabled) return; - var processes = System.Diagnostics.Process.GetProcesses(); + var processes = System.Diagnostics.Process.GetProcesses().Where(p => !p.HasExited).ToList(); var module = ModuleManager.ActiveModule; // If the current active module is in preview-mode, leave it alone @@ -158,7 +158,7 @@ namespace Artemis.Managers ModuleManager.DisableProcessBoundModule(); // If the currently active effect is a no longer running game, get rid of it. - if (!processes.Any(p => p.ProcessName == module.ProcessName && p.HasExited == false)) + if (!processes.Any(p => module.ProcessNames.Contains(p.ProcessName))) { Logger.Info("Disabling process bound module because process stopped: {0}", module.Name); ModuleManager.DisableProcessBoundModule(); @@ -167,7 +167,7 @@ namespace Artemis.Managers // Look for running games, stopping on the first one that's found. var newModule = ModuleManager.ProcessModules.Where(g => g.Settings.IsEnabled && g.Settings.IsEnabled) - .FirstOrDefault(g => processes.Any(p => p.ProcessName == g.ProcessName && p.HasExited == false)); + .FirstOrDefault(g => processes.Any(p => g.ProcessNames.Contains(p.ProcessName))); if (newModule == null || module == newModule) return; diff --git a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs index 299421c01..13776b9ab 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs @@ -79,9 +79,9 @@ namespace Artemis.Modules.Abstract public List PreviewLayers { get; set; } /// - /// The process the module is bound to + /// The processes the module is bound to /// - public string ProcessName { get; protected set; } + public List ProcessNames { get; protected set; } = new List(); /// /// The currently active profile of the module diff --git a/Artemis/Artemis/Modules/Games/CounterStrike/CounterStrikeModel.cs b/Artemis/Artemis/Modules/Games/CounterStrike/CounterStrikeModel.cs index 284436c60..dd80b03d7 100644 --- a/Artemis/Artemis/Modules/Games/CounterStrike/CounterStrikeModel.cs +++ b/Artemis/Artemis/Modules/Games/CounterStrike/CounterStrikeModel.cs @@ -28,7 +28,7 @@ namespace Artemis.Modules.Games.CounterStrike Settings = SettingsProvider.Load(); DataModel = new CounterStrikeDataModel(); - ProcessName = "csgo"; + ProcessNames.Add("csgo"); FindGameDir(); PlaceConfigFile(); diff --git a/Artemis/Artemis/Modules/Games/CounterStrike/CounterStrikeView.xaml b/Artemis/Artemis/Modules/Games/CounterStrike/CounterStrikeView.xaml index fe03c3f47..1bdbd9807 100644 --- a/Artemis/Artemis/Modules/Games/CounterStrike/CounterStrikeView.xaml +++ b/Artemis/Artemis/Modules/Games/CounterStrike/CounterStrikeView.xaml @@ -48,7 +48,7 @@ cal:Message.Attach="[Event LostFocus] = [Action PlaceConfigFile]" /> public void DisableProcessBoundModule() { + if (ActiveModule == null) + return; if (!ActiveModule.IsBoundToProcess) { _logger.Warn("Active module {0} is not process bound but is being disabled as if it is.", @@ -176,10 +178,11 @@ namespace Artemis.Managers return; } - if (GetLastModule() == null) + var lastModule = GetLastModule(); + if (lastModule == null || !lastModule.Settings.IsEnabled) ClearActiveModule(); else - ChangeActiveModule(GetLastModule()); + ChangeActiveModule(lastModule); } protected virtual void RaiseEffectChangedEvent(ModuleChangedEventArgs e) diff --git a/Artemis/Artemis/Managers/PreviewManager.cs b/Artemis/Artemis/Managers/PreviewManager.cs index ff5657762..c7a52dfdc 100644 --- a/Artemis/Artemis/Managers/PreviewManager.cs +++ b/Artemis/Artemis/Managers/PreviewManager.cs @@ -82,7 +82,7 @@ namespace Artemis.Managers activePreview?.ProfileEditor?.SaveSelectedProfile(); var lastModule = _moduleManager.GetLastModule(); - if (lastModule != null) + if (lastModule != null && lastModule.Settings.IsEnabled) _moduleManager.ChangeActiveModule(lastModule); else _moduleManager.ClearActiveModule(); diff --git a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs index 13776b9ab..9a8bf6599 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs @@ -88,6 +88,8 @@ namespace Artemis.Modules.Abstract /// public ProfileModel ProfileModel { get; protected set; } + public bool IsGeneral => !IsOverlay && !IsBoundToProcess; + #endregion #region Base methods diff --git a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs index ff70eba65..524dd1608 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs @@ -28,7 +28,7 @@ namespace Artemis.Modules.Abstract _mainManager.EnabledChanged += MainManagerOnEnabledChanged; _moduleManager.EffectChanged += ModuleManagerOnModuleChanged; - + // ReSharper disable once VirtualMemberCallInConstructor if (!UsesProfileEditor) return; @@ -68,7 +68,7 @@ namespace Artemis.Modules.Abstract { get { - if (ModuleModel.IsBoundToProcess || ModuleModel.IsOverlay) + if (!ModuleModel.IsGeneral) return Settings.IsEnabled; return _generalSettings.LastModule == ModuleModel.Name; } @@ -91,7 +91,7 @@ namespace Artemis.Modules.Abstract private void UpdatedEnabledSetting() { - if (ModuleModel.IsBoundToProcess || ModuleModel.IsOverlay) + if (!ModuleModel.IsGeneral || !_moduleManager.ActiveModule.IsGeneral || Settings.IsEnabled == IsModuleActive) return; Settings.IsEnabled = IsModuleActive; @@ -106,7 +106,7 @@ namespace Artemis.Modules.Abstract NotifyOfPropertyChange(() => Settings); // On process-bound modules, only set the module model - if (ModuleModel.IsBoundToProcess || ModuleModel.IsOverlay) + if (!ModuleModel.IsGeneral) { NotifyOfPropertyChange(() => IsModuleActive); NotifyOfPropertyChange(() => IsModuleEnabled); From ab17119fbbf4e00b0b40d2b998385aa27149d672 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Mon, 23 Jan 2017 19:01:34 +0100 Subject: [PATCH 27/32] Fixed audio layer not picking up device change --- .../Artemis/Managers/AudioCaptureManager.cs | 17 ++-- .../Modules/Abstract/ModuleViewModel.cs | 2 +- .../GeneralProfile/GeneralProfileModel.cs | 23 ++--- .../OverlayProfile/OverlayProfileModel.cs | 9 +- .../Audio/AudioCapturing/AudioCapture.cs | 28 ++++-- .../Types/Audio/AudioPropertiesView.xaml | 36 +++++--- .../Types/Audio/AudioPropertiesViewModel.cs | 47 +++++++++- .../Profiles/Layers/Types/Audio/AudioType.cs | 86 +++++++++++++++---- 8 files changed, 187 insertions(+), 61 deletions(-) diff --git a/Artemis/Artemis/Managers/AudioCaptureManager.cs b/Artemis/Artemis/Managers/AudioCaptureManager.cs index 52e063838..161448bc9 100644 --- a/Artemis/Artemis/Managers/AudioCaptureManager.cs +++ b/Artemis/Artemis/Managers/AudioCaptureManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Timers; using Artemis.Events; +using Artemis.Profiles.Layers.Types.Audio; using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; using CSCore.CoreAudioAPI; using Ninject.Extensions.Logging; @@ -19,8 +20,8 @@ namespace Artemis.Managers { Logger = logger; _audioCaptures = new List(); - _lastDefaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - _lastDefaultRecording = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); + _lastDefaultPlayback = MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + _lastDefaultRecording = MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); var defaultDeviceTimer = new Timer(1000); defaultDeviceTimer.Elapsed += DefaultDeviceTimerOnElapsed; @@ -31,11 +32,11 @@ namespace Artemis.Managers private void DefaultDeviceTimerOnElapsed(object sender, ElapsedEventArgs e) { - var defaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - var defaultRecording = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); + var defaultPlayback = MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + var defaultRecording = MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); - if (defaultPlayback.DeviceID == _lastDefaultPlayback.DeviceID && - defaultRecording.DeviceID == _lastDefaultRecording.DeviceID) + if (defaultPlayback?.DeviceID == _lastDefaultPlayback?.DeviceID && + defaultRecording?.DeviceID == _lastDefaultRecording?.DeviceID) return; _lastDefaultPlayback = defaultPlayback; @@ -43,7 +44,7 @@ namespace Artemis.Managers OnAudioDeviceChanged(new AudioDeviceChangedEventArgs(_lastDefaultPlayback, _lastDefaultRecording)); } - public AudioCapture GetAudioCapture(MMDevice device) + public AudioCapture GetAudioCapture(MMDevice device, MmDeviceType type) { // Return existing audio capture if found var audioCapture = _audioCaptures.FirstOrDefault(a => a.Device.DeviceID == device.DeviceID); @@ -51,7 +52,7 @@ namespace Artemis.Managers return audioCapture; // Else create a new one and return that - var newAudioCapture = new AudioCapture(Logger, device); + var newAudioCapture = new AudioCapture(Logger, device, type); _audioCaptures.Add(newAudioCapture); return newAudioCapture; } diff --git a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs index 524dd1608..7176fbbaf 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs @@ -91,7 +91,7 @@ namespace Artemis.Modules.Abstract private void UpdatedEnabledSetting() { - if (!ModuleModel.IsGeneral || !_moduleManager.ActiveModule.IsGeneral || Settings.IsEnabled == IsModuleActive) + if (!ModuleModel.IsGeneral || (_moduleManager.ActiveModule != null && !_moduleManager.ActiveModule.IsGeneral || Settings.IsEnabled == IsModuleActive)) return; Settings.IsEnabled = IsModuleActive; diff --git a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs index ecb0e0873..4f7bea4e5 100644 --- a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs +++ b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs @@ -80,29 +80,32 @@ namespace Artemis.Modules.General.GeneralProfile private void SetupAudio() { - _defaultRecording = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); - _recordingInfo = AudioMeterInformation.FromDevice(_defaultRecording); - _defaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - _playbackInfo = AudioMeterInformation.FromDevice(_defaultPlayback); + _defaultRecording = MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); + _defaultPlayback = MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + + if (_defaultRecording != null) + _recordingInfo = AudioMeterInformation.FromDevice(_defaultRecording); + if (_defaultPlayback != null) + _playbackInfo = AudioMeterInformation.FromDevice(_defaultPlayback); } private void AudioDeviceChanged(object sender, AudioDeviceChangedEventArgs e) { _defaultRecording = e.DefaultRecording; - _recordingInfo = AudioMeterInformation.FromDevice(_defaultRecording); _defaultPlayback = e.DefaultPlayback; - _playbackInfo = AudioMeterInformation.FromDevice(_defaultPlayback); + + if (_defaultRecording != null) + _recordingInfo = AudioMeterInformation.FromDevice(_defaultRecording); + if (_defaultPlayback != null) + _playbackInfo = AudioMeterInformation.FromDevice(_defaultPlayback); } private void UpdateAudio(GeneralProfileDataModel dataModel) { // Update microphone, only bother with OverallPeak if (_defaultRecording != null) - { - dataModel.Audio.Recording.OverallPeak = _recordingInfo.PeakValue; - } - + if (_defaultPlayback == null) return; diff --git a/Artemis/Artemis/Modules/Overlays/OverlayProfile/OverlayProfileModel.cs b/Artemis/Artemis/Modules/Overlays/OverlayProfile/OverlayProfileModel.cs index 2a6c05f2b..72414dfef 100644 --- a/Artemis/Artemis/Modules/Overlays/OverlayProfile/OverlayProfileModel.cs +++ b/Artemis/Artemis/Modules/Overlays/OverlayProfile/OverlayProfileModel.cs @@ -17,8 +17,10 @@ namespace Artemis.Modules.Overlays.OverlayProfile Settings = SettingsProvider.Load(); DataModel = new OverlayProfileDataModel(); - var defaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - _endPointVolume = AudioEndpointVolume.FromDevice(defaultPlayback); + var defaultPlayback = MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + if (defaultPlayback != null) + _endPointVolume = AudioEndpointVolume.FromDevice(defaultPlayback); + audioCaptureManager.AudioDeviceChanged += OnAudioDeviceChanged; Enable(); @@ -30,7 +32,8 @@ namespace Artemis.Modules.Overlays.OverlayProfile private void OnAudioDeviceChanged(object sender, AudioDeviceChangedEventArgs e) { - _endPointVolume = AudioEndpointVolume.FromDevice(e.DefaultPlayback); + if (e.DefaultPlayback != null) + _endPointVolume = AudioEndpointVolume.FromDevice(e.DefaultPlayback); } public override void Update() diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs index 29d488632..8ad1700de 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs @@ -5,6 +5,7 @@ using CSCore; using CSCore.CoreAudioAPI; using CSCore.DSP; using CSCore.SoundIn; +using CSCore.SoundOut; using CSCore.Streams; using Ninject.Extensions.Logging; @@ -18,16 +19,17 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing private readonly Timer _disableTimer; private bool _mayStop; private SingleSpectrum _singleSpectrum; - private WasapiLoopbackCapture _soundIn; + private ISoundIn _soundIn; private GainSource _source; private BasicSpectrumProvider _spectrumProvider; private GainSource _volume; private int _volumeIndex; - public AudioCapture(ILogger logger, MMDevice device) + public AudioCapture(ILogger logger, MMDevice device, MmDeviceType type) { Logger = logger; Device = device; + Type = type; DesiredAverage = 0.75; _volumeValues = new double[5]; @@ -40,6 +42,7 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing public ILogger Logger { get; } public MMDevice Device { get; } + public MmDeviceType Type { get; } public double DesiredAverage { get; set; } public float Volume @@ -95,6 +98,8 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing public LineSpectrum GetLineSpectrum(int barCount, ScalingStrategy scalingStrategy) { + if (_spectrumProvider == null) + return null; return new LineSpectrum(FftSize) { SpectrumProvider = _spectrumProvider, @@ -135,11 +140,20 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing { Stop(); - _soundIn = new WasapiLoopbackCapture(); + if (Type == MmDeviceType.Input) + { + _soundIn = Device != null + ? new WasapiCapture {Device = Device} + : new WasapiCapture(); + } + else + { + _soundIn = Device != null + ? new WasapiLoopbackCapture {Device = Device} + : new WasapiLoopbackCapture(); + } + _soundIn.Initialize(); - // Not sure if this null check is needed but doesnt hurt - if (Device != null) - _soundIn.Device = Device; var soundInSource = new SoundInSource(_soundIn); _source = soundInSource.ToSampleSource().AppendSource(x => new GainSource(x), out _volume); @@ -182,7 +196,7 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing { Running = false; - + if (_soundIn != null) { _soundIn.Stop(); diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesView.xaml b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesView.xaml index e6bfb99ef..00ec05a65 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesView.xaml +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesView.xaml @@ -11,6 +11,13 @@ d:DesignHeight="600" d:DesignWidth="500"> + + + + + @@ -54,22 +61,23 @@ TickPlacement="None" TickFrequency="0.05" Value="{Binding Path=LayerModel.Properties.AnimationSpeed, Mode=TwoWay}" Minimum="0.05" Maximum="3" SmallChange="0" IsSnapToTickEnabled="True" Margin="10,12,10,2" Height="24" /> - - - - - - + + + + + + + + + + - + diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs index 079b386b4..23ba6c280 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs @@ -2,8 +2,8 @@ using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; using Artemis.ViewModels; -using Artemis.ViewModels.Profiles; using Caliburn.Micro; +using CSCore.CoreAudioAPI; namespace Artemis.Profiles.Layers.Types.Audio { @@ -14,12 +14,18 @@ namespace Artemis.Profiles.Layers.Types.Audio public AudioPropertiesViewModel(LayerEditorViewModel editorVm) : base(editorVm) { LayerAnimations = new BindableCollection(editorVm.LayerAnimations); + Devices = new BindableCollection(); SelectedLayerAnimation = LayerAnimations.FirstOrDefault(l => l.Name == editorVm.ProposedLayer.LayerAnimation?.Name) ?? LayerAnimations.First(l => l.Name == "None"); + + SetupAudioSelection(); + if (SelectedDevice == null) + SelectedDevice = Devices.First(); } public BindableCollection LayerAnimations { get; set; } + public BindableCollection Devices { get; set; } public ILayerAnimation SelectedLayerAnimation { @@ -32,6 +38,45 @@ namespace Artemis.Profiles.Layers.Types.Audio } } + public MmDeviceType DeviceType + { + get { return ((AudioPropertiesModel) LayerModel.Properties).DeviceType; } + set + { + if (value == ((AudioPropertiesModel) LayerModel.Properties).DeviceType) return; + ((AudioPropertiesModel) LayerModel.Properties).DeviceType = value; + SetupAudioSelection(); + SelectedDevice = Devices.First(); + NotifyOfPropertyChange(() => DeviceType); + } + } + + public string SelectedDevice + { + get { return ((AudioPropertiesModel) LayerModel.Properties).Device; } + set + { + if (value == ((AudioPropertiesModel) LayerModel.Properties).Device) return; + ((AudioPropertiesModel) LayerModel.Properties).Device = value; + NotifyOfPropertyChange(() => SelectedDevice); + } + } + + private void SetupAudioSelection() + { + var properties = (AudioPropertiesModel) LayerModel.Properties; + + Devices.Clear(); + Devices.Add("Default"); + + // Select the proper devices and make sure they are unique + Devices.AddRange(properties.DeviceType == MmDeviceType.Input + ? MMDeviceEnumerator.EnumerateDevices(DataFlow.Capture, DeviceState.Active) + .Select(d => d.FriendlyName).GroupBy(d => d).Select(g => g.First()) + : MMDeviceEnumerator.EnumerateDevices(DataFlow.Render, DeviceState.Active) + .Select(d => d.FriendlyName).GroupBy(d => d).Select(g => g.First())); + } + public override void ApplyProperties() { LayerModel.Properties.Brush = Brush; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index 058b2ca65..eb0b6ef5d 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; +using System.Collections.Generic; +using System.Linq; using System.Windows; using System.Windows.Media; using Artemis.Events; @@ -15,7 +13,6 @@ using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; using Artemis.Properties; using Artemis.Utilities; using Artemis.ViewModels; -using Artemis.ViewModels.Profiles; using CSCore.CoreAudioAPI; namespace Artemis.Profiles.Layers.Types.Audio @@ -27,22 +24,21 @@ namespace Artemis.Profiles.Layers.Types.Audio private int _lines; private LineSpectrum _lineSpectrum; private List _lineValues; + private AudioPropertiesModel _properties; + private bool _subscribed; public AudioType(AudioCaptureManager audioCaptureManager) { _audioCaptureManager = audioCaptureManager; - - // TODO: Setup according to settings - _audioCapture = _audioCaptureManager.GetAudioCapture(MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia)); - _audioCaptureManager.AudioDeviceChanged += OnAudioDeviceChanged; } - private void OnAudioDeviceChanged(object sender, AudioDeviceChangedEventArgs e) + private void SubscribeToAudioChange() { - // TODO: Check if layer must use default - // TODO: Check recording type - _audioCapture = _audioCaptureManager.GetAudioCapture(e.DefaultPlayback); - _lines = 0; + if (_subscribed) + return; + + _audioCaptureManager.AudioDeviceChanged += OnAudioDeviceChanged; + _subscribed = true; } public string Name => "Keyboard - Audio visualization"; @@ -73,7 +69,7 @@ namespace Artemis.Profiles.Layers.Types.Audio var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; // Create a geometry that will be formed by all the bars - GeometryGroup barGeometry = new GeometryGroup(); + var barGeometry = new GeometryGroup(); switch (direction) { @@ -121,6 +117,25 @@ namespace Artemis.Profiles.Layers.Types.Audio public void Update(LayerModel layerModel, ModuleDataModel dataModel, bool isPreview = false) { layerModel.ApplyProperties(true); + var newProperties = (AudioPropertiesModel) layerModel.Properties; + if (_properties == null) + _properties = newProperties; + + SubscribeToAudioChange(); + + if (_audioCapture == null || newProperties.Device != _properties.Device || + newProperties.DeviceType != _properties.DeviceType) + { + var device = GetMmDevice(); + if (device != null) + _audioCapture = _audioCaptureManager.GetAudioCapture(device, newProperties.DeviceType); + } + + _properties = newProperties; + + if (_audioCapture == null) + return; + _audioCapture.Pulse(); var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; @@ -142,6 +157,8 @@ namespace Artemis.Profiles.Layers.Types.Audio { _lines = currentLines; _lineSpectrum = _audioCapture.GetLineSpectrum(_lines, ScalingStrategy.Decibel); + if (_lineSpectrum == null) + return; } var newLineValues = _lineSpectrum?.GetLineValues(currentHeight); @@ -183,8 +200,8 @@ namespace Artemis.Profiles.Layers.Types.Audio // Otherwise draw the rectangle with its layer.AppliedProperties dimensions and brush var rect = layerModel.Properties.Contain ? layerModel.LayerRect() - : new Rect(layerModel.Properties.X*4, layerModel.Properties.Y*4, - layerModel.Properties.Width*4, layerModel.Properties.Height*4); + : new Rect(layerModel.Properties.X * 4, layerModel.Properties.Y * 4, + layerModel.Properties.Width * 4, layerModel.Properties.Height * 4); var clip = layerModel.LayerRect(DrawScale); @@ -196,5 +213,40 @@ namespace Artemis.Profiles.Layers.Types.Audio c.DrawRectangle(brush, null, rect); c.Pop(); } + + private void OnAudioDeviceChanged(object sender, AudioDeviceChangedEventArgs e) + { + if (_properties == null || _properties.Device != "Default") + return; + + if (_properties.DeviceType == MmDeviceType.Input) + { + if (e.DefaultRecording != null) + _audioCapture = _audioCaptureManager.GetAudioCapture(e.DefaultRecording, MmDeviceType.Input); + } + else + { + if (e.DefaultPlayback != null) + _audioCapture = _audioCaptureManager.GetAudioCapture(e.DefaultPlayback, MmDeviceType.Ouput); + } + + _lines = 0; + } + + private MMDevice GetMmDevice() + { + if (_properties == null) + return null; + + if (_properties.DeviceType == MmDeviceType.Input) + return _properties.Device == "Default" + ? MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia) + : MMDeviceEnumerator.EnumerateDevices(DataFlow.Capture) + .FirstOrDefault(d => d.FriendlyName == _properties.Device); + return _properties.Device == "Default" + ? MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia) + : MMDeviceEnumerator.EnumerateDevices(DataFlow.Render) + .FirstOrDefault(d => d.FriendlyName == _properties.Device); + } } } \ No newline at end of file From f090246c2bb9c05a32e39f28d26e578853d464a1 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Tue, 24 Jan 2017 12:17:22 +0100 Subject: [PATCH 28/32] Eurotruck fixes --- Artemis/Artemis/Modules/Abstract/ModuleModel.cs | 4 ++++ .../EurotruckSimulator2DataModel.cs | 9 ++++++++- .../EurotruckSimulator2Model.cs | 15 +++++++++++++-- .../Profiles/LayerConditionViewModel.cs | 1 + 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs index 9a8bf6599..809865cc5 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs @@ -205,6 +205,10 @@ namespace Artemis.Modules.Abstract _lastTrace = DateTime.Now; var dmJson = JsonConvert.SerializeObject(DataModel, Formatting.Indented); Logger.Trace("Effect datamodel as JSON: \r\n{0}", dmJson); + + if (layers == null) + return; + Logger.Trace("Effect {0} has to render {1} layers", Name, layers.Count); foreach (var renderLayer in layers) Logger.Trace("- Layer name: {0}, layer type: {1}", renderLayer.Name, renderLayer.LayerType); diff --git a/Artemis/Artemis/Modules/Games/EurotruckSimulator2/EurotruckSimulator2DataModel.cs b/Artemis/Artemis/Modules/Games/EurotruckSimulator2/EurotruckSimulator2DataModel.cs index d15fd4882..bb45aab72 100644 --- a/Artemis/Artemis/Modules/Games/EurotruckSimulator2/EurotruckSimulator2DataModel.cs +++ b/Artemis/Artemis/Modules/Games/EurotruckSimulator2/EurotruckSimulator2DataModel.cs @@ -17,10 +17,17 @@ namespace Artemis.Modules.Games.EurotruckSimulator2 UserData.RegisterType(); } + public TruckSimulatorGameName GameName { get; set; } public IEts2Game Game { get; set; } public IEts2Job Job { get; set; } public IEts2Navigation Navigation { get; set; } public IEts2Trailer Trailer { get; set; } - public IEts2Truck Truck { get; set; } + public IEts2Truck Truck { get; set; } + + public enum TruckSimulatorGameName + { + EuroTruckSimulator2, + AmericanTruckSimulator + } } } \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Games/EurotruckSimulator2/EurotruckSimulator2Model.cs b/Artemis/Artemis/Modules/Games/EurotruckSimulator2/EurotruckSimulator2Model.cs index 3c7b18606..b8a38ff02 100644 --- a/Artemis/Artemis/Modules/Games/EurotruckSimulator2/EurotruckSimulator2Model.cs +++ b/Artemis/Artemis/Modules/Games/EurotruckSimulator2/EurotruckSimulator2Model.cs @@ -7,6 +7,8 @@ using Artemis.Modules.Games.EurotruckSimulator2.Data; using Artemis.Properties; using Artemis.Services; using Artemis.Utilities; +using Artemis.Utilities.Memory; +using static Artemis.Modules.Games.EurotruckSimulator2.EurotruckSimulator2DataModel; namespace Artemis.Modules.Games.EurotruckSimulator2 { @@ -35,9 +37,18 @@ namespace Artemis.Modules.Games.EurotruckSimulator2 public override void Update() { + var etsProcess = MemoryHelpers.GetProcessIfRunning("eurotrucks2"); + var atsProcess = MemoryHelpers.GetProcessIfRunning("amtrucks"); + if (etsProcess == null && atsProcess == null) + return; + var dataModel = (EurotruckSimulator2DataModel) DataModel; var telemetryData = Ets2TelemetryDataReader.Instance.Read(); + dataModel.GameName = etsProcess != null + ? TruckSimulatorGameName.EuroTruckSimulator2 + : TruckSimulatorGameName.AmericanTruckSimulator; + dataModel.Game = telemetryData.Game; dataModel.Job = telemetryData.Job; dataModel.Navigation = telemetryData.Navigation; @@ -64,10 +75,10 @@ namespace Artemis.Modules.Games.EurotruckSimulator2 public void FindAtsGameDir() { - // Demo is also supported but resides in a different directory, the full game can also be 64-bits + // Demo is also supported but resides in a different directory, the full game can also be 32-bits (I think) var dir = GeneralHelpers.FindSteamGame(@"\American Truck Simulator\bin\win_x64\amtrucks.exe") ?? GeneralHelpers.FindSteamGame(@"\American Truck Simulator\bin\win_x86\amtrucks.exe") ?? - GeneralHelpers.FindSteamGame(@"\American Truck Simulator Demo\bin\win_x86\amtrucks.exe"); + GeneralHelpers.FindSteamGame(@"\American Truck Simulator Demo\bin\win_x64\amtrucks.exe"); if (string.IsNullOrEmpty(dir)) return; diff --git a/Artemis/Artemis/ViewModels/Profiles/LayerConditionViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/LayerConditionViewModel.cs index 113006d04..b307e44c3 100644 --- a/Artemis/Artemis/ViewModels/Profiles/LayerConditionViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/LayerConditionViewModel.cs @@ -226,6 +226,7 @@ namespace Artemis.ViewModels.Profiles } else if (SelectedDataModelProp.EnumValues != null) { + Enums.Clear(); Enums.AddRange(SelectedDataModelProp.EnumValues); EnumValueIsVisible = true; } From e8467ddf589e1bb6231a742ff65ebf830b9c7bec Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Tue, 24 Jan 2017 17:11:39 +0100 Subject: [PATCH 29/32] Added WindowsProfile migration to GeneralProfile Added default profiles for OverlayProfile --- Artemis/Artemis/Artemis.csproj | 9 ++-- Artemis/Artemis/DAL/ProfileProvider.cs | 6 +-- .../InjectionModules/ManagerModules.cs | 1 + Artemis/Artemis/Managers/MainManager.cs | 9 ++-- Artemis/Artemis/Managers/MigrationManager.cs | 40 ++++++++++++++++++ Artemis/Artemis/Properties/AssemblyInfo.cs | 4 +- .../Resources/Keyboards/default-profiles.zip | Bin 434888 -> 448148 bytes .../Utilities/Keyboard/KeyboardHook.cs | 11 ++--- Artemis/Artemis/packages.config | 2 +- 9 files changed, 63 insertions(+), 19 deletions(-) create mode 100644 Artemis/Artemis/Managers/MigrationManager.cs diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 4642e480e..90b14a29b 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -247,6 +247,10 @@ ..\packages\squirrel.windows.1.4.4\lib\Net45\NuGet.Squirrel.dll True + + ..\packages\Open.WinKeyboardHook.1.0.11\lib\net45\Open.WinKeyboardHook.dll + True + ..\packages\Process.NET.1.0.5\lib\Process.NET.dll True @@ -298,10 +302,6 @@ ..\packages\WpfExceptionViewer.1.0.0.0\lib\VioletTape.WpfExceptionViewer.dll True - - ..\packages\VirtualInput.1.0.1\lib\net20\VirtualInput.dll - True - @@ -354,6 +354,7 @@ + diff --git a/Artemis/Artemis/DAL/ProfileProvider.cs b/Artemis/Artemis/DAL/ProfileProvider.cs index 2721ae7d6..b1fc66710 100644 --- a/Artemis/Artemis/DAL/ProfileProvider.cs +++ b/Artemis/Artemis/DAL/ProfileProvider.cs @@ -21,7 +21,7 @@ namespace Artemis.DAL { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private static readonly string ProfileFolder = + public static readonly string ProfileFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + @"\Artemis\profiles"; private static bool _installedDefaults; @@ -33,7 +33,7 @@ namespace Artemis.DAL CheckProfiles(); InstallDefaults(); } - + public static List GetProfileNames(KeyboardProvider keyboard, ModuleModel module) { if (keyboard == null || module == null) @@ -177,7 +177,7 @@ namespace Artemis.DAL } } - private static List ReadProfiles(string subDirectory) + public static List ReadProfiles(string subDirectory) { var profiles = new List(); var directory = ProfileFolder + "/" + subDirectory; diff --git a/Artemis/Artemis/InjectionModules/ManagerModules.cs b/Artemis/Artemis/InjectionModules/ManagerModules.cs index 6f6b1c1b3..c1e788ba7 100644 --- a/Artemis/Artemis/InjectionModules/ManagerModules.cs +++ b/Artemis/Artemis/InjectionModules/ManagerModules.cs @@ -14,6 +14,7 @@ namespace Artemis.InjectionModules Bind().ToSelf().InSingletonScope(); Bind().ToSelf().InSingletonScope(); Bind().ToSelf().InSingletonScope(); + Bind().ToSelf().InSingletonScope(); } } } \ No newline at end of file diff --git a/Artemis/Artemis/Managers/MainManager.cs b/Artemis/Artemis/Managers/MainManager.cs index c266bb4d8..dba3b4657 100644 --- a/Artemis/Artemis/Managers/MainManager.cs +++ b/Artemis/Artemis/Managers/MainManager.cs @@ -19,12 +19,14 @@ namespace Artemis.Managers ///
public class MainManager : IDisposable { + private readonly MigrationManager _migrationManager; private readonly Timer _processTimer; public MainManager(ILogger logger, LoopManager loopManager, DeviceManager deviceManager, - ModuleManager moduleManager, PreviewManager previewManager, PipeServer pipeServer, - GameStateWebServer gameStateWebServer) + ModuleManager moduleManager, PreviewManager previewManager, MigrationManager migrationManager, + PipeServer pipeServer, GameStateWebServer gameStateWebServer) { + _migrationManager = migrationManager; Logger = logger; LoopManager = loopManager; DeviceManager = deviceManager; @@ -111,6 +113,7 @@ namespace Artemis.Managers ProgramEnabled = true; await LoopManager.StartAsync(); + _migrationManager.MigrateProfiles(); RaiseEnabledChangedEvent(new EnabledChangedEventArgs(ProgramEnabled)); } @@ -121,10 +124,8 @@ namespace Artemis.Managers { Logger.Debug("Disabling program"); foreach (var overlayModule in ModuleManager.OverlayModules) - { if (overlayModule.Settings.IsEnabled) overlayModule.Dispose(); - } LoopManager.Stop(); ProgramEnabled = false; RaiseEnabledChangedEvent(new EnabledChangedEventArgs(ProgramEnabled)); diff --git a/Artemis/Artemis/Managers/MigrationManager.cs b/Artemis/Artemis/Managers/MigrationManager.cs new file mode 100644 index 000000000..b314b6952 --- /dev/null +++ b/Artemis/Artemis/Managers/MigrationManager.cs @@ -0,0 +1,40 @@ +using System.IO; +using Artemis.DAL; + +namespace Artemis.Managers +{ + public class MigrationManager + { + private readonly DeviceManager _deviceManager; + + public MigrationManager(DeviceManager deviceManager) + { + _deviceManager = deviceManager; + } + + /// + /// Migrates old versions of profiles to new versions + /// + public void MigrateProfiles() + { + // 1.8.0.0 - Rename WindowsProfile to GeneralProfile + foreach (var keyboardProvider in _deviceManager.KeyboardProviders) + { + var folder = ProfileProvider.ProfileFolder + "/" + keyboardProvider.Slug + "/WindowsProfile"; + if (!Directory.Exists(folder)) + continue; + + // Get all the profiles + var profiles = ProfileProvider.ReadProfiles(keyboardProvider.Slug + "/WindowsProfile"); + foreach (var profile in profiles) + { + // Change their GameName and save, effectively moving them to the new folder + profile.GameName = "GeneralProfile"; + ProfileProvider.AddOrUpdate(profile); + } + // Delete the old profiles + Directory.Delete(folder, true); + } + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Properties/AssemblyInfo.cs b/Artemis/Artemis/Properties/AssemblyInfo.cs index 7205a0d2c..b89de5c2b 100644 --- a/Artemis/Artemis/Properties/AssemblyInfo.cs +++ b/Artemis/Artemis/Properties/AssemblyInfo.cs @@ -53,7 +53,7 @@ using System.Windows; // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.7.1.0")] -[assembly: AssemblyFileVersion("1.7.1.0")] +[assembly: AssemblyVersion("1.8.0.0")] +[assembly: AssemblyFileVersion("1.8.0.0")] [assembly: InternalsVisibleTo("Artemis.Explorables")] diff --git a/Artemis/Artemis/Resources/Keyboards/default-profiles.zip b/Artemis/Artemis/Resources/Keyboards/default-profiles.zip index a675632a6fb9d7f03d39c03073bb0a56ddaff366..9520a5698ed6ae6c72bd0ecbc286e4d7ed69978f 100644 GIT binary patch delta 12494 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 diff --git a/Artemis/Artemis/Utilities/Keyboard/KeyboardHook.cs b/Artemis/Artemis/Utilities/Keyboard/KeyboardHook.cs index 326552cae..13555234f 100644 --- a/Artemis/Artemis/Utilities/Keyboard/KeyboardHook.cs +++ b/Artemis/Artemis/Utilities/Keyboard/KeyboardHook.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; using System.Windows.Forms; -using VirtualInput; +using Open.WinKeyboardHook; namespace Artemis.Utilities.Keyboard { @@ -10,13 +10,14 @@ namespace Artemis.Utilities.Keyboard static KeyboardHook() { - VirtualKeyboard.StartInterceptor(); - VirtualKeyboard.KeyDown += VirtualKeyboardOnKeyDown; + var interceptor = new KeyboardInterceptor(); + interceptor.KeyDown += VirtualKeyboardOnKeyDown; + interceptor.StartCapturing(); } - private static void VirtualKeyboardOnKeyDown(object sender, KeyEventArgs keyEventArgs) + private static async void VirtualKeyboardOnKeyDown(object sender, KeyEventArgs keyEventArgs) { - Task.Factory.StartNew(() => { KeyDownCallback?.Invoke(keyEventArgs); }); + await Task.Factory.StartNew(() => { KeyDownCallback?.Invoke(keyEventArgs); }); } public static event KeyDownCallbackHandler KeyDownCallback; diff --git a/Artemis/Artemis/packages.config b/Artemis/Artemis/packages.config index 55b725806..5afbe906d 100644 --- a/Artemis/Artemis/packages.config +++ b/Artemis/Artemis/packages.config @@ -23,12 +23,12 @@ + - \ No newline at end of file From 182d710d16d83b9d1738b0b693681452a08d89dc Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Tue, 24 Jan 2017 18:27:51 +0100 Subject: [PATCH 30/32] Updated Nuget packages Fixed keyboard hook --- Artemis/Artemis.sln | 2 +- Artemis/Artemis/Artemis.csproj | 43 +++---- Artemis/Artemis/ArtemisBootstrapper.cs | 3 + .../DeviceProviders/Corsair/CorsairHeadset.cs | 2 +- .../Corsair/CorsairKeyboard.cs | 20 ++-- .../DeviceProviders/Corsair/CorsairMouse.cs | 2 +- .../Corsair/CorsairMousemat.cs | 2 +- .../Corsair/Utilities/KeyMap.cs | 112 +++++++++--------- .../Utilities/Keyboard/KeyboardHook.cs | 10 +- Artemis/Artemis/packages.config | 16 +-- 10 files changed, 109 insertions(+), 103 deletions(-) diff --git a/Artemis/Artemis.sln b/Artemis/Artemis.sln index 785b97bb9..e10b6c3a6 100644 --- a/Artemis/Artemis.sln +++ b/Artemis/Artemis.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis", "Artemis\Artemis.csproj", "{ED9997A2-E54C-4E9F-9350-62BE672C3ABE}" EndProject diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 90b14a29b..1e17bdd9a 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -135,16 +135,16 @@ ..\packages\Betwixt.1.4.1\lib\net35\Betwixt.dll True - - ..\packages\Caliburn.Micro.Core.3.0.2\lib\net45\Caliburn.Micro.dll + + ..\packages\Caliburn.Micro.Core.3.0.3\lib\net45\Caliburn.Micro.dll True - - ..\packages\Caliburn.Micro.3.0.2\lib\net45\Caliburn.Micro.Platform.dll + + ..\packages\Caliburn.Micro.3.0.3\lib\net45\Caliburn.Micro.Platform.dll True - - ..\packages\Caliburn.Micro.3.0.2\lib\net45\Caliburn.Micro.Platform.Core.dll + + ..\packages\Caliburn.Micro.3.0.3\lib\net45\Caliburn.Micro.Platform.Core.dll True @@ -159,8 +159,9 @@ ..\packages\CSCore.1.1.0\lib\net35-client\CSCore.dll True - - ..\packages\CUE.NET.1.1.0.2\lib\net45\CUE.NET.dll + + False + ..\packages\CUE.NET.1.1.1\lib\net45\CUE.NET.dll True @@ -176,7 +177,7 @@ True - ..\packages\DynamicExpresso.Core.1.3.3.4\lib\net40\DynamicExpresso.Core.dll + ..\packages\DynamicExpresso.Core.1.3.3.5\lib\net40\DynamicExpresso.Core.dll True @@ -188,15 +189,15 @@ True - ..\packages\squirrel.windows.1.4.4\lib\Net45\ICSharpCode.SharpZipLib.dll + ..\packages\squirrel.windows.1.5.1\lib\Net45\ICSharpCode.SharpZipLib.dll True - - ..\packages\log4net.2.0.5\lib\net45-full\log4net.dll + + ..\packages\log4net.2.0.7\lib\net45-full\log4net.dll True - - ..\packages\MahApps.Metro.1.3.0\lib\net45\MahApps.Metro.dll + + ..\packages\MahApps.Metro.1.4.1\lib\net45\MahApps.Metro.dll True @@ -244,7 +245,7 @@ True - ..\packages\squirrel.windows.1.4.4\lib\Net45\NuGet.Squirrel.dll + ..\packages\squirrel.windows.1.5.1\lib\Net45\NuGet.Squirrel.dll True @@ -252,7 +253,7 @@ True - ..\packages\Process.NET.1.0.5\lib\Process.NET.dll + ..\packages\Process.NET.1.0.8\lib\Process.NET.dll True @@ -271,8 +272,8 @@ ..\packages\SpotifyAPI-NET.2.12.0\lib\SpotifyAPI.dll True - - ..\packages\squirrel.windows.1.4.4\lib\Net45\Squirrel.dll + + ..\packages\squirrel.windows.1.5.1\lib\Net45\Squirrel.dll True @@ -286,7 +287,7 @@ - ..\packages\MahApps.Metro.1.3.0\lib\net45\System.Windows.Interactivity.dll + ..\packages\MahApps.Metro.1.4.1\lib\net45\System.Windows.Interactivity.dll True @@ -1033,12 +1034,12 @@ - + 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}. - + 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 Date: Thu, 26 Jan 2017 10:49:44 +0100 Subject: [PATCH 32/32] Fix ATS directory overwrite --- .../EurotruckSimulator2ViewModel.cs | 120 +++++++++--------- .../Audio/AudioCapturing/LineSpectrum.cs | 3 +- 2 files changed, 61 insertions(+), 62 deletions(-) diff --git a/Artemis/Artemis/Modules/Games/EurotruckSimulator2/EurotruckSimulator2ViewModel.cs b/Artemis/Artemis/Modules/Games/EurotruckSimulator2/EurotruckSimulator2ViewModel.cs index 6371900b5..0d40fbd24 100644 --- a/Artemis/Artemis/Modules/Games/EurotruckSimulator2/EurotruckSimulator2ViewModel.cs +++ b/Artemis/Artemis/Modules/Games/EurotruckSimulator2/EurotruckSimulator2ViewModel.cs @@ -1,63 +1,63 @@ -using System.Windows.Forms; -using Artemis.Managers; -using Artemis.Modules.Abstract; -using Ninject; - -namespace Artemis.Modules.Games.EurotruckSimulator2 -{ - public sealed class EurotruckSimulator2ViewModel : ModuleViewModel - { - public EurotruckSimulator2ViewModel(MainManager mainManager, - [Named(nameof(EurotruckSimulator2Model))] ModuleModel moduleModel, - IKernel kernel) : base(mainManager, moduleModel, kernel) - { - DisplayName = "Truck Simulator"; - } - - public override bool UsesProfileEditor => true; - - public void Ets2BrowseDirectory() - { - var settings = (EurotruckSimulator2Settings) Settings; - var model = (EurotruckSimulator2Model) ModuleModel; - - var dialog = new FolderBrowserDialog {SelectedPath = settings.Ets2GameDirectory}; - var result = dialog.ShowDialog(); - if (result != DialogResult.OK) - return; - - settings.Ets2GameDirectory = dialog.SelectedPath; - NotifyOfPropertyChange(() => Settings); - Settings.Save(); - model.PlaceTruckSimulatorPlugin(settings.Ets2GameDirectory, "eurotrucks2.exe"); - } - - public void AtsBrowseDirectory() - { - var settings = (EurotruckSimulator2Settings) Settings; - var model = (EurotruckSimulator2Model)ModuleModel; - - var dialog = new FolderBrowserDialog {SelectedPath = settings.AtsGameDirectory}; - var result = dialog.ShowDialog(); - if (result != DialogResult.OK) - return; - - settings.AtsGameDirectory = dialog.SelectedPath; - NotifyOfPropertyChange(() => Settings); - Settings.Save(); - model.PlaceTruckSimulatorPlugin(settings.Ets2GameDirectory, "amtrucks.exe"); +using System.Windows.Forms; +using Artemis.Managers; +using Artemis.Modules.Abstract; +using Ninject; + +namespace Artemis.Modules.Games.EurotruckSimulator2 +{ + public sealed class EurotruckSimulator2ViewModel : ModuleViewModel + { + public EurotruckSimulator2ViewModel(MainManager mainManager, + [Named(nameof(EurotruckSimulator2Model))] ModuleModel moduleModel, + IKernel kernel) : base(mainManager, moduleModel, kernel) + { + DisplayName = "Truck Simulator"; } - public void Ets2PlacePlugin() - { - var ets2Path = ((EurotruckSimulator2Settings)Settings).Ets2GameDirectory; - ((EurotruckSimulator2Model)ModuleModel).PlaceTruckSimulatorPlugin(ets2Path, "eurotrucks2.exe"); - } - - public void AtsPlacePlugin() - { - var atsPath = ((EurotruckSimulator2Settings)Settings).AtsGameDirectory; - ((EurotruckSimulator2Model)ModuleModel).PlaceTruckSimulatorPlugin(atsPath, "amtrucks.exe"); - } - } + public override bool UsesProfileEditor => true; + + public void Ets2BrowseDirectory() + { + var settings = (EurotruckSimulator2Settings) Settings; + var model = (EurotruckSimulator2Model) ModuleModel; + + var dialog = new FolderBrowserDialog {SelectedPath = settings.Ets2GameDirectory}; + var result = dialog.ShowDialog(); + if (result != DialogResult.OK) + return; + + settings.Ets2GameDirectory = dialog.SelectedPath; + NotifyOfPropertyChange(() => Settings); + Settings.Save(); + model.PlaceTruckSimulatorPlugin(settings.Ets2GameDirectory, "eurotrucks2.exe"); + } + + public void AtsBrowseDirectory() + { + var settings = (EurotruckSimulator2Settings) Settings; + var model = (EurotruckSimulator2Model)ModuleModel; + + var dialog = new FolderBrowserDialog {SelectedPath = settings.AtsGameDirectory}; + var result = dialog.ShowDialog(); + if (result != DialogResult.OK) + return; + + settings.AtsGameDirectory = dialog.SelectedPath; + NotifyOfPropertyChange(() => Settings); + Settings.Save(); + model.PlaceTruckSimulatorPlugin(settings.AtsGameDirectory, "amtrucks.exe"); + } + + public void Ets2PlacePlugin() + { + var ets2Path = ((EurotruckSimulator2Settings)Settings).Ets2GameDirectory; + ((EurotruckSimulator2Model)ModuleModel).PlaceTruckSimulatorPlugin(ets2Path, "eurotrucks2.exe"); + } + + public void AtsPlacePlugin() + { + var atsPath = ((EurotruckSimulator2Settings)Settings).AtsGameDirectory; + ((EurotruckSimulator2Model)ModuleModel).PlaceTruckSimulatorPlugin(atsPath, "amtrucks.exe"); + } + } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs index 1830033be..a658f5761 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs @@ -36,9 +36,8 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing if (!SpectrumProvider.GetFftData(fftBuffer, this)) return null; - var spectrumPoints = CalculateSpectrumPoints(height, fftBuffer); + var spectrumPoints = CalculateSpectrumPoints(height, fftBuffer); return spectrumPoints?.Select(s => s.Value).ToList(); - } } } \ No newline at end of file