diff --git a/common/accel-motivity.hpp b/common/accel-motivity.hpp index e292b23..4b92424 100644 --- a/common/accel-motivity.hpp +++ b/common/accel-motivity.hpp @@ -8,21 +8,67 @@ namespace rawaccel { template <> struct loglog_sigmoid { - double accel; - double motivity; - double midpoint; - double constant; + double log_motivity; + double gamma_const; + double log_syncspeed; + double syncspeed; + double sharpness; + double sharpness_recip; + bool use_linear_clamp; + double minimum_sens; + double maximum_sens; loglog_sigmoid(const accel_args& args) : - accel(exp(args.growth_rate)), - motivity(2 * log(args.motivity)), - midpoint(log(args.midpoint)), - constant(-motivity / 2) {} + log_motivity(log(args.motivity)), + gamma_const(args.gamma / log_motivity), + log_syncspeed(log(args.sync_speed)), + syncspeed(args.sync_speed), + sharpness(args.smooth == 0 ? 16 : 0.5 / args.smooth), + sharpness_recip(1 / sharpness), + use_linear_clamp(sharpness >= 16), + minimum_sens(1 / args.motivity), + maximum_sens(args.motivity) {} double operator()(double x, const accel_args&) const { - double denom = exp(accel * (midpoint - log(x))) + 1; - return exp(motivity / denom + constant); + // if sharpness >= 16, use linear clamp for activation function. + // linear clamp means: clamp(x, -1, 1). + if (use_linear_clamp) + { + double log_space = gamma_const * (log(x) - log_syncspeed); + + if (log_space < -1) + { + return minimum_sens; + } + + if (log_space > 1) + { + return maximum_sens; + } + + return exp(log_space * log_motivity); + } + + if (x == syncspeed) { + return 1.0; + } + + double log_x = log(x); + double log_diff = log_x - log_syncspeed; + + if (log_diff > 0) + { + double log_space = gamma_const * log_diff; + double exponent = pow(tanh(pow(log_space, sharpness)), sharpness_recip); + return exp(exponent * log_motivity); + } + else + { + double log_space = -gamma_const * log_diff; + double exponent = -pow(tanh(pow(log_space, sharpness)), sharpness_recip); + return exp(exponent * log_motivity); + } } }; diff --git a/common/rawaccel-base.hpp b/common/rawaccel-base.hpp index 2789847..bfb881e 100644 --- a/common/rawaccel-base.hpp +++ b/common/rawaccel-base.hpp @@ -49,13 +49,13 @@ namespace rawaccel { double output_offset = 0; double acceleration = 0.005; double decay_rate = 0.1; - double growth_rate = 1; + double gamma = 1; double motivity = 1.5; double exponent_classic = 2; double scale = 1; double exponent_power = 0.05; double limit = 1.5; - double midpoint = 5; + double sync_speed = 5; double smooth = 0.5; vec2d cap = { 15, 1.5 }; diff --git a/common/rawaccel-validate.hpp b/common/rawaccel-validate.hpp index 2f4d1e6..907a9b0 100644 --- a/common/rawaccel-validate.hpp +++ b/common/rawaccel-validate.hpp @@ -96,8 +96,8 @@ namespace rawaccel { error("scale"" must be positive"); } - if (args.growth_rate <= 0) { - error("growth rate"" must be positive"); + if (args.gamma <= 0) { + error("gamma"" must be positive"); } if (args.decay_rate <= 0) { @@ -120,8 +120,8 @@ namespace rawaccel { error("limit"" must be positive"); } - if (args.midpoint <= 0) { - error("midpoint"" must be positive"); + if (args.sync_speed <= 0) { + error("synchronous speed"" must be positive"); } if (args.smooth < 0 || args.smooth > 1) { diff --git a/common/rawaccel.hpp b/common/rawaccel.hpp index 604dc81..e82cb87 100644 --- a/common/rawaccel.hpp +++ b/common/rawaccel.hpp @@ -420,7 +420,6 @@ namespace rawaccel { if (flags.apply_dir_mul_y && in.y < 0) { in.y *= args.ud_output_dpi_ratio; } - } modifier(modifier_settings& settings) diff --git a/grapher/Models/Options/AccelTypeOptions.cs b/grapher/Models/Options/AccelTypeOptions.cs index c5dfc9d..8959bea 100644 --- a/grapher/Models/Options/AccelTypeOptions.cs +++ b/grapher/Models/Options/AccelTypeOptions.cs @@ -281,12 +281,12 @@ namespace grapher OutputOffset.SetActiveValue(args.outputOffset); InputOffset.SetActiveValue(args.inputOffset); DecayRate.SetActiveValue(args.decayRate); - GrowthRate.SetActiveValue(args.growthRate); + GrowthRate.SetActiveValue(args.gamma); Smooth.SetActiveValue(args.smooth); Limit.SetActiveValue((args.mode == AccelMode.motivity) ? args.motivity : args.limit); PowerClassic.SetActiveValue(args.exponentClassic); Exponent.SetActiveValue(args.exponentPower); - Midpoint.SetActiveValue(args.midpoint); + Midpoint.SetActiveValue(args.syncSpeed); LutPanel.SetActiveValues(args.data, args.length, args.mode); LutApply.SetActiveValue(args.gain); } @@ -326,7 +326,7 @@ namespace grapher GainSwitch.CheckBox.Checked; if (DecayRate.Visible) args.decayRate = DecayRate.Field.Data; - if (GrowthRate.Visible) args.growthRate = GrowthRate.Field.Data; + if (GrowthRate.Visible) args.gamma = GrowthRate.Field.Data; if (Smooth.Visible) args.smooth = Smooth.Field.Data; if (ClassicCap.Visible) { @@ -360,7 +360,7 @@ namespace grapher if (InputOffset.Visible) args.inputOffset = InputOffset.Field.Data; if (OutputOffset.Visible) args.outputOffset = OutputOffset.Field.Data; - if (Midpoint.Visible) args.midpoint = Midpoint.Field.Data; + if (Midpoint.Visible) args.syncSpeed = Midpoint.Field.Data; if (LutPanel.Visible) { (var points, var length) = LutPanel.GetPoints(); diff --git a/wrapper-tests/SynchronousAccelTests.cs b/wrapper-tests/SynchronousAccelTests.cs new file mode 100644 index 0000000..27cc7c6 --- /dev/null +++ b/wrapper-tests/SynchronousAccelTests.cs @@ -0,0 +1,94 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; + +namespace wrapper_tests +{ + [TestClass] + public class SynchronousAccelTests + { + [TestMethod] + public void GivenSpeeds_SynchronousAccel_YieldsCorrectSens() + { + double syncSpeed = 20; + double gamma = 0.5; + double motivity = 1.3; + double smooth = 0.5; + + var profile = new Profile(); + profile.outputDPI = 1000; + profile.argsX.mode = AccelMode.motivity; + profile.argsX.gain = false; + profile.argsX.syncSpeed = syncSpeed; + profile.argsX.gamma = gamma; + profile.argsX.motivity = motivity; + profile.argsX.smooth = smooth; + var accel = new ManagedAccel(profile); + var accelSimulator = new SynchronousAccelSimulator(syncSpeed, motivity, gamma, smooth); + + List inputs = new List() + { + 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, + }; + + foreach (int input in inputs) + { + Tuple output = accel.Accelerate(input, 0, 1, 10); + double expectedOutput = input * accelSimulator.Accelerate(input / 10.0); + Assert.AreEqual(expectedOutput, output.Item1, expectedOutput * 0.0001); + } + } + } + + /// + /// Contains definition of how synchronous accel is expected to accelerate inputs + /// No optimization tricks are used for clarity of behavior. + /// + public class SynchronousAccelSimulator + { + public SynchronousAccelSimulator( + double syncSpeed, + double motivity, + double gamma, + double smooth) + { + SyncSpeed = syncSpeed; + Motivity = motivity; + Gamma = gamma; + Sharpness = smooth <= 0 ? 16 : 0.5 / smooth; + } + + public double SyncSpeed { get; } + + public double Motivity { get; } + + public double Gamma { get; } + + public double Sharpness { get; } + + public double Accelerate(double inputSpeed) + { + double logSpace = CalculateLogSpace(inputSpeed); + double activation = ActivationFunction(logSpace); + return Math.Pow(Motivity, activation); + } + + public double CalculateLogSpace(double x) + { + double syncRatio = x / SyncSpeed; + double logSpaceUnadjusted = Math.Log(syncRatio, Motivity); + double gammaAdjusted = Gamma * logSpaceUnadjusted; + return gammaAdjusted; + } + + public double ActivationFunction(double x) + { + if (Sharpness >= 16) + { + return Math.Min(1, Math.Max(-1, x)); + } + + return Math.Sign(x) * Math.Pow(Math.Tanh(Math.Pow(Math.Abs(x), Sharpness)), 1 / Sharpness); + } + } +} \ No newline at end of file diff --git a/wrapper-tests/wrapper-tests.csproj b/wrapper-tests/wrapper-tests.csproj index cb73a51..592f930 100644 --- a/wrapper-tests/wrapper-tests.csproj +++ b/wrapper-tests/wrapper-tests.csproj @@ -53,6 +53,7 @@ + diff --git a/wrapper/wrapper.cpp b/wrapper/wrapper.cpp index 4f297ff..ecc4de3 100644 --- a/wrapper/wrapper.cpp +++ b/wrapper/wrapper.cpp @@ -68,13 +68,13 @@ public value struct AccelArgs double outputOffset; double acceleration; double decayRate; - double growthRate; + double gamma; double motivity; double exponentClassic; double scale; double exponentPower; double limit; - double midpoint; + double syncSpeed; double smooth; [JsonProperty("Cap / Jump")]