rawinput: reimplement analog relative mode and delay (#674)

## Link to GitHub Issue or related Pull Request, if one exists
#181 

## Description of change
Implement analog relative mode and delay option using a new
millisecond-based algorithm.

## Testing
WIP
This commit is contained in:
bicarus
2026-05-04 22:50:11 -07:00
committed by GitHub
parent 5f94ef4478
commit 96950b6b4e
13 changed files with 250 additions and 132 deletions
+37 -37
View File
@@ -39,8 +39,8 @@
<button name="VEFX" vkey="10" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/> <button name="VEFX" vkey="10" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons> </buttons>
<analogs> <analogs>
<analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Turntable P2" devid="Player 2" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Turntable P2" devid="Player 2" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
</analogs> </analogs>
<lights> <lights>
<light name="P1 1" devid="Player 1" index="0"/> <light name="P1 1" devid="Player 1" index="0"/>
@@ -117,8 +117,8 @@
<keypad_buttons/> <keypad_buttons/>
<!-- Assumes analog mode. Need to invert direction since this FW is designed for INF. --> <!-- Assumes analog mode. Need to invert direction since this FW is designed for INF. -->
<analogs> <analogs>
<analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1"/> <analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Turntable P2" devid="arcin (2p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1"/> <analog name="Turntable P2" devid="arcin (2p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
</analogs> </analogs>
<lights> <lights>
<light name="P1 1" devid="arcin (1p)" index="0"/> <light name="P1 1" devid="arcin (1p)" index="0"/>
@@ -144,34 +144,34 @@
<buttons/> <buttons/>
<keypad_buttons/> <keypad_buttons/>
<analogs> <analogs>
<analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 2" devid="Analogs key 01-28" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 2" devid="Analogs key 01-28" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 3" devid="Analogs key 01-28" index="6" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 3" devid="Analogs key 01-28" index="6" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 4" devid="Analogs key 01-28" index="7" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 4" devid="Analogs key 01-28" index="7" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 5" devid="Analogs key 01-28" index="8" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 5" devid="Analogs key 01-28" index="8" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 6" devid="Analogs key 01-28" index="9" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 6" devid="Analogs key 01-28" index="9" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 7" devid="Analogs key 01-28" index="10" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 7" devid="Analogs key 01-28" index="10" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 8" devid="Analogs key 01-28" index="11" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 8" devid="Analogs key 01-28" index="11" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 9" devid="Analogs key 01-28" index="12" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 9" devid="Analogs key 01-28" index="12" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 10" devid="Analogs key 01-28" index="13" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 10" devid="Analogs key 01-28" index="13" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 11" devid="Analogs key 01-28" index="14" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 11" devid="Analogs key 01-28" index="14" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 12" devid="Analogs key 01-28" index="15" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 12" devid="Analogs key 01-28" index="15" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 13" devid="Analogs key 01-28" index="16" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 13" devid="Analogs key 01-28" index="16" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 14" devid="Analogs key 01-28" index="17" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 14" devid="Analogs key 01-28" index="17" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 15" devid="Analogs key 01-28" index="18" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 15" devid="Analogs key 01-28" index="18" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 16" devid="Analogs key 01-28" index="19" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 16" devid="Analogs key 01-28" index="19" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 17" devid="Analogs key 01-28" index="20" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 17" devid="Analogs key 01-28" index="20" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 18" devid="Analogs key 01-28" index="21" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 18" devid="Analogs key 01-28" index="21" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 19" devid="Analogs key 01-28" index="22" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 19" devid="Analogs key 01-28" index="22" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 20" devid="Analogs key 01-28" index="23" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 20" devid="Analogs key 01-28" index="23" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 21" devid="Analogs key 01-28" index="24" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 21" devid="Analogs key 01-28" index="24" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 22" devid="Analogs key 01-28" index="25" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 22" devid="Analogs key 01-28" index="25" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 23" devid="Analogs key 01-28" index="26" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 23" devid="Analogs key 01-28" index="26" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 24" devid="Analogs key 01-28" index="27" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 24" devid="Analogs key 01-28" index="27" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 25" devid="Analogs key 01-28" index="28" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 25" devid="Analogs key 01-28" index="28" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 26" devid="Analogs key 01-28" index="29" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 26" devid="Analogs key 01-28" index="29" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 27" devid="Analogs key 01-28" index="30" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 27" devid="Analogs key 01-28" index="30" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Key 28" devid="Analogs key 01-28" index="31" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Key 28" devid="Analogs key 01-28" index="31" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
</analogs> </analogs>
<lights> <lights>
<light name="Key 1 R" devid="Lights 01-14" index="0"/> <light name="Key 1 R" devid="Lights 01-14" index="0"/>
@@ -277,9 +277,9 @@
</buttons> </buttons>
<keypad_buttons/> <keypad_buttons/>
<analogs> <analogs>
<analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Guitar P1 Wail Y" devid="Gitaller P1" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Guitar P1 Wail Y" devid="Gitaller P1" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="Guitar P1 Wail Z" devid="Gitaller P1" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="Guitar P1 Wail Z" devid="Gitaller P1" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
</analogs> </analogs>
<lights/> <lights/>
<!-- Gen 1 FAUCETWO in HID mode; should also work for later gens --> <!-- Gen 1 FAUCETWO in HID mode; should also work for later gens -->
@@ -296,8 +296,8 @@
</buttons> </buttons>
<keypad_buttons/> <keypad_buttons/>
<analogs> <analogs>
<analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
<analog name="VOL-R" devid="Buttons (MI_01)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/> <analog name="VOL-R" devid="Buttons (MI_01)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
</analogs> </analogs>
<lights> <lights>
<light name="BT-A" devid="Lights (MI_02)" index="24"/> <light name="BT-A" devid="Lights (MI_02)" index="24"/>
+73 -1
View File
@@ -237,7 +237,8 @@ float Analog::applyDeadzone(float raw_value) {
deadzone = -deadzone; deadzone = -deadzone;
} }
if (getType() != GameAPI::Analogs::AnalogType::LinearPositive) { // 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 // calculate values
const auto delta = value - 0.5f; const auto delta = value - 0.5f;
@@ -291,3 +292,74 @@ float Analog::applyDeadzone(float raw_value) {
} }
return value; return value;
} }
float Analog::getRelativeModeValue(float raw_value) {
const auto now = get_performance_seconds();
auto delta_time = now - this->rel_mode_last_read_time_s;
if (this->rel_mode_last_read_time_s != 0.f) {
// some heuristics to prevent huge jumps:
// * if we went for more than 250ms without polls, discard it (e.g., during loading screens)
// * cap the delta at 100ms to prevent huge jumps in case of very infrequent polling
if (delta_time < 0.f || 0.25f < delta_time) {
delta_time = 0.f;
} else if (delta_time > 0.1f) {
delta_time = 0.1f;
}
// scale [0, 1] to [-1, 1] to simplify calculations
const auto delta_raw_value = (raw_value - 0.5f) * 2.f;
// target is one revolution per second at max speed at 1.0 sensitivity
auto adjusted_delta_value = delta_raw_value * delta_time;
// multiplier / divisor
if (this->getMultiplier() > 1) {
adjusted_delta_value *= this->getMultiplier();
} else if (this->getMultiplier() < -1) {
adjusted_delta_value /= -this->getMultiplier();
}
// sensitivity
if (this->isSensitivitySet()) {
adjusted_delta_value *= this->getSensitivity();
}
// calculate the new absolute value
this->rel_mode_absolute_value += adjusted_delta_value;
}
// update for next poll
this->rel_mode_last_read_time_s = now;
this->rel_mode_absolute_value = normalizeAnalogValue(this->rel_mode_absolute_value);
return this->rel_mode_absolute_value;
}
float Analog::getDelayedValue(float raw_value) {
const double delay_ms = static_cast<double>(this->getDelayMs());
if (delay_ms == 0.0) {
return raw_value;
}
// always push a new value
const auto now = get_performance_milliseconds();
this->delayed_inputs.emplace(now, raw_value);
// drain the queue down to reasonable length to prevent unconstrained growth
// this would accommodate 1 second at ~1000Hz which is overkill
// (UI only allows for 250ms of delay)
while (this->delayed_inputs.size() > 1024) {
this->delayed_inputs.pop();
}
// pop until we find the oldest value still inside the delay window
while (this->delayed_inputs.size() > 1) {
const auto delta_t = now - this->delayed_inputs.front().time_in_ms;
if (delta_t <= delay_ms) {
break;
}
this->delayed_inputs.pop();
}
return this->delayed_inputs.front().value;
}
+40
View File
@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <cstdint>
#include <array> #include <array>
#include <string> #include <string>
#include <cmath> #include <cmath>
@@ -34,6 +35,11 @@ struct AnalogMovingAverage {
float cosine; float cosine;
}; };
struct AnalogDelayEntry {
double time_in_ms;
float value;
};
class Analog { class Analog {
private: private:
std::string name; std::string name;
@@ -63,6 +69,15 @@ private:
float divisor_previous_value = 0.5f; float divisor_previous_value = 0.5f;
unsigned short divisor_region = 0; unsigned short divisor_region = 0;
// relative input mode
float rel_mode_absolute_value = 0.5f;
float rel_mode_last_read_time_s = 0.f;
bool relative_mode = false;
// delay
uint32_t delay_ms = 0;
std::queue<AnalogDelayEntry> delayed_inputs;
float calculateAngularDifference(float old_rads, float new_rads); float calculateAngularDifference(float old_rads, float new_rads);
float normalizeAngle(float rads); float normalizeAngle(float rads);
float normalizeAnalogValue(float value); float normalizeAnalogValue(float value);
@@ -83,6 +98,8 @@ public:
float applyAngularSensitivity(float raw_rads); float applyAngularSensitivity(float raw_rads);
float applyMultiplier(float raw_value); float applyMultiplier(float raw_value);
float applyDeadzone(float raw_value); float applyDeadzone(float raw_value);
float getRelativeModeValue(float raw_value);
float getDelayedValue(float raw_value);
inline bool isSet() { inline bool isSet() {
if (this->override_enabled) { if (this->override_enabled) {
@@ -98,7 +115,9 @@ public:
smoothing = false; smoothing = false;
deadzone_mirror = false; deadzone_mirror = false;
setMultiplier(1); setMultiplier(1);
setRelativeMode(false);
setLastState(0.5f); setLastState(0.5f);
setDelayMs(0);
} }
inline void clearBindings() { inline void clearBindings() {
@@ -195,6 +214,19 @@ public:
this->last_state = last_state; this->last_state = last_state;
} }
inline bool isRelativeMode() const {
return this->relative_mode;
}
inline void setRelativeMode(bool relative_mode) {
if (relative_mode) {
this->smoothing = false;
}
this->relative_mode = relative_mode;
this->rel_mode_absolute_value = 0.5f;
this->rel_mode_last_read_time_s = 0.f;
}
inline GameAPI::Analogs::AnalogType getType() const { inline GameAPI::Analogs::AnalogType getType() const {
return this->type; return this->type;
} }
@@ -202,4 +234,12 @@ public:
inline void setType(GameAPI::Analogs::AnalogType type) { inline void setType(GameAPI::Analogs::AnalogType type) {
this->type = type; this->type = type;
} }
inline uint32_t getDelayMs() const {
return this->delay_ms;
}
inline void setDelayMs(uint32_t delay_ms) {
this->delay_ms = delay_ms;
}
}; };
+15 -2
View File
@@ -679,12 +679,18 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::D
} }
// deadzone // deadzone
// do not apply deadzone to circular analogs since it doesn't make sense // do not apply deadzone to circular analogs since it doesn't make sense (except in relative mode)
if (analog.isDeadzoneSet() && analog.getType() != AnalogType::Circular) { if (analog.isDeadzoneSet() &&
(analog.getType() != AnalogType::Circular || analog.isRelativeMode())) {
value = analog.applyDeadzone(value); value = analog.applyDeadzone(value);
} }
if (analog.getType() == AnalogType::Circular) { if (analog.getType() == AnalogType::Circular) {
if (analog.isRelativeMode()) {
value = analog.getRelativeModeValue(value);
} else {
// integer multiplier // integer multiplier
value = analog.applyMultiplier(value); value = analog.applyMultiplier(value);
@@ -712,6 +718,7 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::D
// apply to value // apply to value
value = rads * (float) M_1_TAU; value = rads * (float) M_1_TAU;
} }
}
} else { } else {
// sensitivity // sensitivity
if (analog.isSensitivitySet()) { if (analog.isSensitivitySet()) {
@@ -760,6 +767,12 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::D
value = std::clamp(value, 0.f, 1.f); value = std::clamp(value, 0.f, 1.f);
} }
} }
// delay
if (analog.getDelayMs() > 0) {
value = analog.getDelayedValue(value);
}
break; break;
} }
case rawinput::MIDI: { case rawinput::MIDI: {
+22
View File
@@ -252,6 +252,8 @@ bool Config::addGame(Game &game) {
bool invert = false; bool invert = false;
bool smoothing = false; bool smoothing = false;
int multiplier = 1; int multiplier = 1;
bool relative_mode = false;
uint32_t delay = 0;
tinyxml2::XMLError err1 = gameAnalogNode->QueryIntAttribute("index", &index); tinyxml2::XMLError err1 = gameAnalogNode->QueryIntAttribute("index", &index);
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity); gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone); gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
@@ -259,6 +261,8 @@ bool Config::addGame(Game &game) {
gameAnalogNode->QueryBoolAttribute("invert", &invert); gameAnalogNode->QueryBoolAttribute("invert", &invert);
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing); gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier); gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
gameAnalogNode->QueryUnsignedAttribute("delay_ms", &delay);
const char *devid = gameAnalogNode->Attribute("devid"); const char *devid = gameAnalogNode->Attribute("devid");
if (err1 != tinyxml2::XMLError::XML_SUCCESS || !devid) { if (err1 != tinyxml2::XMLError::XML_SUCCESS || !devid) {
@@ -273,6 +277,8 @@ bool Config::addGame(Game &game) {
gameAnalogNode->SetAttribute("invert", it.getInvert()); gameAnalogNode->SetAttribute("invert", it.getInvert());
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing()); gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier()); gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
gameAnalogNode->SetAttribute("delay_ms", it.getDelayMs());
gameAnalogsNode->InsertEndChild(gameAnalogNode); gameAnalogsNode->InsertEndChild(gameAnalogNode);
} else { } else {
it.setIndex(static_cast<unsigned short int>(index)); it.setIndex(static_cast<unsigned short int>(index));
@@ -283,6 +289,8 @@ bool Config::addGame(Game &game) {
it.setInvert(invert); it.setInvert(invert);
it.setSmoothing(smoothing); it.setSmoothing(smoothing);
it.setMultiplier(multiplier); it.setMultiplier(multiplier);
it.setRelativeMode(relative_mode);
it.setDelayMs(delay);
} }
} else { } else {
gameAnalogNode = this->configFile.NewElement("analog"); gameAnalogNode = this->configFile.NewElement("analog");
@@ -294,6 +302,8 @@ bool Config::addGame(Game &game) {
gameAnalogNode->SetAttribute("invert", it.getInvert()); gameAnalogNode->SetAttribute("invert", it.getInvert());
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing()); gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier()); gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
gameAnalogNode->SetAttribute("delay_ms", it.getDelayMs());
gameAnalogNode->SetAttribute("devid", it.getDeviceIdentifier().c_str()); gameAnalogNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
gameAnalogsNode->InsertEndChild(gameAnalogNode); gameAnalogsNode->InsertEndChild(gameAnalogNode);
} }
@@ -447,6 +457,8 @@ bool Config::addGame(Game &game) {
gameAnalogNode->SetAttribute("invert", it.getInvert()); gameAnalogNode->SetAttribute("invert", it.getInvert());
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing()); gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier()); gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
gameAnalogNode->SetAttribute("delay_ms", it.getDelayMs());
gameAnalogsNode->InsertEndChild(gameAnalogNode); gameAnalogsNode->InsertEndChild(gameAnalogNode);
} }
@@ -679,6 +691,8 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
gameAnalogNode->SetAttribute("invert", analog.getInvert()); gameAnalogNode->SetAttribute("invert", analog.getInvert());
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing()); gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier()); gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
gameAnalogNode->SetAttribute("delay_ms", analog.getDelayMs());
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str()); gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
} else { } else {
gameAnalogNode = this->configFile.NewElement("analog"); gameAnalogNode = this->configFile.NewElement("analog");
@@ -689,6 +703,8 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
gameAnalogNode->SetAttribute("invert", analog.getInvert()); gameAnalogNode->SetAttribute("invert", analog.getInvert());
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing()); gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier()); gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
gameAnalogNode->SetAttribute("delay_ms", analog.getDelayMs());
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str()); gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
gameAnalogsNode->InsertEndChild(gameAnalogNode); gameAnalogsNode->InsertEndChild(gameAnalogNode);
} }
@@ -1125,6 +1141,8 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
bool invert = false; bool invert = false;
bool smoothing = false; bool smoothing = false;
int multiplier = 1; int multiplier = 1;
bool relative_mode = false;
uint32_t delay = 0;
gameAnalogNode->QueryIntAttribute("index", &index); gameAnalogNode->QueryIntAttribute("index", &index);
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity); gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone); gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
@@ -1132,6 +1150,8 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
gameAnalogNode->QueryBoolAttribute("invert", &invert); gameAnalogNode->QueryBoolAttribute("invert", &invert);
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing); gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier); gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
gameAnalogNode->QueryUnsignedAttribute("delay_ms", &delay);
const char *devid = gameAnalogNode->Attribute("devid"); const char *devid = gameAnalogNode->Attribute("devid");
// create analog and add to list // create analog and add to list
@@ -1143,6 +1163,8 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
analog.setInvert(invert); analog.setInvert(invert);
analog.setSmoothing(smoothing); analog.setSmoothing(smoothing);
analog.setMultiplier(multiplier); analog.setMultiplier(multiplier);
analog.setRelativeMode(relative_mode);
analog.setDelayMs(delay);
if (devid) { if (devid) {
analog.setDeviceIdentifier(devid); analog.setDeviceIdentifier(devid);
} }
+4
View File
@@ -69,6 +69,8 @@ namespace overlay::windows {
el->SetAttribute("invert", analog.invert); el->SetAttribute("invert", analog.invert);
el->SetAttribute("smoothing", analog.smoothing); el->SetAttribute("smoothing", analog.smoothing);
el->SetAttribute("multiplier", analog.multiplier); el->SetAttribute("multiplier", analog.multiplier);
el->SetAttribute("relative", analog.relative_mode);
el->SetAttribute("delay_ms", analog.delay_ms);
parent->InsertEndChild(el); parent->InsertEndChild(el);
} }
@@ -90,6 +92,8 @@ namespace overlay::windows {
el->QueryBoolAttribute("invert", &a.invert); el->QueryBoolAttribute("invert", &a.invert);
el->QueryBoolAttribute("smoothing", &a.smoothing); el->QueryBoolAttribute("smoothing", &a.smoothing);
el->QueryIntAttribute("multiplier", &a.multiplier); el->QueryIntAttribute("multiplier", &a.multiplier);
el->QueryBoolAttribute("relative", &a.relative_mode);
el->QueryUnsignedAttribute("delay_ms", &a.delay_ms);
return a; return a;
} }
+1 -36
View File
@@ -783,42 +783,7 @@ namespace games::iidx {
} }
// return higher 8 bit // return higher 8 bit
uint8_t result = (uint8_t) (ret_value >> 2); return (uint8_t)(ret_value >> 2);
// delay
if ((player == 0 && TT_DELAY_P1 > 0) ||
(player == 1 && TT_DELAY_P2 > 0)) {
static std::queue<std::pair<double, uint8_t>> delay_queue[2];
auto &queue = delay_queue[player];
const auto max_delta_ms =
static_cast<double>((player == 0) ? TT_DELAY_P1 : TT_DELAY_P2);
// always push a new value
const auto now = get_performance_milliseconds();
queue.push(std::make_pair(now, result));
// drain the queue down to reasonable length to prevent unconstrained growth
// this would accommodate 1 second at ~1000Hz
// (in reality all three iidx I/O emulation runs well under 500Hz)
while (queue.size() > 1024) {
queue.pop();
}
// pop until we find one that falls just under the time threshold
while (!queue.empty()) {
const auto delta_t = now - queue.front().first;
if (delta_t <= max_delta_ms) {
break;
}
queue.pop();
}
result = queue.front().second;
}
return result;
} }
unsigned char get_slider(uint8_t slider) { unsigned char get_slider(uint8_t slider) {
-2
View File
@@ -31,8 +31,6 @@ namespace games::iidx {
extern bool NATIVE_TOUCH; extern bool NATIVE_TOUCH;
extern std::optional<std::string> SOUND_OUTPUT_DEVICE; extern std::optional<std::string> SOUND_OUTPUT_DEVICE;
extern std::optional<std::string> ASIO_DRIVER; extern std::optional<std::string> ASIO_DRIVER;
extern uint32_t TT_DELAY_P1;
extern uint32_t TT_DELAY_P2;
extern uint8_t DIGITAL_TT_SENS; extern uint8_t DIGITAL_TT_SENS;
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE; extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
extern std::optional<std::string> SCREEN_MODE; extern std::optional<std::string> SCREEN_MODE;
-6
View File
@@ -518,12 +518,6 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::IIDXTDJMode].value_bool()) { if (options[launcher::Options::IIDXTDJMode].value_bool()) {
games::iidx::TDJ_MODE = true; games::iidx::TDJ_MODE = true;
} }
if (options[launcher::Options::IIDXTTDelayP1].is_active()) {
games::iidx::TT_DELAY_P1 = options[launcher::Options::IIDXTTDelayP1].value_uint32();
}
if (options[launcher::Options::IIDXTTDelayP2].is_active()) {
games::iidx::TT_DELAY_P2 = options[launcher::Options::IIDXTTDelayP2].value_uint32();
}
if (options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].is_active()) { if (options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].is_active()) {
games::iidx::DIGITAL_TT_SENS = (uint8_t) games::iidx::DIGITAL_TT_SENS = (uint8_t)
options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].value_uint32(); options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].value_uint32();
-24
View File
@@ -655,30 +655,6 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options", .category = "Game Options",
}, },
{
// IIDXTTDelayP1
.title = "IIDX TT Delay ms (Player 1)",
.name = "iidxttdelayp1",
.desc = "Delays turntable by number of milliseconds. "
"Delay will be *at most* the specified period; maximum error is dependent on the game's poll rate. "
"As usual, changing any option requires a restart. Default: 0 (no delay).",
.type = OptionType::Integer,
.setting_name = "(0-500)",
.game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)",
},
{
// IIDXTTDelayP2
.title = "IIDX TT Delay ms (Player 2)",
.name = "iidxttdelayp2",
.desc = "Delays turntable by number of milliseconds. "
"Delay will be *at most* the specified period; maximum error is dependent on the game's poll rate. "
"As usual, changing any option requires a restart. Default: 0 (no delay).",
.type = OptionType::Integer,
.setting_name = "(0-500)",
.game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)",
},
{ {
// spice2x_IIDXDigitalTTSensitivity // spice2x_IIDXDigitalTTSensitivity
.title = "IIDX Digital TT Sensitivity", .title = "IIDX Digital TT Sensitivity",
-2
View File
@@ -69,8 +69,6 @@ namespace launcher {
IIDXAsioDriver, IIDXAsioDriver,
IIDXBIO2FW, IIDXBIO2FW,
IIDXTDJMode, IIDXTDJMode,
IIDXTTDelayP1,
IIDXTTDelayP2,
spice2x_IIDXDigitalTTSensitivity, spice2x_IIDXDigitalTTSensitivity,
IIDXDigitalTTSocd, IIDXDigitalTTSocd,
spice2x_IIDXLDJForce720p, spice2x_IIDXLDJForce720p,
+34 -2
View File
@@ -2539,9 +2539,9 @@ namespace overlay::windows {
} }
} }
// hide deadzone for circular analog since it doesn't make any sense // 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) && if ((device->type == rawinput::HID || device->type == rawinput::MIDI) &&
analog.getType() != GameAPI::Analogs::AnalogType::Circular) { ((analog.getType() != GameAPI::Analogs::AnalogType::Circular) || analog.isRelativeMode())) {
auto deadzone = analog.getDeadzone(); auto deadzone = analog.getDeadzone();
// for back compat (before each analog had a type) // for back compat (before each analog had a type)
@@ -2585,17 +2585,47 @@ namespace overlay::windows {
if (analog.getType() == GameAPI::Analogs::AnalogType::Circular) { if (analog.getType() == GameAPI::Analogs::AnalogType::Circular) {
// smoothing // smoothing
bool smoothing = analog.getSmoothing(); bool smoothing = analog.getSmoothing();
ImGui::BeginDisabled(analog.isRelativeMode());
ImGui::Checkbox("Smooth Axis (adds latency)", &smoothing); ImGui::Checkbox("Smooth Axis (adds latency)", &smoothing);
ImGui::SameLine(); ImGui::SameLine();
ImGui::HelpMarker( ImGui::HelpMarker(
"Apply a moving average algorithm; intended for angular input (knobs, turntables). " "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. " "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."); "Only use in dire situations where the input is too jittery for the game.");
ImGui::EndDisabled();
if (smoothing != analog.getSmoothing()) { if (smoothing != analog.getSmoothing()) {
analog.setSmoothing(smoothing); analog.setSmoothing(smoothing);
} }
// relative input mode
bool relative_analog = analog.isRelativeMode();
ImGui::Checkbox("Relative Axis", &relative_analog);
ImGui::SameLine();
ImGui::HelpMarker(
"Use relative directional input instead of positional values. "
"Can be used to translate analog sticks to knob input.\n\n"
"At default settings, max speed is one revolution per second.\n\n"
"Adjust sensitivity to increase/decrease max speed, and deadzone to prevent jitter.");
if (relative_analog != analog.isRelativeMode()) {
analog.setRelativeMode(relative_analog);
} }
} }
// delay
int delay = analog.getDelayMs();
if (ImGui::InputInt("Delay (ms)", &delay, 1, 1)) {
delay = std::clamp(delay, 0, 250);
analog.setDelayMs(delay);
}
ImGui::SameLine();
ImGui::HelpMarker(
"Adds an artificial delay, in milliseconds.\n\n"
"Value read by the game engine will be delayed AT MOST this amount of time (coould be slightly less).\n\n"
"To minimize quantization error, delay value should be a multiple of the game's poll rate, "
"add 0.5ms of buffer, and then round up to the nearest millisecond.\n\n"
"If you aren't sure, assume the game polls for input at the same rate as the display FPS."
);
}
} }
// current state // current state
@@ -5406,6 +5436,8 @@ namespace overlay::windows {
a.setInvert(ta.invert); a.setInvert(ta.invert);
a.setSmoothing(ta.smoothing); a.setSmoothing(ta.smoothing);
a.setMultiplier(ta.multiplier); a.setMultiplier(ta.multiplier);
a.setRelativeMode(ta.relative_mode);
a.setDelayMs(ta.delay_ms);
::Config::getInstance().updateBinding(game, a); ::Config::getInstance().updateBinding(game, a);
break; break;
} }
@@ -71,6 +71,8 @@ namespace overlay::windows {
bool invert = false; bool invert = false;
bool smoothing = false; bool smoothing = false;
int multiplier = 1; int multiplier = 1;
bool relative_mode = false;
uint32_t delay_ms = 0;
bool is_device() const { return !device_identifier.empty(); } bool is_device() const { return !device_identifier.empty(); }
bool is_unbound() const { return device_identifier.empty() && index == 0xFF; } bool is_unbound() const { return device_identifier.empty() && index == 0xFF; }
@@ -87,6 +89,8 @@ namespace overlay::windows {
invert = a.getInvert(); invert = a.getInvert();
smoothing = a.getSmoothing(); smoothing = a.getSmoothing();
multiplier = a.getMultiplier(); multiplier = a.getMultiplier();
relative_mode = a.isRelativeMode();
delay_ms = a.getDelayMs();
} }
}; };