1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2026-01-02 10:43:31 +00:00

Audio layer progress

This commit is contained in:
SpoinkyNL 2017-01-11 22:34:05 +01:00
parent 1fa6063706
commit 0656687b88
17 changed files with 232 additions and 208 deletions

View File

@ -494,6 +494,7 @@
<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\SingleSpectrum.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">

View File

@ -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();
}

View File

@ -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);
}
}

View File

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

View File

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

View File

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

View File

@ -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.");
}
/// <summary>

View File

@ -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");
}
}
}

View File

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

View File

@ -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");
}
}
}
}

View File

@ -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,7 +26,7 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing
}
}
public void SetupLayersVertical(double height, List<LayerModel> audioLayers)
public void UpdateLinesVertical(double height, Point[] points)
{
var fftBuffer = new float[(int) FftSize];
@ -36,11 +35,14 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing
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<LayerModel> audioLayers)
public void UpdateLinesHorizontal(double width, Point[] points)
{
var fftBuffer = new float[(int) FftSize];
@ -49,8 +51,11 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing
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;
}
}
}
}

View File

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

View File

@ -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<LayerModel> _audioLayers = new List<LayerModel>();
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);
}
/// <summary>
/// Sets up the inner layers when the settings have changed
/// </summary>
/// <param name="layerModel"></param>
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);
}
}
}
}

View File

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

View File

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

View File

@ -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<LayerModel> _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();
}

View File

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