mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 14:20: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 "rawinput/touch.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
// touch layout: a 4x4 grid of 160px buttons separated by 37 / 38 / 37 px gaps
|
||||
@@ -36,6 +37,7 @@ namespace games::jb {
|
||||
// touch state
|
||||
JubeatTouchAlgorithm TOUCH_ALGORITHM = Improved;
|
||||
JubeatTouchDebugMode TOUCH_DEBUG_OVERLAY = JbTouchDebugAuto;
|
||||
uint32_t TOUCH_DEBOUNCE_MS = 0;
|
||||
static std::atomic_bool TOUCH_ENABLE = false;
|
||||
static bool TOUCH_ATTACHED = false;
|
||||
static bool IS_PORTRAIT = true;
|
||||
@@ -52,22 +54,31 @@ namespace games::jb {
|
||||
|
||||
static void clear_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();
|
||||
if (count > JB_MAX_TOUCH_POINTS) {
|
||||
count = JB_MAX_TOUCH_POINTS;
|
||||
}
|
||||
|
||||
for (size_t i = count; i < JB_MAX_TOUCH_POINTS; i++) {
|
||||
TOUCH_POINTS[i].store(JB_INVALID_TOUCH_POINT, std::memory_order_relaxed);
|
||||
}
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
POINT point { touch_points[i].x, touch_points[i].y };
|
||||
TOUCH_POINTS[i].store(point, std::memory_order_relaxed);
|
||||
for (size_t i = 0; i < JB_MAX_TOUCH_POINTS; i++) {
|
||||
POINT point = JB_INVALID_TOUCH_POINT;
|
||||
if (i < count && touch_matured(touch_points[i], now_ms, threshold_ms)) {
|
||||
point = { touch_points[i].x, touch_points[i].y };
|
||||
}
|
||||
TOUCH_POINTS[i].store(point, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +218,19 @@ namespace games::jb {
|
||||
uint16_t next_state = 0;
|
||||
std::vector<TouchPoint> 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) {
|
||||
|
||||
// legacy: evenly divide the play area into a 4x4 grid
|
||||
@@ -355,18 +378,21 @@ namespace games::jb {
|
||||
int radius = 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) {
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool pressed = touch_presses_button(point.x, point.y, gx, gy, legacy, radius);
|
||||
|
||||
SelectObject(hdc, pressed ? pen_white : pen_gray);
|
||||
Ellipse(hdc, 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
|
||||
if (pressed || TOUCH_ALGORITHM == Plus) {
|
||||
continue;
|
||||
|
||||
@@ -26,6 +26,10 @@ namespace games::jb {
|
||||
extern JubeatTouchAlgorithm TOUCH_ALGORITHM;
|
||||
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
|
||||
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
|
||||
if (options[launcher::Options::spice2x_RBTouchScale].is_active()) {
|
||||
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",
|
||||
},
|
||||
{
|
||||
// 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
|
||||
.title = "RB Touch Emulation Scale",
|
||||
|
||||
@@ -264,6 +264,7 @@ namespace launcher {
|
||||
spice2x_JubeatLegacyTouch,
|
||||
JubeatTouchAlgo,
|
||||
JubeatTouchDebug,
|
||||
JubeatTouchDebounce,
|
||||
spice2x_RBTouchScale,
|
||||
RBTouchSize,
|
||||
RBTouchPollRate,
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "handler.h"
|
||||
@@ -538,6 +539,7 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
|
||||
.x = GET_X_LPARAM(lParam),
|
||||
.y = GET_Y_LPARAM(lParam),
|
||||
.mouse = true,
|
||||
.down_ms = get_performance_milliseconds(),
|
||||
};
|
||||
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
|
||||
if (!found) {
|
||||
|
||||
// stamp the landing time so debounce can measure the contact's age
|
||||
tp.down_ms = get_performance_milliseconds();
|
||||
|
||||
// add touch point
|
||||
TOUCH_POINTS.push_back(tp);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ struct TouchPoint {
|
||||
DWORD id;
|
||||
LONG x, y;
|
||||
bool mouse;
|
||||
double down_ms = 0.0; // performance-counter milliseconds when the contact first landed
|
||||
};
|
||||
enum TouchEventType {
|
||||
TOUCH_DOWN,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/time.h"
|
||||
#include "rawinput/touch.h"
|
||||
|
||||
// mingw issue #2205 workaround
|
||||
@@ -177,6 +178,7 @@ void Win7Handler::handle_message(msg_handler_result &result, HWND hWnd, UINT msg
|
||||
.x = point.x,
|
||||
.y = point.y,
|
||||
.mouse = false,
|
||||
.down_ms = get_performance_milliseconds(),
|
||||
};
|
||||
TOUCH_POINTS.push_back(tp);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/time.h"
|
||||
#include "rawinput/touch.h"
|
||||
|
||||
// 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,
|
||||
.y = point.y,
|
||||
.mouse = false,
|
||||
.down_ms = get_performance_milliseconds(),
|
||||
};
|
||||
TOUCH_POINTS.push_back(tp);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user