mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
WIP on new audio layers
This commit is contained in:
parent
52afde89ec
commit
1fa6063706
@ -151,6 +151,10 @@
|
||||
<HintPath>..\packages\Colore.5.1.0\lib\net35\Corale.Colore.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="CSCore, Version=1.1.5992.18249, Culture=neutral, PublicKeyToken=5a08f2b6f4415dea, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CSCore.1.1.0\lib\net35-client\CSCore.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="CUE.NET, Version=1.1.0.2, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CUE.NET.1.1.0.2\lib\net45\CUE.NET.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
@ -211,10 +215,6 @@
|
||||
<HintPath>..\packages\MoonSharp.2.0.0.0\lib\net40-client\MoonSharp.Interpreter.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="NAudio, Version=1.7.3.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NAudio.1.7.3\lib\net35\NAudio.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
@ -489,9 +489,12 @@
|
||||
<Compile Include="Profiles\Layers\Types\AmbientLight\ScreenCapturing\DX9ScreenCapture.cs" />
|
||||
<Compile Include="Profiles\Layers\Types\AmbientLight\ScreenCapturing\IScreenCapture.cs" />
|
||||
<Compile Include="Profiles\Layers\Types\AmbientLight\ScreenCapturing\ScreenCaptureManager.cs" />
|
||||
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\AudioCapture.cs" />
|
||||
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\AudioCaptureManager.cs" />
|
||||
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\FftEventArgs.cs" />
|
||||
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\SampleAggregator.cs" />
|
||||
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\BaseSpectrumProvider.cs" />
|
||||
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\ISpectrumProvider.cs" />
|
||||
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\LineSpectrum.cs" />
|
||||
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\SpectrumBase.cs" />
|
||||
<Compile Include="Profiles\Layers\Types\Audio\AudioPropertiesModel.cs" />
|
||||
<Compile Include="Profiles\Layers\Types\Audio\AudioPropertiesView.xaml.cs">
|
||||
<DependentUpon>AudioPropertiesView.xaml</DependentUpon>
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -54,18 +54,21 @@ namespace Artemis.Modules.Games.RocketLeague
|
||||
{
|
||||
Updater.GetPointers();
|
||||
_pointer = SettingsProvider.Load<OffsetSettings>().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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<AudioCapture> _audioCaptures;
|
||||
|
||||
public AudioCaptureManager(ILogger logger)
|
||||
{
|
||||
Logger = logger;
|
||||
Device = new MMDeviceEnumerator().EnumerateAudioEndPoints(DataFlow.All, DeviceState.Active).FirstOrDefault();
|
||||
_audioCaptures = new List<AudioCapture>();
|
||||
}
|
||||
|
||||
_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<byte> GetSpectrumData(int lines)
|
||||
{
|
||||
_lastRequest = DateTime.Now;
|
||||
if (!Running)
|
||||
Start();
|
||||
|
||||
var spectrumData = new List<byte>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CSCore.DSP;
|
||||
|
||||
namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing
|
||||
{
|
||||
/// <summary>
|
||||
/// BasicSpectrumProvider
|
||||
/// </summary>
|
||||
public class BasicSpectrumProvider : FftProvider, ISpectrumProvider
|
||||
{
|
||||
private readonly List<object> _contexts = new List<object>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing
|
||||
{
|
||||
public interface ISpectrumProvider
|
||||
{
|
||||
bool GetFftData(float[] fftBuffer, object context);
|
||||
int GetFftBandIndex(float frequency);
|
||||
}
|
||||
}
|
||||
@ -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<LayerModel> 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<LayerModel> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<FftEventArgs> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<SpectrumPointData>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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<byte> 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<byte> 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++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the layer manually faking the width and height for a properly working animation
|
||||
/// </summary>
|
||||
/// <param name="settings"></param>
|
||||
/// <param name="audioLayer"></param>
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the inner layers when the settings have changed
|
||||
/// </summary>
|
||||
@ -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)
|
||||
|
||||
@ -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())
|
||||
{
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
<package id="Caliburn.Micro.Core" version="3.0.2" targetFramework="net461" />
|
||||
<package id="Castle.Core" version="3.3.3" targetFramework="net452" />
|
||||
<package id="Colore" version="5.1.0" targetFramework="net461" />
|
||||
<package id="CSCore" version="1.1.0" targetFramework="net461" />
|
||||
<package id="CUE.NET" version="1.1.0.2" targetFramework="net461" />
|
||||
<package id="DeltaCompressionDotNet" version="1.1.0" targetFramework="net461" />
|
||||
<package id="DynamicExpresso.Core" version="1.3.3.4" targetFramework="net461" />
|
||||
@ -15,7 +16,6 @@
|
||||
<package id="MahApps.Metro.Resources" version="0.6.1.0" targetFramework="net452" />
|
||||
<package id="Mono.Cecil" version="0.9.6.4" targetFramework="net461" />
|
||||
<package id="MoonSharp" version="2.0.0.0" targetFramework="net461" />
|
||||
<package id="NAudio" version="1.7.3" targetFramework="net452" />
|
||||
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" />
|
||||
<package id="Ninject" version="3.2.2.0" targetFramework="net452" />
|
||||
<package id="Ninject.Extensions.Conventions" version="3.2.0.0" targetFramework="net461" />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user