variable smooth window with cutoff almost working

This commit is contained in:
Jacob Palecki 2023-09-14 15:41:10 -07:00
parent 2e614e6bc6
commit e40ed565cb
9 changed files with 211 additions and 38 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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