mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
jb: touch debounce (#798)
## Link to GitHub Issue or related Pull Request, if one exists n/a ## Description of change Debouce option for touch input. Applies to all touch handlers (rawinput, win8, win7) since it sits at the touch layer, and then consumed by jb touch logic. ## Testing
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
#include "touch/touch.h"
|
#include "touch/touch.h"
|
||||||
#include "rawinput/touch.h"
|
#include "rawinput/touch.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/time.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
// touch layout: a 4x4 grid of 160px buttons separated by 37 / 38 / 37 px gaps
|
// touch layout: a 4x4 grid of 160px buttons separated by 37 / 38 / 37 px gaps
|
||||||
@@ -36,6 +37,7 @@ namespace games::jb {
|
|||||||
// touch state
|
// touch state
|
||||||
JubeatTouchAlgorithm TOUCH_ALGORITHM = Improved;
|
JubeatTouchAlgorithm TOUCH_ALGORITHM = Improved;
|
||||||
JubeatTouchDebugMode TOUCH_DEBUG_OVERLAY = JbTouchDebugAuto;
|
JubeatTouchDebugMode TOUCH_DEBUG_OVERLAY = JbTouchDebugAuto;
|
||||||
|
uint32_t TOUCH_DEBOUNCE_MS = 0;
|
||||||
static std::atomic_bool TOUCH_ENABLE = false;
|
static std::atomic_bool TOUCH_ENABLE = false;
|
||||||
static bool TOUCH_ATTACHED = false;
|
static bool TOUCH_ATTACHED = false;
|
||||||
static bool IS_PORTRAIT = true;
|
static bool IS_PORTRAIT = true;
|
||||||
@@ -52,22 +54,31 @@ namespace games::jb {
|
|||||||
|
|
||||||
static void clear_touch_points() {
|
static void clear_touch_points() {
|
||||||
for (auto &point : TOUCH_POINTS) {
|
for (auto &point : TOUCH_POINTS) {
|
||||||
point.store(JB_INVALID_TOUCH_POINT, std::memory_order_relaxed);
|
point.store(JB_INVALID_TOUCH_POINT, std::memory_order_release);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void publish_touch_points(const std::vector<TouchPoint> &touch_points) {
|
// false when a contact is younger than the debounce window and should be ignored
|
||||||
|
static bool touch_matured(const TouchPoint &tp, double now_ms, double threshold_ms) {
|
||||||
|
return now_ms - tp.down_ms >= threshold_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
// snapshot for the debug overlay: each slot atomically holds one matured contact or
|
||||||
|
// the invalid sentinel. immature and absent contacts are published as invalid, so the
|
||||||
|
// overlay never shows a suppressed tap and a single atomic per slot cannot tear
|
||||||
|
static void publish_touch_points(const std::vector<TouchPoint> &touch_points,
|
||||||
|
double now_ms, double threshold_ms) {
|
||||||
size_t count = touch_points.size();
|
size_t count = touch_points.size();
|
||||||
if (count > JB_MAX_TOUCH_POINTS) {
|
if (count > JB_MAX_TOUCH_POINTS) {
|
||||||
count = JB_MAX_TOUCH_POINTS;
|
count = JB_MAX_TOUCH_POINTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = count; i < JB_MAX_TOUCH_POINTS; i++) {
|
for (size_t i = 0; i < JB_MAX_TOUCH_POINTS; i++) {
|
||||||
TOUCH_POINTS[i].store(JB_INVALID_TOUCH_POINT, std::memory_order_relaxed);
|
POINT point = JB_INVALID_TOUCH_POINT;
|
||||||
}
|
if (i < count && touch_matured(touch_points[i], now_ms, threshold_ms)) {
|
||||||
for (size_t i = 0; i < count; i++) {
|
point = { touch_points[i].x, touch_points[i].y };
|
||||||
POINT point { touch_points[i].x, touch_points[i].y };
|
}
|
||||||
TOUCH_POINTS[i].store(point, std::memory_order_relaxed);
|
TOUCH_POINTS[i].store(point, std::memory_order_release);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +218,19 @@ namespace games::jb {
|
|||||||
uint16_t next_state = 0;
|
uint16_t next_state = 0;
|
||||||
std::vector<TouchPoint> touch_points;
|
std::vector<TouchPoint> touch_points;
|
||||||
touch_get_points(touch_points);
|
touch_get_points(touch_points);
|
||||||
publish_touch_points(touch_points);
|
|
||||||
|
// debounce: the landing time is stamped by the touch layer, so this is
|
||||||
|
// independent of how often the game polls for input (0 = off)
|
||||||
|
double now_ms = get_performance_milliseconds();
|
||||||
|
double threshold_ms = TOUCH_DEBOUNCE_MS;
|
||||||
|
|
||||||
|
// publish every raw contact (with its maturity) for the debug overlay, then drop
|
||||||
|
// the ones still inside the debounce window so no algorithm sees them
|
||||||
|
publish_touch_points(touch_points, now_ms, threshold_ms);
|
||||||
|
std::erase_if(touch_points, [&](const TouchPoint &tp) {
|
||||||
|
return !touch_matured(tp, now_ms, threshold_ms);
|
||||||
|
});
|
||||||
|
|
||||||
if (TOUCH_ALGORITHM == Legacy) {
|
if (TOUCH_ALGORITHM == Legacy) {
|
||||||
|
|
||||||
// legacy: evenly divide the play area into a 4x4 grid
|
// legacy: evenly divide the play area into a 4x4 grid
|
||||||
@@ -355,18 +378,21 @@ namespace games::jb {
|
|||||||
int radius = touch_radius();
|
int radius = touch_radius();
|
||||||
int draw_radius = radius > 0 ? radius : JB_TOUCH_RADIUS;
|
int draw_radius = radius > 0 ? radius : JB_TOUCH_RADIUS;
|
||||||
|
|
||||||
|
// each slot holds a matured contact or the invalid sentinel; immature and absent
|
||||||
|
// contacts were already filtered out by publish_touch_points
|
||||||
for (auto &touch_point : TOUCH_POINTS) {
|
for (auto &touch_point : TOUCH_POINTS) {
|
||||||
POINT point = touch_point.load(std::memory_order_relaxed);
|
POINT point = touch_point.load(std::memory_order_acquire);
|
||||||
if (point.x == JB_INVALID_TOUCH_COORD) {
|
if (point.x == JB_INVALID_TOUCH_COORD) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pressed = touch_presses_button(point.x, point.y, gx, gy, legacy, radius);
|
bool pressed = touch_presses_button(point.x, point.y, gx, gy, legacy, radius);
|
||||||
|
|
||||||
SelectObject(hdc, pressed ? pen_white : pen_gray);
|
SelectObject(hdc, pressed ? pen_white : pen_gray);
|
||||||
Ellipse(hdc, point.x - draw_radius, point.y - draw_radius,
|
Ellipse(hdc, point.x - draw_radius, point.y - draw_radius,
|
||||||
point.x + draw_radius, point.y + draw_radius);
|
point.x + draw_radius, point.y + draw_radius);
|
||||||
|
|
||||||
// the arc would point at a single snapped-to button; plus can trigger several at
|
// the arc points at a single snapped-to button; plus can trigger several at
|
||||||
// once, so skip it there
|
// once, so skip it there
|
||||||
if (pressed || TOUCH_ALGORITHM == Plus) {
|
if (pressed || TOUCH_ALGORITHM == Plus) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ namespace games::jb {
|
|||||||
extern JubeatTouchAlgorithm TOUCH_ALGORITHM;
|
extern JubeatTouchAlgorithm TOUCH_ALGORITHM;
|
||||||
extern JubeatTouchDebugMode TOUCH_DEBUG_OVERLAY;
|
extern JubeatTouchDebugMode TOUCH_DEBUG_OVERLAY;
|
||||||
|
|
||||||
|
// minimum contact age in milliseconds before a touch registers (0 = off);
|
||||||
|
// filters momentary phantom touches from noisy panels
|
||||||
|
extern uint32_t TOUCH_DEBOUNCE_MS;
|
||||||
|
|
||||||
// atomically published panel state, read by the I/O layer
|
// atomically published panel state, read by the I/O layer
|
||||||
std::bitset<16> touch_state();
|
std::bitset<16> touch_state();
|
||||||
|
|
||||||
|
|||||||
@@ -1333,6 +1333,11 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options[launcher::Options::JubeatTouchDebounce].is_active()) {
|
||||||
|
games::jb::TOUCH_DEBOUNCE_MS =
|
||||||
|
options[launcher::Options::JubeatTouchDebounce].value_uint32();
|
||||||
|
}
|
||||||
|
|
||||||
// reflec beat touch emulation
|
// reflec beat touch emulation
|
||||||
if (options[launcher::Options::spice2x_RBTouchScale].is_active()) {
|
if (options[launcher::Options::spice2x_RBTouchScale].is_active()) {
|
||||||
games::rb::TOUCH_SCALING = options[launcher::Options::spice2x_RBTouchScale].value_uint32();
|
games::rb::TOUCH_SCALING = options[launcher::Options::spice2x_RBTouchScale].value_uint32();
|
||||||
|
|||||||
@@ -2725,6 +2725,20 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
},
|
},
|
||||||
.quick_setting_category = "Game",
|
.quick_setting_category = "Game",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// JubeatTouchDebounce
|
||||||
|
.title = "JB Touch Debounce",
|
||||||
|
.name = "jubeattouchdebounce",
|
||||||
|
.desc = "For touch screen players: ignore extremely quick touches by requiring a "
|
||||||
|
"touch to be held for at least this many milliseconds before it registers.\n\n"
|
||||||
|
"Useful for filtering phantom touches on a noisy touch screen. A higher value "
|
||||||
|
"adds input latency, so keep it small. Default: off (0).",
|
||||||
|
.type = OptionType::Integer,
|
||||||
|
.setting_name = "8",
|
||||||
|
.game_name = "Jubeat",
|
||||||
|
.category = "Game Options",
|
||||||
|
.quick_setting_category = "Game",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// spice2x_RBTouchScale
|
// spice2x_RBTouchScale
|
||||||
.title = "RB Touch Emulation Scale",
|
.title = "RB Touch Emulation Scale",
|
||||||
|
|||||||
@@ -264,6 +264,7 @@ namespace launcher {
|
|||||||
spice2x_JubeatLegacyTouch,
|
spice2x_JubeatLegacyTouch,
|
||||||
JubeatTouchAlgo,
|
JubeatTouchAlgo,
|
||||||
JubeatTouchDebug,
|
JubeatTouchDebug,
|
||||||
|
JubeatTouchDebounce,
|
||||||
spice2x_RBTouchScale,
|
spice2x_RBTouchScale,
|
||||||
RBTouchSize,
|
RBTouchSize,
|
||||||
RBTouchPollRate,
|
RBTouchPollRate,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "util/libutils.h"
|
#include "util/libutils.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/time.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
#include "handler.h"
|
#include "handler.h"
|
||||||
@@ -538,6 +539,7 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
|
|||||||
.x = GET_X_LPARAM(lParam),
|
.x = GET_X_LPARAM(lParam),
|
||||||
.y = GET_Y_LPARAM(lParam),
|
.y = GET_Y_LPARAM(lParam),
|
||||||
.mouse = true,
|
.mouse = true,
|
||||||
|
.down_ms = get_performance_milliseconds(),
|
||||||
};
|
};
|
||||||
TOUCH_POINTS.push_back(tp);
|
TOUCH_POINTS.push_back(tp);
|
||||||
|
|
||||||
@@ -851,6 +853,9 @@ void touch_write_points(std::vector<TouchPoint> *touch_points) {
|
|||||||
// create new touch point when not found
|
// create new touch point when not found
|
||||||
if (!found) {
|
if (!found) {
|
||||||
|
|
||||||
|
// stamp the landing time so debounce can measure the contact's age
|
||||||
|
tp.down_ms = get_performance_milliseconds();
|
||||||
|
|
||||||
// add touch point
|
// add touch point
|
||||||
TOUCH_POINTS.push_back(tp);
|
TOUCH_POINTS.push_back(tp);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ struct TouchPoint {
|
|||||||
DWORD id;
|
DWORD id;
|
||||||
LONG x, y;
|
LONG x, y;
|
||||||
bool mouse;
|
bool mouse;
|
||||||
|
double down_ms = 0.0; // performance-counter milliseconds when the contact first landed
|
||||||
};
|
};
|
||||||
enum TouchEventType {
|
enum TouchEventType {
|
||||||
TOUCH_DOWN,
|
TOUCH_DOWN,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
#include "util/libutils.h"
|
#include "util/libutils.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/time.h"
|
||||||
#include "rawinput/touch.h"
|
#include "rawinput/touch.h"
|
||||||
|
|
||||||
// mingw issue #2205 workaround
|
// mingw issue #2205 workaround
|
||||||
@@ -177,6 +178,7 @@ void Win7Handler::handle_message(msg_handler_result &result, HWND hWnd, UINT msg
|
|||||||
.x = point.x,
|
.x = point.x,
|
||||||
.y = point.y,
|
.y = point.y,
|
||||||
.mouse = false,
|
.mouse = false,
|
||||||
|
.down_ms = get_performance_milliseconds(),
|
||||||
};
|
};
|
||||||
TOUCH_POINTS.push_back(tp);
|
TOUCH_POINTS.push_back(tp);
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "util/libutils.h"
|
#include "util/libutils.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/time.h"
|
||||||
#include "rawinput/touch.h"
|
#include "rawinput/touch.h"
|
||||||
|
|
||||||
// mingw does not seem to have this either
|
// mingw does not seem to have this either
|
||||||
@@ -220,6 +221,7 @@ void Win8Handler::handle_message(msg_handler_result &result, HWND hWnd, UINT msg
|
|||||||
.x = point.x,
|
.x = point.x,
|
||||||
.y = point.y,
|
.y = point.y,
|
||||||
.mouse = false,
|
.mouse = false,
|
||||||
|
.down_ms = get_performance_milliseconds(),
|
||||||
};
|
};
|
||||||
TOUCH_POINTS.push_back(tp);
|
TOUCH_POINTS.push_back(tp);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user