rawinput: distinguish between circular and linear analog input (#665)

## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Currently, all analogs are treated as circular values that wrap around.
This works for knobs and turntables, but can get weird for linear values
(like tilt sensors and sliders) especially once analog options are
enabled, such as sensitivity.

This PR groups analogs into 3 buckets: circular, linear (centered), and
linear (positive). Linear types get different algorithms applied to it
when options are set (e.g., sensitivity caps out at 0, 1 instead of
wrapping around).

In linear mode:
* Sensitivity value applies an exponential response curve (power curve)
to the original value.
* Multiplier/divisor serves as a linear cut-off.
* Relative mode works as you expect, but of course, values don't wrap
around.
* Invert and deadzone work as you expect (since they applied to the
controller input anyway)
* Smoothing is removed from the UI and has no effect.
* Delay is unaffected.

Also, fix a small bug with -/+ button not working for analog `Delay`
option.

## Testing
WIP
This commit is contained in:
bicarus
2026-05-01 19:02:32 -07:00
committed by GitHub
parent d9d5823fdb
commit af8d8dae9f
18 changed files with 324 additions and 166 deletions
+30 -14
View File
@@ -207,22 +207,38 @@ float Analog::applyMultiplier(float value) {
}
float Analog::normalizeAnalogValue(float value) {
// effectively the same as fmodf(value, 1.f)
// for small values, this is MUCH faster than fmodf.
float new_value = value;
while (new_value > 1.f) {
new_value -= 1.f;
if (getType() == GameAPI::Analogs::AnalogType::Circular) {
// effectively the same as fmodf(value, 1.f)
// for small values, this is MUCH faster than fmodf.
float new_value = value;
while (new_value > 1.f) {
new_value -= 1.f;
}
while (new_value < 0.f) {
new_value += 1.f;
}
return new_value;
} else {
// clamp to [0, 1] range
return std::clamp(value, 0.f, 1.f);
}
while (new_value < 0.f) {
new_value += 1.f;
}
return new_value;
}
float Analog::applyDeadzone(float raw_value) {
float value = raw_value;
const auto deadzone = this->getDeadzone();
if (deadzone > 0) {
auto deadzone = this->getDeadzone();
// in the past, positive deadzone applied in the center, negative deadzone applied to 0
// after each analog value received a type (circular/linear) this has been simpliifed to
// positive values only since we can figure out where the rest value is
// for back compat, treat negative value as positive
if (deadzone < 0.f) {
deadzone = -deadzone;
}
// relative mode assumes that user is using a stick, so center is neutral regardless of analog type
if (getType() != GameAPI::Analogs::AnalogType::LinearPositive || isRelativeMode()) {
// calculate values
const auto delta = value - 0.5f;
@@ -255,7 +271,7 @@ float Analog::applyDeadzone(float raw_value) {
}
}
} else if (deadzone < 0) {
} else {
// invert for mirror
if (this->getDeadzoneMirror()) {
@@ -263,8 +279,8 @@ float Analog::applyDeadzone(float raw_value) {
}
// deadzone from minimum value
if (deadzone > -1 && value > -deadzone) {
value = std::min(1.f, (value + deadzone) / (1.f + deadzone));
if (deadzone < 1.f && deadzone < value) {
value = std::max(0.f, (value - deadzone) / (1.f - deadzone));
} else {
value = 0.f;
}
+27 -2
View File
@@ -13,6 +13,21 @@ namespace rawinput {
class RawInputManager;
}
namespace GameAPI::Analogs {
enum class AnalogType {
// default; values wrap around (below 0 turns into 1, over 1 is 0)
// knobs, turntables
Circular = 0,
// typical joystick that rests at the center and caps at [0, 1]
LinearCentered = 1,
// one-directional value (sliders and instruments like piano/drum velocity)
// starts at 0 and goes up to 1
LinearPositive = 2,
};
}
struct AnalogMovingAverage {
double time_in_ms;
float sine;
@@ -31,10 +46,11 @@ private:
float last_state = 0.5f;
bool sensitivity_set = false;
bool deadzone_set = false;
GameAPI::Analogs::AnalogType type = GameAPI::Analogs::AnalogType::Circular;
// smoothing function
bool smoothing = false;
std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history;
std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history = {};
int vector_history_index = 0;
float smoothed_last_state = 0.f;
@@ -66,7 +82,8 @@ public:
float override_state = 0.5f;
explicit Analog(std::string name) : name(std::move(name)) {
vector_history.fill({0.0, 0.f, 0.f});
};
explicit Analog(std::string name, GameAPI::Analogs::AnalogType type) : name(std::move(name)), type(type) {
};
std::string getDisplayString(rawinput::RawInputManager* manager);
@@ -214,4 +231,12 @@ public:
inline std::queue<float> &getDelayBuffer() {
return this->delay_buffer;
}
inline GameAPI::Analogs::AnalogType getType() const {
return this->type;
}
inline void setType(GameAPI::Analogs::AnalogType type) {
this->type = type;
}
};
+87 -2
View File
@@ -679,7 +679,9 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::D
}
// deadzone
if (analog.isDeadzoneSet()) {
// do not apply deadzone to circular analogs since it doesn't make sense (except in relative mode)
if (analog.isDeadzoneSet() &&
(analog.getType() != AnalogType::Circular || analog.isRelativeMode())) {
value = analog.applyDeadzone(value);
}
@@ -704,7 +706,7 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::D
// translate relative movement to absolute value
value = analog.getAbsoluteValue(relative_delta);
} else {
} else if (analog.getType() == AnalogType::Circular) {
// integer multiplier
value = analog.applyMultiplier(value);
@@ -732,6 +734,53 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::D
// apply to value
value = rads * (float) M_1_TAU;
}
} else {
// sensitivity
if (analog.isSensitivitySet()) {
// adjust curve
// values < 1.f : less sensitive around neutral
// values > 1.f : more sensitive around neutral
float curve = analog.getSensitivity();
if (curve <= 0.f) {
curve = 0.01f;
}
curve = 1.f / curve;
if (analog.getType() == AnalogType::LinearCentered) {
// convert 0.0..1.0 to -1.0..+1.0
float signed_raw = (value - 0.5f) * 2.0f;
// apply curve
float sign = signed_raw < 0.0f ? -1.0f : 1.0f;
float magnitude = fabsf(signed_raw);
float curved = sign * powf(magnitude, curve);
// convert back to 0.0..1.0
value = curved * 0.5f + 0.5f;
} else {
value = powf(value, curve);
}
value = std::clamp(value, 0.f, 1.f);
}
// multiplier / divisor
if (analog.getMultiplier() < -1) {
if (analog.getType() == AnalogType::LinearCentered) {
value = (value - 0.5f) / (-analog.getMultiplier()) + 0.5f;
} else {
value /= -analog.getMultiplier();
}
value = std::clamp(value, 0.f, 1.f);
} else if (analog.getMultiplier() > 1) {
if (analog.getType() == AnalogType::LinearCentered) {
value = (value - 0.5f) * analog.getMultiplier() + 0.5f;
} else {
value *= analog.getMultiplier();
}
value = std::clamp(value, 0.f, 1.f);
}
}
// delay
@@ -839,6 +888,42 @@ std::vector<Analog> GameAPI::Analogs::sortAnalogs(
return sorted;
}
static std::vector<Analog> sortAnalogsWithTypeInternal(
std::vector<Analog> &analogs,
const std::initializer_list<GameAPI::Analogs::AnalogWithType> list) {
std::vector<Analog> sorted;
bool analog_found;
for (auto &a : list) {
analog_found = false;
for (auto &analog : analogs) {
if (a.name == analog.getName()) {
analog_found = true;
analog.setType(a.type);
sorted.push_back(analog);
break;
}
}
if (!analog_found) {
sorted.emplace_back(a.name, a.type);
}
}
return sorted;
}
void GameAPI::Analogs::sortAnalogsWithType(
std::vector<Analog> *analogs,
const std::initializer_list<AnalogWithType> list) {
if (analogs) {
*analogs = sortAnalogsWithTypeInternal(*analogs, list);
}
}
float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &analog) {
// check override
+8 -7
View File
@@ -85,14 +85,15 @@ namespace GameAPI {
const std::vector<Analog> &analogs,
const std::vector<std::string> &analog_names);
template<typename T>
void sortAnalogs(std::vector<Analog> *analogs, T t) {
const std::vector<std::string> analog_names { t };
struct AnalogWithType {
const std::string name;
const AnalogType type;
if (analogs) {
*analogs = GameAPI::Analogs::sortAnalogs(*analogs, analog_names);
}
}
AnalogWithType(std::string name, AnalogType type) :
name(std::move(name)), type(type) {}
};
void sortAnalogsWithType(std::vector<Analog> *analogs, const std::initializer_list<AnalogWithType> list);
template<typename T, typename... Rest>
void sortAnalogs(std::vector<Analog> *analogs, T t, Rest... rest) {
+6 -5
View File
@@ -38,11 +38,12 @@ std::vector<Analog> &games::bc::get_analogs() {
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Busou Shinki: Armored Princess Battle Conductor");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Stick X",
"Stick Y"
);
using GameAPI::Analogs::AnalogType;
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
{ "Stick X", AnalogType::LinearCentered },
{ "Stick Y", AnalogType::LinearCentered }
});
}
return analogs;
+8 -7
View File
@@ -45,13 +45,14 @@ std::vector<Analog> &games::ccj::get_analogs() {
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Chase Chase Jokers");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Joystick X",
"Joystick Y",
"Trackball DX",
"Trackball DY"
);
using GameAPI::Analogs::AnalogType;
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
{ "Joystick X", AnalogType::LinearCentered },
{ "Joystick Y", AnalogType::LinearCentered },
{ "Trackball DX", AnalogType::LinearCentered },
{ "Trackball DY", AnalogType::LinearCentered }
});
}
return analogs;
+8 -7
View File
@@ -55,13 +55,14 @@ std::vector<Analog> &games::ddr::get_analogs() {
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Dance Dance Revolution");
GameAPI::Analogs::sortAnalogs(
&analogs,
"P1 Left-Right (Axis Fix)",
"P1 Up-Down (Axis Fix)",
"P2 Left-Right (Axis Fix)",
"P2 Up-Down (Axis Fix)"
);
using namespace GameAPI::Analogs;
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
{ "P1 Left-Right (Axis Fix)", AnalogType::LinearCentered },
{ "P1 Up-Down (Axis Fix)", AnalogType::LinearCentered },
{ "P2 Left-Right (Axis Fix)", AnalogType::LinearCentered },
{ "P2 Up-Down (Axis Fix)", AnalogType::LinearCentered }
});
}
return analogs;
}
+8 -7
View File
@@ -35,13 +35,14 @@ std::vector<Analog> &games::ftt::get_analogs() {
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("FutureTomTom");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Pad 1",
"Pad 2",
"Pad 3",
"Pad 4"
);
using GameAPI::Analogs::AnalogType;
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
{ "Pad 1", AnalogType::LinearPositive },
{ "Pad 2", AnalogType::LinearPositive },
{ "Pad 3", AnalogType::LinearPositive },
{ "Pad 4", AnalogType::LinearPositive }
});
}
return analogs;
+12 -10
View File
@@ -86,19 +86,21 @@ std::vector<Button> &games::gitadora::get_buttons() {
std::vector<Analog> &games::gitadora::get_analogs() {
static std::vector<Analog> analogs;
using namespace GameAPI::Analogs;
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("GitaDora");
GameAPI::Analogs::sortAnalogs(&analogs,
"Guitar P1 Wail X",
"Guitar P1 Wail Y",
"Guitar P1 Wail Z",
"Guitar P1 Knob",
"Guitar P2 Wail X",
"Guitar P2 Wail Y",
"Guitar P2 Wail Z",
"Guitar P2 Knob"
);
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
{"Guitar P1 Wail X", AnalogType::LinearCentered},
{"Guitar P1 Wail Y", AnalogType::LinearCentered},
{"Guitar P1 Wail Z", AnalogType::LinearCentered},
{"Guitar P1 Knob", AnalogType::Circular},
{"Guitar P2 Wail X", AnalogType::LinearCentered},
{"Guitar P2 Wail Y", AnalogType::LinearCentered},
{"Guitar P2 Wail Z", AnalogType::LinearCentered},
{"Guitar P2 Knob", AnalogType::Circular}
});
}
return analogs;
+11 -10
View File
@@ -63,16 +63,17 @@ std::vector<Analog> &games::iidx::get_analogs() {
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Beatmania IIDX");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Turntable P1",
"Turntable P2",
"VEFX",
"Low-EQ",
"Hi-EQ",
"Filter",
"Play Volume"
);
using namespace GameAPI::Analogs;
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
{ "Turntable P1", AnalogType::Circular },
{ "Turntable P2", AnalogType::Circular },
{ "VEFX", AnalogType::LinearPositive },
{ "Low-EQ", AnalogType::LinearPositive },
{ "Hi-EQ", AnalogType::LinearPositive },
{ "Filter", AnalogType::LinearPositive },
{ "Play Volume", AnalogType::LinearPositive }
});
}
return analogs;
}
+5 -5
View File
@@ -36,11 +36,11 @@ std::vector<Analog> &games::mga::get_analogs() {
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Metal Gear");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Joy X",
"Joy Y"
);
using namespace GameAPI::Analogs;
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
{ "Joy X", AnalogType::LinearCentered },
{ "Joy Y", AnalogType::LinearCentered }
});
}
return analogs;
+32 -31
View File
@@ -165,37 +165,38 @@ std::vector<Analog> &games::nost::get_analogs() {
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Nostalgia");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Key 1",
"Key 2",
"Key 3",
"Key 4",
"Key 5",
"Key 6",
"Key 7",
"Key 8",
"Key 9",
"Key 10",
"Key 11",
"Key 12",
"Key 13",
"Key 14",
"Key 15",
"Key 16",
"Key 17",
"Key 18",
"Key 19",
"Key 20",
"Key 21",
"Key 22",
"Key 23",
"Key 24",
"Key 25",
"Key 26",
"Key 27",
"Key 28"
);
using namespace GameAPI::Analogs;
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
{ "Key 1", AnalogType::LinearPositive },
{ "Key 2", AnalogType::LinearPositive },
{ "Key 3", AnalogType::LinearPositive },
{ "Key 4", AnalogType::LinearPositive },
{ "Key 5", AnalogType::LinearPositive },
{ "Key 6", AnalogType::LinearPositive },
{ "Key 7", AnalogType::LinearPositive },
{ "Key 8", AnalogType::LinearPositive },
{ "Key 9", AnalogType::LinearPositive },
{ "Key 10", AnalogType::LinearPositive },
{ "Key 11", AnalogType::LinearPositive },
{ "Key 12", AnalogType::LinearPositive },
{ "Key 13", AnalogType::LinearPositive },
{ "Key 14", AnalogType::LinearPositive },
{ "Key 15", AnalogType::LinearPositive },
{ "Key 16", AnalogType::LinearPositive },
{ "Key 17", AnalogType::LinearPositive },
{ "Key 18", AnalogType::LinearPositive },
{ "Key 19", AnalogType::LinearPositive },
{ "Key 20", AnalogType::LinearPositive },
{ "Key 21", AnalogType::LinearPositive },
{ "Key 22", AnalogType::LinearPositive },
{ "Key 23", AnalogType::LinearPositive },
{ "Key 24", AnalogType::LinearPositive },
{ "Key 25", AnalogType::LinearPositive },
{ "Key 26", AnalogType::LinearPositive },
{ "Key 27", AnalogType::LinearPositive },
{ "Key 28", AnalogType::LinearPositive }
});
}
return analogs;
}
+6 -5
View File
@@ -51,11 +51,12 @@ std::vector<Analog> &games::pc::get_analogs() {
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Polaris Chord");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Fader-L",
"Fader-R"
);
using GameAPI::Analogs::AnalogType;
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
{ "Fader-L", AnalogType::LinearCentered },
{ "Fader-R", AnalogType::LinearCentered }
});
}
return analogs;
+7 -6
View File
@@ -35,12 +35,13 @@ std::vector<Analog> &games::rf3d::get_analogs() {
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Road Fighters 3D");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Wheel",
"Accelerate",
"Brake"
);
using GameAPI::Analogs::AnalogType;
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
{ "Wheel", AnalogType::LinearCentered },
{ "Accelerate", AnalogType::LinearPositive },
{ "Brake", AnalogType::LinearPositive }
});
}
return analogs;
+8 -7
View File
@@ -31,13 +31,14 @@ std::vector<Analog> &games::sc::get_analogs() {
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Steel Chronicle");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Left Stick X",
"Left Stick Y",
"Right Stick X",
"Right Stick Y"
);
using GameAPI::Analogs::AnalogType;
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
{ "Left Stick X", AnalogType::LinearCentered },
{ "Left Stick Y", AnalogType::LinearCentered },
{ "Right Stick X", AnalogType::LinearCentered },
{ "Right Stick Y", AnalogType::LinearCentered }
});
}
return analogs;
+6 -5
View File
@@ -31,11 +31,12 @@ std::vector<Analog> &games::silentscope::get_analogs() {
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Silent Scope: Bone Eater");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Gun X",
"Gun Y"
);
using GameAPI::Analogs::AnalogType;
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
{ "Gun X", AnalogType::LinearCentered },
{ "Gun Y", AnalogType::LinearCentered }
});
}
return analogs;
+8 -7
View File
@@ -50,13 +50,14 @@ std::vector<Analog> &games::we::get_analogs() {
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Winning Eleven");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Pad Stick Left X",
"Pad Stick Left Y",
"Pad Stick Right X",
"Pad Stick Right Y"
);
using GameAPI::Analogs::AnalogType;
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
{ "Pad Stick Left X", AnalogType::LinearCentered },
{ "Pad Stick Left Y", AnalogType::LinearCentered },
{ "Pad Stick Right X", AnalogType::LinearCentered },
{ "Pad Stick Right Y", AnalogType::LinearCentered }
});
}
return analogs;
+47 -29
View File
@@ -2506,31 +2506,45 @@ namespace overlay::windows {
const bool value_changed =
ImGui::SliderFloat("Sensitivity", &sensitivity, 0.f, 2.f, "%.3f");
ImGui::SameLine();
ImGui::HelpMarker(
"Adjust floating point multiplier to relative movement.\n\n"
"Value is squared before being multiplied (e.g., 1.44 is 2x sensitivity, 2.00 is 4x).\n\n"
"Dependent on how often the game polls for input. Intended for angular input (knobs, turntables)");
if (device->type == rawinput::HID && analog.getType() != GameAPI::Analogs::AnalogType::Circular) {
ImGui::HelpMarker(
"Adjust the analog sensitivity curve; "
"values <1.0 are less sensitive around neutral, "
"values >1.0 are more sensitive.");
} else {
ImGui::HelpMarker(
"Adjust floating point multiplier to relative movement.\n\n"
"Value is squared before being multiplied (e.g., 1.44 is 2x sensitivity, 2.00 is 4x).");
}
if (value_changed) {
analog.setSensitivity(sensitivity * sensitivity);
}
}
if (device->type == rawinput::HID || device->type == rawinput::MIDI) {
// hide deadzone for circular analog since it doesn't make any sense (unless in relative mode)
if ((device->type == rawinput::HID || device->type == rawinput::MIDI) &&
((analog.getType() != GameAPI::Analogs::AnalogType::Circular) || analog.isRelativeMode())) {
auto deadzone = analog.getDeadzone();
// for back compat (before each analog had a type)
if (deadzone < 0.f) {
deadzone = -deadzone;
analog.setDeadzone(deadzone);
}
const bool value_changed =
ImGui::SliderFloat("Deadzone", &deadzone, -0.999f, 0.999f, "%.3f");
ImGui::SliderFloat("Deadzone", &deadzone, 0.f, 0.999f, "%.3f");
if (value_changed) {
analog.setDeadzone(deadzone);
}
ImGui::SameLine();
ImGui::HelpMarker("Positive values specify a deadzone around the middle.\n"
"Negative values specify a deadzone from the minimum value.");
ImGui::HelpMarker("Specify the deadzone that gets applied to at-rest (neutral) value.");
// deadzone mirror
bool deadzone_mirror = analog.getDeadzoneMirror();
ImGui::Checkbox("Deadzone Mirror", &deadzone_mirror);
ImGui::SameLine();
ImGui::HelpMarker("Positive deadzone values cut off at edges instead.\n"
"Negative deadzone values cut off at maximum value instead.");
ImGui::HelpMarker("Apply deadzone to extreme value(s) instead of neutral.");
if (deadzone_mirror != analog.getDeadzoneMirror()) {
analog.setDeadzoneMirror(deadzone_mirror);
}
@@ -2549,23 +2563,26 @@ namespace overlay::windows {
if (this->analogs_devices_selected >= 0) {
const auto device = this->analogs_devices.at(this->analogs_devices_selected);
if (device->type == rawinput::HID) {
// smoothing
bool smoothing = analog.getSmoothing();
ImGui::BeginDisabled(analog.isRelativeMode());
ImGui::Checkbox("Smooth Axis (adds latency)", &smoothing);
ImGui::SameLine();
ImGui::HelpMarker(
"Apply a moving average algorithm; intended for angular input (knobs, turntables). "
"Adds a slight bit of latency to input as the algorithm averages out recent input. "
"Only use in dire situations where the input is too jittery for the game.");
ImGui::EndDisabled();
if (smoothing != analog.getSmoothing()) {
analog.setSmoothing(smoothing);
if (analog.getType() == GameAPI::Analogs::AnalogType::Circular) {
// smoothing
bool smoothing = analog.getSmoothing();
ImGui::BeginDisabled(analog.isRelativeMode());
ImGui::Checkbox("Smooth Axis (adds latency)", &smoothing);
ImGui::SameLine();
ImGui::HelpMarker(
"Apply a moving average algorithm; intended for angular input (knobs, turntables). "
"Adds a slight bit of latency to input as the algorithm averages out recent input. "
"Only use in dire situations where the input is too jittery for the game.");
ImGui::EndDisabled();
if (smoothing != analog.getSmoothing()) {
analog.setSmoothing(smoothing);
}
}
// relative input mode
bool relative_analog = analog.isRelativeMode();
ImGui::Checkbox("Relative Axis (experimental)", &relative_analog);
ImGui::Checkbox("Relative Axis", &relative_analog);
ImGui::SameLine();
ImGui::HelpMarker(
"Use relative directional input instead of positional values.\n\n"
@@ -2579,8 +2596,7 @@ namespace overlay::windows {
// delay buffer
int delay = analog.getDelayBufferDepth();
ImGui::InputInt("Delay (experimental)", &delay, 1, 10);
if (ImGui::IsItemDeactivatedAfterEdit()) {
if (ImGui::InputInt("Delay", &delay, 1, 10)) {
delay = CLAMP(delay, 0, 256);
analog.setDelayBufferDepth(delay);
}
@@ -2600,10 +2616,12 @@ namespace overlay::windows {
ImGui::ProgressBar(value);
// centered knob preview
const float knob_size = 64.f;
auto width = ImGui::GetContentRegionAvail().x - knob_size;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (width / 2));
ImGui::Knob(value, knob_size);
if (analog.getType() == GameAPI::Analogs::AnalogType::Circular) {
const float knob_size = 64.f;
auto width = ImGui::GetContentRegionAvail().x - knob_size;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (width / 2));
ImGui::Knob(value, knob_size);
}
// update analog
if (analogs_devices_selected >= 0 && analogs_devices_selected < (int) analogs_devices.size()) {