mirror of
https://github.com/vale981/VoiDPlugins
synced 2025-03-04 16:51:38 -05:00
Add Lagrange, and various prep changes
This commit is contained in:
parent
c1820d15f2
commit
250c0ff6d3
10 changed files with 186 additions and 100 deletions
40
Filter/Lagrange/Lagrange.cs
Normal file
40
Filter/Lagrange/Lagrange.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
using OpenTabletDriver.Plugin.Attributes;
|
||||
using OpenTabletDriver.Plugin.Tablet.Interpolator;
|
||||
using OpenTabletDriver.Plugin.Timers;
|
||||
|
||||
namespace VoiDPlugins.Filter
|
||||
{
|
||||
[PluginName("Lagrange")]
|
||||
public class Lagrange : Interpolator
|
||||
{
|
||||
private readonly LagrangeCore Core = new();
|
||||
|
||||
private SyntheticTabletReport Report;
|
||||
|
||||
public Lagrange(ITimer timer) : base(timer)
|
||||
{
|
||||
}
|
||||
|
||||
public override SyntheticTabletReport Interpolate()
|
||||
{
|
||||
if (Core.IsFilled)
|
||||
Report.Position = Core.Predict();
|
||||
return Report;
|
||||
}
|
||||
|
||||
public override void UpdateState(SyntheticTabletReport report)
|
||||
{
|
||||
Report = report;
|
||||
Core.Add(report.Position);
|
||||
}
|
||||
|
||||
[Property("Samples")]
|
||||
public int Samples { set => Core.Samples = value; }
|
||||
|
||||
[Property("Offset"), Unit("ms")]
|
||||
public float Offset { get; set; }
|
||||
|
||||
[BooleanProperty("Mode 2", "Enable if tablet report is jittery. (most of 266+hz tablets)")]
|
||||
public bool Jitter { set => Core.Jitter = value; }
|
||||
}
|
||||
}
|
12
Filter/Lagrange/Lagrange.csproj
Normal file
12
Filter/Lagrange/Lagrange.csproj
Normal file
|
@ -0,0 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\.modules\OpenTabletDriver\OpenTabletDriver.Plugin\OpenTabletDriver.Plugin.csproj" />
|
||||
<ProjectReference Include="..\..\VoiDPlugins.Library\VoiD\VoiD.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
70
Filter/Lagrange/LagrangeCore.cs
Normal file
70
Filter/Lagrange/LagrangeCore.cs
Normal file
|
@ -0,0 +1,70 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using VoiDPlugins.Library;
|
||||
|
||||
namespace VoiDPlugins.Filter
|
||||
{
|
||||
public class LagrangeCore
|
||||
{
|
||||
private RingBuffer<TimeSeriesPoint> Points;
|
||||
private readonly Stopwatch Watch = new();
|
||||
private float? prevElapsed;
|
||||
public int Samples { set => Points = new(value); }
|
||||
public bool Jitter;
|
||||
public bool IsFilled { get => Points.IsFilled; }
|
||||
public float TimeNow { get => (float)Watch.Elapsed.TotalMilliseconds; }
|
||||
public float ReportInterval = 4;
|
||||
|
||||
public LagrangeCore()
|
||||
{
|
||||
Watch.Start();
|
||||
}
|
||||
|
||||
~LagrangeCore()
|
||||
{
|
||||
Watch.Stop();
|
||||
}
|
||||
|
||||
public void Add(Vector2 point)
|
||||
{
|
||||
var now = TimeNow;
|
||||
Points.Insert(new TimeSeriesPoint(point, now));
|
||||
if (prevElapsed.HasValue)
|
||||
{
|
||||
var interval = now - prevElapsed;
|
||||
if (interval < ReportInterval * 1.5)
|
||||
ReportInterval += (float)((interval - ReportInterval) * 0.1);
|
||||
}
|
||||
prevElapsed = now;
|
||||
}
|
||||
|
||||
public Vector2 Predict(float offset = 0)
|
||||
{
|
||||
Vector2 lagrange = new(0, 0);
|
||||
var i = 0;
|
||||
foreach (var z in Points)
|
||||
{
|
||||
lagrange += z.Point * Decompose(TimeNow + offset, (float)z.Elapsed, i);
|
||||
i++;
|
||||
}
|
||||
|
||||
return lagrange;
|
||||
}
|
||||
|
||||
private float Decompose(float interpTime, float time, int decompIndex)
|
||||
{
|
||||
float decomposed = 1;
|
||||
int i = 0;
|
||||
foreach (var point in Points)
|
||||
{
|
||||
if (i != decompIndex)
|
||||
{
|
||||
decomposed *= (float)((interpTime - point.Elapsed) / (time - point.Elapsed));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return decomposed;
|
||||
}
|
||||
}
|
||||
}
|
16
Filter/Lagrange/TimeSeriesPoint.cs
Normal file
16
Filter/Lagrange/TimeSeriesPoint.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace VoiDPlugins.Filter
|
||||
{
|
||||
public class TimeSeriesPoint
|
||||
{
|
||||
public TimeSeriesPoint(Vector2 point, double elapsed)
|
||||
{
|
||||
Point = point;
|
||||
Elapsed = elapsed;
|
||||
}
|
||||
|
||||
public readonly Vector2 Point;
|
||||
public double Elapsed;
|
||||
}
|
||||
}
|
|
@ -49,7 +49,7 @@ namespace VoiDPlugins.Filter.MeL.Core
|
|||
{
|
||||
var predicted = new Vector2();
|
||||
double predictAhead;
|
||||
predictAhead = TimeNow - this.timeSeriesPoints.PeekFirst().Elapsed + offset;
|
||||
predictAhead = TimeNow - this.timeSeriesPoints[0].Elapsed + offset;
|
||||
|
||||
predicted.X = (float)this.xCoeff.Evaluate(predictAhead);
|
||||
predicted.Y = (float)this.yCoeff.Evaluate(predictAhead);
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace VoiDPlugins.Filter.MeL.Core
|
|||
|
||||
private double[] ConstructTimeDesignMatrix()
|
||||
{
|
||||
var baseTime = this.timeSeriesPoints.PeekFirst().Elapsed;
|
||||
var baseTime = this.timeSeriesPoints[0].Elapsed;
|
||||
var data = new double[Samples];
|
||||
var index = 0;
|
||||
foreach (var timePoint in this.timeSeriesPoints)
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
namespace VoiDPlugins.Filter
|
||||
{
|
||||
enum ReconstructionAlg
|
||||
{
|
||||
ReverseMA,
|
||||
ReverseEMA
|
||||
}
|
||||
}
|
|
@ -1,47 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using OpenTabletDriver.Plugin.Attributes;
|
||||
using OpenTabletDriver.Plugin.Tablet;
|
||||
using VoiDPlugins.Library;
|
||||
|
||||
namespace VoiDPlugins.Filter
|
||||
{
|
||||
[PluginName("Reconstructor")]
|
||||
public class Reconstructor : IFilter
|
||||
{
|
||||
private RingBuffer<Vector2> truePoints;
|
||||
private Vector2? lastAvg;
|
||||
private ReconstructionAlg mode;
|
||||
|
||||
public Vector2 Filter(Vector2 point)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case ReconstructionAlg.ReverseMA:
|
||||
{
|
||||
var truePoint = truePoints.IsFilled ? ReverseMAFunc(truePoints, point, MAWindow) : point;
|
||||
truePoints.Insert(truePoint);
|
||||
return truePoint;
|
||||
}
|
||||
case ReconstructionAlg.ReverseEMA:
|
||||
{
|
||||
var truePoint = lastAvg.HasValue ? ReverseEMAFunc(point, lastAvg.Value, (float)EMAWeight) : point;
|
||||
lastAvg = point;
|
||||
return truePoint;
|
||||
}
|
||||
default:
|
||||
return point;
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector2 ReverseMAFunc(IEnumerable<Vector2> trueHistory, Vector2 input, int window)
|
||||
{
|
||||
Vector2 sum = new Vector2();
|
||||
foreach (var item in trueHistory)
|
||||
sum += item;
|
||||
|
||||
return (input * window) - sum;
|
||||
var truePoint = lastAvg.HasValue ? ReverseEMAFunc(point, lastAvg.Value, (float)EMAWeight) : point;
|
||||
lastAvg = point;
|
||||
return truePoint;
|
||||
}
|
||||
|
||||
private static Vector2 ReverseEMAFunc(Vector2 currentEMA, Vector2 lastEMA, float weight)
|
||||
|
@ -49,52 +22,6 @@ namespace VoiDPlugins.Filter
|
|||
return ((currentEMA - lastEMA) / weight) + lastEMA;
|
||||
}
|
||||
|
||||
[BooleanProperty("Reverse MA", "Set to True if the tablet is using MA algorithm for smoothing/noise reduction")]
|
||||
[ToolTip(
|
||||
"100% reconstruction accuracy when the tablet smoothing algorithm is MA and the window is exactly known\n\n" +
|
||||
"Warning: Reverse MA completely fails when tablet is not using MA + specified window"
|
||||
)]
|
||||
public bool ReverseMA
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
mode = ReconstructionAlg.ReverseMA;
|
||||
}
|
||||
}
|
||||
|
||||
[BooleanProperty("Reverse EMA", "Set to True if the tablet is using EMA algorithm for smoothing/noise reduction")]
|
||||
[ToolTip
|
||||
(
|
||||
"100% reconstruction accuracy when the tablet smoothing algorithm is EMA and the weight is exactly known\n\n" +
|
||||
"Great reconstruction stability when true tablet smoothing algorithm is unknown, but determining exact weight is hard\n\n" +
|
||||
"As a sidenote, hawku uses EMA for his smoothing filter"
|
||||
)]
|
||||
public bool ReverseEMA
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
mode = ReconstructionAlg.ReverseEMA;
|
||||
}
|
||||
}
|
||||
|
||||
[Property("MA Window"), ToolTip
|
||||
(
|
||||
"Default: 3\n\n" +
|
||||
"Defines in integers how many samples is considered. [Range: 2 - n]"
|
||||
)]
|
||||
public int MAWindow
|
||||
{
|
||||
set
|
||||
{
|
||||
window = value;
|
||||
if (window > 1)
|
||||
truePoints = new RingBuffer<Vector2>(value - 1);
|
||||
}
|
||||
get => window;
|
||||
}
|
||||
|
||||
[Property("EMA Weight"), ToolTip
|
||||
(
|
||||
"Default: 0.35\n\n" +
|
||||
|
@ -108,8 +35,6 @@ namespace VoiDPlugins.Filter
|
|||
}
|
||||
get => weight;
|
||||
}
|
||||
|
||||
private int window;
|
||||
private double weight;
|
||||
|
||||
public FilterStage FilterStage => FilterStage.PreInterpolate;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace VoiDPlugins.Library
|
||||
{
|
||||
|
@ -28,24 +29,17 @@ namespace VoiDPlugins.Library
|
|||
}
|
||||
}
|
||||
|
||||
public T PeekFirst()
|
||||
{
|
||||
var currentPosition = this.head;
|
||||
return this.dataStream[currentPosition];
|
||||
}
|
||||
|
||||
public T PeekLast()
|
||||
{
|
||||
var last = Math.Abs(this.head - 1);
|
||||
return this.dataStream[last];
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
this.dataStream = new T[this.Size];
|
||||
this.head = 0;
|
||||
}
|
||||
|
||||
private int Wrap(int index)
|
||||
{
|
||||
return (index + this.Size) % this.Size;
|
||||
}
|
||||
|
||||
IEnumerator<T> RingGetEnumerator()
|
||||
{
|
||||
if (this.head == 0)
|
||||
|
@ -68,6 +62,18 @@ namespace VoiDPlugins.Library
|
|||
}
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => this.dataStream[Wrap(index + this.head)];
|
||||
set => this.dataStream[Wrap(index + this.head)] = value;
|
||||
}
|
||||
|
||||
public T this[Index index]
|
||||
{
|
||||
get => this.dataStream[Wrap(index.IsFromEnd ? Wrap(this.head - index.Value) : Wrap(index.Value + this.head))];
|
||||
set => this.dataStream[Wrap(index.IsFromEnd ? Wrap(this.head - index.Value) : Wrap(index.Value + this.head))] = value;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
if (!this.IsFilled)
|
||||
|
@ -80,5 +86,15 @@ namespace VoiDPlugins.Library
|
|||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string a = "";
|
||||
foreach (var item in this.SkipLast(1))
|
||||
a += $"{item}, ";
|
||||
|
||||
a += this[^1];
|
||||
return a;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,6 +31,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VoiDPlugins.Library", "VoiD
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VMulti", "VoiDPlugins.Library\VMulti\VMulti.csproj", "{E6A1BA35-D3D4-4F28-9B59-FA946B3039C7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lagrange", "Filter\Lagrange\Lagrange.csproj", "{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -152,6 +154,18 @@ Global
|
|||
{E6A1BA35-D3D4-4F28-9B59-FA946B3039C7}.Release|x64.Build.0 = Release|Any CPU
|
||||
{E6A1BA35-D3D4-4F28-9B59-FA946B3039C7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{E6A1BA35-D3D4-4F28-9B59-FA946B3039C7}.Release|x86.Build.0 = Release|Any CPU
|
||||
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Release|x64.Build.0 = Release|Any CPU
|
||||
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{33F0E415-AB65-43F2-8AE2-3462B56B9E91} = {CFC2BC9E-CFD6-4B01-946D-43C26E6C2108}
|
||||
|
@ -163,5 +177,6 @@ Global
|
|||
{D8DBD60E-BE5C-43DE-AEB6-D6ADBAC3BE8C} = {44DF0884-9B34-45CA-92B7-2D57DCA3A1A8}
|
||||
{DE2D6E1E-F04E-4E0C-BE95-7B8E8AFBCD4C} = {C3E1E369-CD2C-4030-88BE-3D4E7493F8C7}
|
||||
{E6A1BA35-D3D4-4F28-9B59-FA946B3039C7} = {9B841994-6F85-4834-8C8C-04D196FE388A}
|
||||
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB} = {6EFE4444-C6E7-4743-8EE5-7A2BE7493457}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
Loading…
Add table
Reference in a new issue