mirror of
https://github.com/RawAccelOfficial/rawaccel.git
synced 2025-05-11 18:36:46 +02:00
variable smooth window with cutoff almost working
This commit is contained in:
parent
2e614e6bc6
commit
e40ed565cb
9 changed files with 211 additions and 38 deletions
|
@ -19,6 +19,8 @@ namespace rawaccel {
|
|||
inline constexpr size_t LUT_RAW_DATA_CAPACITY = 514;
|
||||
inline constexpr size_t LUT_POINTS_CAPACITY = LUT_RAW_DATA_CAPACITY / 2;
|
||||
|
||||
inline constexpr size_t SMOOTH_RAW_DATA_CAPACITY = 8192;
|
||||
|
||||
inline constexpr double MAX_NORM = 16;
|
||||
|
||||
inline constexpr bool LEGACY = 0;
|
||||
|
@ -62,6 +64,15 @@ namespace rawaccel {
|
|||
mutable float data[LUT_RAW_DATA_CAPACITY] = {};
|
||||
};
|
||||
|
||||
struct input_speed_args
|
||||
{
|
||||
bool whole = true;
|
||||
double lp_norm = 2;
|
||||
bool should_smooth = false;
|
||||
double smooth_window = 100;
|
||||
bool use_cutoff = false;
|
||||
double cutoff_window = 10;
|
||||
};
|
||||
|
||||
struct profile {
|
||||
wchar_t name[MAX_NAME_LEN] = L"default";
|
||||
|
@ -73,6 +84,7 @@ namespace rawaccel {
|
|||
|
||||
accel_args accel_x;
|
||||
accel_args accel_y;
|
||||
input_speed_args input_speed_args;
|
||||
|
||||
double sensitivity = 1;
|
||||
double yx_sens_ratio = 1;
|
||||
|
|
|
@ -36,7 +36,6 @@ namespace rawaccel {
|
|||
bool compute_ref_angle = 0;
|
||||
bool apply_snap = 0;
|
||||
bool clamp_speed = 0;
|
||||
distance_mode dist_mode = {};
|
||||
bool apply_directional_weight = 0;
|
||||
bool apply_dir_mul_x = 0;
|
||||
bool apply_dir_mul_y = 0;
|
||||
|
@ -51,6 +50,19 @@ namespace rawaccel {
|
|||
compute_ref_angle = apply_snap || apply_directional_weight;
|
||||
apply_dir_mul_x = args.lr_sens_ratio != 1;
|
||||
apply_dir_mul_y = args.ud_sens_ratio != 1;
|
||||
}
|
||||
|
||||
modifier_flags() = default;
|
||||
};
|
||||
|
||||
struct input_speed_processor {
|
||||
|
||||
input_speed_args speed_args = {};
|
||||
distance_mode dist_mode = {};
|
||||
|
||||
void init(input_speed_args args)
|
||||
{
|
||||
speed_args = args;
|
||||
|
||||
if (!args.whole) {
|
||||
dist_mode = distance_mode::separate;
|
||||
|
@ -64,43 +76,45 @@ namespace rawaccel {
|
|||
else {
|
||||
dist_mode = distance_mode::euclidean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
modifier_flags() = default;
|
||||
};
|
||||
|
||||
struct input_speed_processor {
|
||||
double calc_speed(vec2d in, distance_mode mode, double lp_norm)
|
||||
double calc_speed(vec2d in, milliseconds time)
|
||||
{
|
||||
double speed;
|
||||
|
||||
if (mode == distance_mode::max) {
|
||||
if (dist_mode == distance_mode::max) {
|
||||
speed = maxsd(in.x, in.y);
|
||||
}
|
||||
else if (mode == distance_mode::Lp) {
|
||||
speed = lp_distance(in, lp_norm);
|
||||
else if (dist_mode == distance_mode::Lp) {
|
||||
speed = lp_distance(in, speed_args.lp_norm);
|
||||
}
|
||||
else {
|
||||
speed = magnitude(in);
|
||||
}
|
||||
|
||||
speed = smooth_speed(speed, 1);
|
||||
if (speed_args.should_smooth &&
|
||||
speed_args.smooth_window > 0)
|
||||
{
|
||||
speed = smooth_speed(speed, time);
|
||||
}
|
||||
|
||||
return speed;
|
||||
}
|
||||
|
||||
milliseconds times[100] = {};
|
||||
double speeds[100] = {};
|
||||
milliseconds times[SMOOTH_RAW_DATA_CAPACITY] = {};
|
||||
double speeds[SMOOTH_RAW_DATA_CAPACITY] = {};
|
||||
int smoothBufferIndex = 0;
|
||||
|
||||
double smooth_speed(const double speed,
|
||||
const milliseconds time)
|
||||
{
|
||||
double total = speed;
|
||||
double windowSpeed = speed;
|
||||
double cutoffWindowSpeed = speed;
|
||||
int newIndex = smoothBufferIndex - 1;
|
||||
if (newIndex < 0)
|
||||
{
|
||||
newIndex = 99;
|
||||
newIndex = SMOOTH_RAW_DATA_CAPACITY - 1;
|
||||
}
|
||||
|
||||
times[smoothBufferIndex] = 0;
|
||||
|
@ -109,27 +123,46 @@ namespace rawaccel {
|
|||
while (smoothBufferIndex != newIndex)
|
||||
{
|
||||
smoothBufferIndex++;
|
||||
if (smoothBufferIndex > 99)
|
||||
if (smoothBufferIndex >= SMOOTH_RAW_DATA_CAPACITY)
|
||||
{
|
||||
smoothBufferIndex = 0;
|
||||
}
|
||||
|
||||
milliseconds age = times[smoothBufferIndex] + time;
|
||||
if (age > 100)
|
||||
if (age > speed_args.smooth_window)
|
||||
{
|
||||
times[smoothBufferIndex] = 0;
|
||||
speeds[smoothBufferIndex] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
total += speeds[smoothBufferIndex];
|
||||
windowSpeed += speeds[smoothBufferIndex];
|
||||
|
||||
if (speed_args.use_cutoff &&
|
||||
age <= speed_args.cutoff_window)
|
||||
{
|
||||
cutoffWindowSpeed += speeds[smoothBufferIndex];
|
||||
}
|
||||
|
||||
times[smoothBufferIndex] = age;
|
||||
}
|
||||
}
|
||||
|
||||
smoothBufferIndex = newIndex;
|
||||
|
||||
return total / 100;
|
||||
windowSpeed /= speed_args.smooth_window;
|
||||
|
||||
if (speed_args.use_cutoff)
|
||||
{
|
||||
cutoffWindowSpeed /= speed_args.cutoff_window;
|
||||
|
||||
if (cutoffWindowSpeed < windowSpeed)
|
||||
{
|
||||
return cutoffWindowSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
return windowSpeed;
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -220,12 +253,12 @@ namespace rawaccel {
|
|||
fabs(in.y * ips_factor * args.domain_weights.y)
|
||||
};
|
||||
|
||||
if (flags.dist_mode == distance_mode::separate) {
|
||||
if (speed_processor.dist_mode == distance_mode::separate) {
|
||||
in.x *= (*cb_x)(data.accel_x, args.accel_x, abs_weighted_vel.x, args.range_weights.x);
|
||||
in.y *= (*cb_y)(data.accel_y, args.accel_y, abs_weighted_vel.y, args.range_weights.y);
|
||||
}
|
||||
else {
|
||||
double speed = speed_processor.calc_speed(abs_weighted_vel, flags.dist_mode, args.lp_norm);
|
||||
double speed = speed_processor.calc_speed(abs_weighted_vel, time);
|
||||
|
||||
double weight = args.range_weights.x;
|
||||
|
||||
|
|
|
@ -380,6 +380,7 @@ DeviceSetup(WDFOBJECT hDevice)
|
|||
if (wcsncmp(prof_name, profile.name, ra::MAX_NAME_LEN) == 0) {
|
||||
devExt->mod_settings = global.modifier_data[i];
|
||||
devExt->mod = { devExt->mod_settings };
|
||||
devExt->speed_processor.init(devExt->mod_settings.prof.input_speed_args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,12 +123,12 @@ namespace grapher
|
|||
|
||||
// TODO - separate sensitivity fields, add new label for ratio
|
||||
settings.yxSensRatio = ApplyOptions.YToXRatio.Value;
|
||||
settings.combineMagnitudes = ApplyOptions.IsWhole;
|
||||
settings.inputSpeedArgs.combineMagnitudes = ApplyOptions.IsWhole;
|
||||
ApplyOptions.SetArgsFromActiveValues(ref settings.argsX, ref settings.argsY);
|
||||
|
||||
var (domWeights, lpNorm) = ApplyOptions.Directionality.GetDomainArgs();
|
||||
settings.domainXY = domWeights;
|
||||
settings.lpNorm = lpNorm;
|
||||
settings.inputSpeedArgs.lpNorm = lpNorm;
|
||||
|
||||
settings.rangeXY = ApplyOptions.Directionality.GetRangeXY();
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ namespace grapher.Models.Charts.ChartState
|
|||
{
|
||||
ChartState chartState;
|
||||
|
||||
if (settings.combineMagnitudes)
|
||||
if (settings.inputSpeedArgs.combineMagnitudes)
|
||||
{
|
||||
if (settings.yxSensRatio != 1 ||
|
||||
settings.domainXY.x != settings.domainXY.y ||
|
||||
|
|
|
@ -106,8 +106,8 @@ namespace grapher.Models.Options
|
|||
YToXRatio.SetActiveValue(settings.yxSensRatio);
|
||||
Rotation.SetActiveValue(settings.rotation);
|
||||
|
||||
WholeVectorCheckBox.Checked = settings.combineMagnitudes;
|
||||
ByComponentVectorCheckBox.Checked = !settings.combineMagnitudes;
|
||||
WholeVectorCheckBox.Checked = settings.inputSpeedArgs.combineMagnitudes;
|
||||
ByComponentVectorCheckBox.Checked = !settings.inputSpeedArgs.combineMagnitudes;
|
||||
ByComponentVectorXYLock.Checked = settings.argsX.Equals(settings.argsY);
|
||||
OptionSetX.SetActiveValues(ref settings.argsX);
|
||||
OptionSetY.SetActiveValues(ref settings.argsY);
|
||||
|
|
|
@ -104,9 +104,9 @@ namespace grapher.Models.Options.Directionality
|
|||
Domain.SetActiveValues(settings.domainXY.x, settings.domainXY.y);
|
||||
Range.SetActiveValues(settings.rangeXY.x, settings.rangeXY.y);
|
||||
|
||||
if (settings.combineMagnitudes)
|
||||
if (settings.inputSpeedArgs.combineMagnitudes)
|
||||
{
|
||||
LpNorm.SetActiveValue(settings.lpNorm);
|
||||
LpNorm.SetActiveValue(settings.inputSpeedArgs.lpNorm);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace wrapper_tests
|
||||
{
|
||||
|
@ -15,7 +16,7 @@ namespace wrapper_tests
|
|||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Given_Input_Smooths()
|
||||
public void Given_Input_DefaultCalculator_ReturnsUnsmoothed()
|
||||
{
|
||||
var speedCalc = new SpeedCalculator();
|
||||
var inputs = new[]
|
||||
|
@ -26,6 +27,44 @@ namespace wrapper_tests
|
|||
(3,3),
|
||||
};
|
||||
|
||||
double timePerInput = 1;
|
||||
double speed = 0;
|
||||
double sum = 0;
|
||||
|
||||
foreach (var input in inputs)
|
||||
{
|
||||
speed = speedCalc.CalculateSpeed(input.Item1, input.Item2, timePerInput);
|
||||
sum += Magnitude(input.Item1, input.Item2);
|
||||
}
|
||||
|
||||
double expected = Magnitude(inputs[inputs.Length-1].Item1, inputs[inputs.Length-1].Item2) / timePerInput;
|
||||
|
||||
Assert.AreEqual(expected, speed, 0.0001);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Given_Input_SmoothCalculator_Smooths()
|
||||
{
|
||||
double smooth_window = 100;
|
||||
|
||||
var speedArgs = new SpeedCalculatorArgs(
|
||||
lp_norm: 2,
|
||||
should_smooth: true,
|
||||
smooth_window: smooth_window,
|
||||
should_cutoff: false,
|
||||
cutoff_window: 0);
|
||||
|
||||
var speedCalc = new SpeedCalculator();
|
||||
speedCalc.Init(speedArgs);
|
||||
|
||||
var inputs = new[]
|
||||
{
|
||||
(0,0),
|
||||
(1,1),
|
||||
(2,2),
|
||||
(3,3),
|
||||
};
|
||||
|
||||
double speed = 0;
|
||||
double sum = 0;
|
||||
|
||||
|
@ -35,9 +74,50 @@ namespace wrapper_tests
|
|||
sum += Magnitude(input.Item1, input.Item2);
|
||||
}
|
||||
|
||||
double expected = sum / 100;
|
||||
double expected = sum / smooth_window;
|
||||
|
||||
Assert.AreEqual(expected, speed);
|
||||
Assert.AreEqual(expected, speed, 0.0001);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Given_InputWithCutoff_SmoothCalculator_Smooths()
|
||||
{
|
||||
double smooth_window = 100;
|
||||
double cutoff_window = 10;
|
||||
|
||||
var speedArgs = new SpeedCalculatorArgs(
|
||||
lp_norm: 2,
|
||||
should_smooth: true,
|
||||
smooth_window: smooth_window,
|
||||
should_cutoff: true,
|
||||
cutoff_window: cutoff_window);
|
||||
|
||||
var speedCalc = new SpeedCalculator();
|
||||
speedCalc.Init(speedArgs);
|
||||
|
||||
var inputs = new List<(int, int)>();
|
||||
double cutoff_sum = 0;
|
||||
|
||||
for (int i = 100; i > 0; i--)
|
||||
{
|
||||
inputs.Add((i, i));
|
||||
|
||||
if (i <= (cutoff_window + 1))
|
||||
{
|
||||
cutoff_sum += Magnitude(i, i);
|
||||
}
|
||||
}
|
||||
|
||||
double speed = 0;
|
||||
|
||||
foreach (var input in inputs)
|
||||
{
|
||||
speed = speedCalc.CalculateSpeed(input.Item1, input.Item2, 1);
|
||||
}
|
||||
|
||||
double expected = cutoff_sum / cutoff_window;
|
||||
|
||||
Assert.AreEqual(expected, speed, 0.0001);
|
||||
}
|
||||
|
||||
public static double Magnitude (double x, double y)
|
||||
|
|
|
@ -98,6 +98,30 @@ public value struct AccelArgs
|
|||
}
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind::Sequential)]
|
||||
public value struct InputSpeedArgs
|
||||
{
|
||||
[JsonProperty("Whole/combined accel (set false for 'by component' mode)")]
|
||||
[MarshalAs(UnmanagedType::U1)]
|
||||
bool combineMagnitudes;
|
||||
|
||||
double lpNorm;
|
||||
|
||||
[JsonProperty("Whether input speeds should be smoothed to determine acceleration speed")]
|
||||
[MarshalAs(UnmanagedType::U1)]
|
||||
bool shouldSmooth;
|
||||
|
||||
[JsonProperty("Time window in ms over which input should be smoothed")]
|
||||
double smoothWindow;
|
||||
|
||||
[JsonProperty("Whether smoothed input speeds should stop smoothing and take latest speed when latest speed is lower")]
|
||||
[MarshalAs(UnmanagedType::U1)]
|
||||
bool shouldCutoff;
|
||||
|
||||
[JsonProperty("Time window in ms over which cutoff speed is calculated")]
|
||||
float cutoffWindow;
|
||||
};
|
||||
|
||||
[JsonObject(ItemRequired = Required::Always)]
|
||||
[StructLayout(LayoutKind::Sequential, CharSet = CharSet::Unicode)]
|
||||
public ref struct Profile
|
||||
|
@ -105,12 +129,6 @@ public ref struct Profile
|
|||
[MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_NAME_LEN)]
|
||||
System::String^ name;
|
||||
|
||||
[JsonProperty("Whole/combined accel (set false for 'by component' mode)")]
|
||||
[MarshalAs(UnmanagedType::U1)]
|
||||
bool combineMagnitudes;
|
||||
|
||||
double lpNorm;
|
||||
|
||||
[JsonProperty("Stretches domain for horizontal vs vertical inputs")]
|
||||
Vec2<double> domainXY;
|
||||
[JsonProperty("Stretches accel range for horizontal vs vertical inputs")]
|
||||
|
@ -120,6 +138,8 @@ public ref struct Profile
|
|||
AccelArgs argsX;
|
||||
[JsonProperty("Vertical accel parameters")]
|
||||
AccelArgs argsY;
|
||||
[JsonProperty("Input speed calculation parameters")]
|
||||
InputSpeedArgs inputSpeedArgs;
|
||||
|
||||
[JsonProperty("Sensitivity multiplier")]
|
||||
double sensitivity;
|
||||
|
@ -294,7 +314,7 @@ public:
|
|||
}
|
||||
|
||||
auto msgs = elem->messages;
|
||||
if (elem->prof->combineMagnitudes) {
|
||||
if (elem->prof->inputSpeedArgs.combineMagnitudes) {
|
||||
for (int i = 0; i < elem->lastX; i++) {
|
||||
sb->AppendFormat("\t{0}\n", msgs[i]);
|
||||
}
|
||||
|
@ -474,16 +494,43 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
public ref class SpeedCalculatorArgs
|
||||
{
|
||||
public:
|
||||
SpeedCalculatorArgs() {}
|
||||
SpeedCalculatorArgs(
|
||||
double lp_norm,
|
||||
bool should_smooth,
|
||||
double smooth_window,
|
||||
bool should_cutoff,
|
||||
double cutoff_window)
|
||||
{
|
||||
speed_args->lp_norm = lp_norm;
|
||||
speed_args->smooth_window = smooth_window;
|
||||
speed_args->should_smooth = should_smooth;
|
||||
speed_args->use_cutoff = should_cutoff;
|
||||
speed_args->cutoff_window = cutoff_window;
|
||||
}
|
||||
|
||||
ra::input_speed_args* const speed_args = new ra::input_speed_args();
|
||||
};
|
||||
|
||||
public ref class SpeedCalculator
|
||||
{
|
||||
speed_calc_instance_t* const instance = new speed_calc_instance_t();
|
||||
|
||||
public:
|
||||
SpeedCalculator() {}
|
||||
|
||||
void Init(SpeedCalculatorArgs^ args)
|
||||
{
|
||||
instance->speed_calculator.init(*args->speed_args);
|
||||
}
|
||||
|
||||
double CalculateSpeed(double x, double y, double time)
|
||||
{
|
||||
vec2d in = { x ,y };
|
||||
return instance->speed_calculator.calc_speed(in, ra::distance_mode::euclidean, 2);
|
||||
return instance->speed_calculator.calc_speed(in, time);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue