Map analogue triggers reliably on all platforms
Screwtapello opened this issue · 2 comments
According to the USB HID spec, inputs are divided into digital buttons and analogue axes. Axes default to 0, but can vary from -32768 to 32767. Most controllers use buttons for, well, buttons and axes for analogue sticks, but modern controllers (the Xbox 360 pad is what I'm looking at, but Wikipedia tells me the Xbox One pad and the DualShock 3 and above also) include analogue triggers that default to -32767.
In practice this isn't a problem; systems higan emulates support at best left and right bumpers, not triggers, and so nobody has any business mapping triggers to anything. But sometimes somebody does map something to the triggers (see #108) and Weird Things happen.
#109 should fix this specifically for Xbox pads on Windows, but the same issues would occur with PlayStation pads on Windows, or any pads on macOS, Linux, or FreeBSD.
Interestingly in my testing, pulling the analogue trigger quickly caused higan-ui to (correctly) map the "Hi" end of the axis (so the mapped button is pressed when the trigger is pulled), while pulling the trigger slowly caused higan-ui to map the "Lo" end of the axis (so the mapped button is pressed when the trigger is released, i.e. al the time). The byuu-ui always maps the "Lo" end of the axis no matter how fast I pull it.
I'm not sure how triggers can reliably be distinguished from joystick axes — maybe the only solution is to match the controller name and have a database of "controller name X has axes Y and Z and triggers". Maybe the binding code can do something smart with measuring the rest state of analogue axes to see if they hover around 0 or -32768.
I looked into the other drivers a bit.
The uhid driver specifically detects the Xbox 360 controller and handles the triggers correctly, but that won't work for other controller types. The directinput, iokit, udev and sdl drivers only understand axes.
It seems the USB device specification doesn't support triggers (on-off analog states), only buttons (on-off digital states) and axes (low, off, high analog ranges), so there's no generic way to support them. If these controller makers reported triggers as 0 idle to +32767 pressed this would be a non-issue and the triggers would just work. It's because some try to use the full -32768 to +32767 range that problems arise.
Worse still, the sdl driver provides no way to get the controller vendor and device ID to use a database. Further, some controllers will report analog axes and triggers as 0 upon initialization, and only report the real value after moving the respective axes or triggers.
Ideas that come to mind:
-
if joypads are connected, display a calibration screen upon mapping the first input, request the user to move all the axes and triggers, then let everything sit still and hit OK. Values that report close to 0 after calibration are axes, values that report something close to -32768 or +32767 are triggers.
-
provide GUI buttons to override incorrect guesses. Allow a user to flip a Hi/Lo state by clicking a button. Display some sort of informative popup about it the first time an analog input is assigned.
-
create a VID/PID database to auto-detect triggers on the most popular gamepads and don't worry about the sdl driver. It looks like no platform needs the sdl driver anyway.
Another issue with mapping triggers: in higan's current input processing code, a digital button can only be mapped to an analogue input if that input is in the "axis" or "hat" groups:
higan/higan-ui/input/input.cpp
Line 14 in 44cbb88
...but in the xinput:
higan/ruby/input/joypad/xinput.cpp
Lines 135 to 136 in 44cbb88
...and uhid drivers:
higan/ruby/input/joypad/uhid.cpp
Lines 152 to 153 in 44cbb88
...the triggers are part of a "triggers" group.
The equivalent code in ares v120r17's input processing code looks like this:
if(device->isJoypad() && groupID != HID::Joypad::GroupID::Button) {
...so we should probably make that change too.