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 @@ -