1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Performance improvements

This commit is contained in:
SpoinkyNL 2017-01-18 19:44:39 +01:00
parent 6f3d00c8d4
commit 201e65b3b5
13 changed files with 175 additions and 94 deletions

View File

@ -503,7 +503,7 @@
<Compile Include="Profiles\Layers\Types\ConicalBrush\ConicalBrushType.cs" />
<Compile Include="Profiles\Layers\Types\ConicalBrush\Drawing\ConicalGradientDrawer.cs" />
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\AudioCapture.cs" />
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\AudioCaptureManager.cs" />
<Compile Include="Managers\AudioCaptureManager.cs" />
<Compile Include="Events\AudioDeviceChangedEventArgs.cs" />
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\BaseSpectrumProvider.cs" />
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\ISpectrumProvider.cs" />

View File

@ -27,17 +27,11 @@ namespace Artemis.DeviceProviders
public abstract void Enable();
public abstract void DrawBitmap(Bitmap bitmap);
/// <summary>
/// Returns a bitmap matching the keyboard's dimensions
/// </summary>
/// <returns></returns>
public Bitmap KeyboardBitmap() => new Bitmap(Width, Height);
/// <summary>
/// Returns a bitmap matching the keyboard's dimensions using the provided scale
/// </summary>
/// <returns></returns>
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));

View File

@ -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<ILayerType>()
.BindToSelf());
// Type helpers
Bind<AudioCaptureManager>().ToSelf().InSingletonScope();
#endregion
#region Lua

View File

@ -13,6 +13,7 @@ namespace Artemis.InjectionModules
Bind<ModuleManager>().ToSelf().InSingletonScope();
Bind<PreviewManager>().ToSelf().InSingletonScope();
Bind<LuaManager>().ToSelf().InSingletonScope();
Bind<AudioCaptureManager>().ToSelf().InSingletonScope();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,51 +24,87 @@ namespace Artemis.Profiles.Layers.Models
_yTweener = new Tweener<float>((float) layerModel.Y, (float) layerModel.Y, 0);
_widthTweener = new Tweener<float>((float) layerModel.Width, (float) layerModel.Width, 0);
_heightTweener = new Tweener<float>((float) layerModel.Height, (float) layerModel.Height, 0);
_opacityTweener = new Tweener<float>((float) layerModel.Opacity, (float) layerModel.Opacity, 0);
StoreCurrentValues();
_opacityTweener = new Tweener<float>((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<float>(_xTweener.Value, (float) _layerModel.X, widthSpeed, widthFunc);
_widthTweener = new Tweener<float>(_widthTweener.Value, (float) _layerModel.Width, widthSpeed, widthFunc);
}
_xTweener = new Tweener<float>(_xTweener.Value, (float)_layerModel.X, widthSpeed, widthFunc);
_widthTweener = new Tweener<float>(_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<float>(_y, (float) _layerModel.Y, heightSpeed, heightFunc);
_heightTweener = new Tweener<float>(_height, (float) _layerModel.Height, heightSpeed, heightFunc);
}
_yTweener = new Tweener<float>(_y, (float)_layerModel.Y, heightSpeed, heightFunc);
_heightTweener = new Tweener<float>(_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<float>(_opacity, (float) _layerModel.Opacity,
_opacityTweener = new Tweener<float>(_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;
}

View File

@ -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
};
}
/// <summary>
/// Keeps the audio capture active, when not called for longer than 1 sec the capture will
/// stop capturing until Pulse is called again
/// </summary>
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();
}
}
}

View File

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

View File

@ -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;
/// <summary>
/// Resize the image to the specified width and height.
/// </summary>
@ -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;
}
/// <summary>