|
| 1 | +/* |
| 2 | + * Project Teensy XInput Library |
| 3 | + * @author David Madison |
| 4 | + * @link github.com/dmadison/TeensyXInput |
| 5 | + * @license MIT - Copyright (c) 2018 David Madison |
| 6 | + * |
| 7 | + * Permission is hereby granted, free of charge, to any person obtaining a copy |
| 8 | + * of this software and associated documentation files (the "Software"), to deal |
| 9 | + * in the Software without restriction, including without limitation the rights |
| 10 | + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 11 | + * copies of the Software, and to permit persons to whom the Software is |
| 12 | + * furnished to do so, subject to the following conditions: |
| 13 | + * |
| 14 | + * The above copyright notice and this permission notice shall be included in |
| 15 | + * all copies or substantial portions of the Software. |
| 16 | + * |
| 17 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 18 | + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 19 | + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 20 | + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 21 | + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 22 | + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 23 | + * THE SOFTWARE. |
| 24 | + * |
| 25 | + */ |
| 26 | + |
| 27 | +#include "XInput.h" |
| 28 | + |
| 29 | +// -------------------------------------------------------- |
| 30 | +// XInput Button Maps | |
| 31 | +// (Matches ID to tx index with bitmask) | |
| 32 | +// -------------------------------------------------------- |
| 33 | + |
| 34 | +struct XInputMap_Button { |
| 35 | + constexpr XInputMap_Button(uint8_t i, uint8_t o) |
| 36 | + : index(i), mask(BuildMask(o)) {} |
| 37 | + const uint8_t index; |
| 38 | + const uint8_t mask; |
| 39 | + |
| 40 | +private: |
| 41 | + constexpr static uint8_t BuildMask(uint8_t offset) { |
| 42 | + return (1 << offset); // Bitmask of bit to flip |
| 43 | + } |
| 44 | +}; |
| 45 | + |
| 46 | +static const XInputMap_Button Map_DpadUp(2, 0); |
| 47 | +static const XInputMap_Button Map_DpadDown(2, 1); |
| 48 | +static const XInputMap_Button Map_DpadLeft(2, 2); |
| 49 | +static const XInputMap_Button Map_DpadRight(2, 3); |
| 50 | +static const XInputMap_Button Map_ButtonStart(2, 4); |
| 51 | +static const XInputMap_Button Map_ButtonBack(2, 5); |
| 52 | +static const XInputMap_Button Map_ButtonL3(2, 6); |
| 53 | +static const XInputMap_Button Map_ButtonR3(2, 7); |
| 54 | + |
| 55 | +static const XInputMap_Button Map_ButtonLB(3, 0); |
| 56 | +static const XInputMap_Button Map_ButtonRB(3, 1); |
| 57 | +static const XInputMap_Button Map_ButtonLogo(3, 3); |
| 58 | +static const XInputMap_Button Map_ButtonA(3, 4); |
| 59 | +static const XInputMap_Button Map_ButtonB(3, 5); |
| 60 | +static const XInputMap_Button Map_ButtonX(3, 6); |
| 61 | +static const XInputMap_Button Map_ButtonY(3, 7); |
| 62 | + |
| 63 | +constexpr const XInputMap_Button * getButtonFromEnum(XInputControl ctrl) { |
| 64 | + switch (ctrl) { |
| 65 | + case(DPAD_UP): return &Map_DpadUp; |
| 66 | + case(DPAD_DOWN): return &Map_DpadDown; |
| 67 | + case(DPAD_LEFT): return &Map_DpadLeft; |
| 68 | + case(DPAD_RIGHT): return &Map_DpadRight; |
| 69 | + case(BUTTON_A): return &Map_ButtonA; |
| 70 | + case(BUTTON_B): return &Map_ButtonB; |
| 71 | + case(BUTTON_X): return &Map_ButtonX; |
| 72 | + case(BUTTON_Y): return &Map_ButtonY; |
| 73 | + case(BUTTON_LB): return &Map_ButtonLB; |
| 74 | + case(BUTTON_RB): return &Map_ButtonRB; |
| 75 | + case(BUTTON_L3): return &Map_ButtonL3; |
| 76 | + case(BUTTON_R3): return &Map_ButtonR3; |
| 77 | + case(BUTTON_START): return &Map_ButtonStart; |
| 78 | + case(BUTTON_BACK): return &Map_ButtonBack; |
| 79 | + case(BUTTON_LOGO): return &Map_ButtonLogo; |
| 80 | + default: return nullptr; |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +// -------------------------------------------------------- |
| 85 | +// XInput Trigger Maps | |
| 86 | +// (Matches ID to tx index) | |
| 87 | +// -------------------------------------------------------- |
| 88 | + |
| 89 | +struct XInputMap_Trigger { |
| 90 | + constexpr XInputMap_Trigger(uint8_t i) |
| 91 | + : index(i) {} |
| 92 | + const uint8_t index; |
| 93 | +}; |
| 94 | + |
| 95 | +static const XInputMap_Trigger Map_TriggerLeft(4); |
| 96 | +static const XInputMap_Trigger Map_TriggerRight(5); |
| 97 | + |
| 98 | +constexpr const XInputMap_Trigger * getTriggerFromEnum(XInputControl ctrl) { |
| 99 | + switch (ctrl) { |
| 100 | + case(TRIGGER_LEFT): return &Map_TriggerLeft; |
| 101 | + case(TRIGGER_RIGHT): return &Map_TriggerRight; |
| 102 | + default: return nullptr; |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +// -------------------------------------------------------- |
| 107 | +// XInput Joystick Maps | |
| 108 | +// (Matches ID to tx x/y high/low indices) | |
| 109 | +// -------------------------------------------------------- |
| 110 | + |
| 111 | +struct XInputMap_Joystick { |
| 112 | + constexpr XInputMap_Joystick(uint8_t xl, uint8_t xh, uint8_t yl, uint8_t yh) |
| 113 | + : x_low(xl), x_high(xh), y_low(yl), y_high(yh) {} |
| 114 | + const uint8_t x_low; |
| 115 | + const uint8_t x_high; |
| 116 | + const uint8_t y_low; |
| 117 | + const uint8_t y_high; |
| 118 | +}; |
| 119 | + |
| 120 | +static const XInputMap_Joystick Map_JoystickLeft(6, 7, 8, 9); |
| 121 | +static const XInputMap_Joystick Map_JoystickRight(10, 11, 12, 13); |
| 122 | + |
| 123 | +constexpr const XInputMap_Joystick * getJoyFromEnum(XInputControl ctrl) { |
| 124 | + switch (ctrl) { |
| 125 | + case(JOY_LEFT): return &Map_JoystickLeft; |
| 126 | + case(JOY_RIGHT): return &Map_JoystickRight; |
| 127 | + default: return nullptr; |
| 128 | + } |
| 129 | +} |
| 130 | + |
| 131 | +// -------------------------------------------------------- |
| 132 | +// XInput Rumble Maps | |
| 133 | +// (Stores rx index and buffer index for each motor) | |
| 134 | +// -------------------------------------------------------- |
| 135 | + |
| 136 | +struct XInputMap_Rumble { |
| 137 | + constexpr XInputMap_Rumble(uint8_t rIndex, uint8_t bIndex) |
| 138 | + : rxIndex(rIndex), bufferIndex(bIndex) {} |
| 139 | + const uint8_t rxIndex; |
| 140 | + const uint8_t bufferIndex; |
| 141 | +}; |
| 142 | + |
| 143 | +static const XInputMap_Rumble RumbleLeft(3, 0); // Large motor |
| 144 | +static const XInputMap_Rumble RumbleRight(4, 1); // Small motor |
| 145 | + |
| 146 | + |
| 147 | +// -------------------------------------------------------- |
| 148 | +// XInputGamepad Class (API) | |
| 149 | +// -------------------------------------------------------- |
| 150 | + |
| 151 | +XInputGamepad::XInputGamepad() : |
| 152 | + tx(), rumble() // Zero initialize arrays |
| 153 | +{ |
| 154 | + tx[0] = 0x00; // Message type |
| 155 | + tx[1] = 0x14; // Packet size |
| 156 | +} |
| 157 | + |
| 158 | +void XInputGamepad::press(XInputControl button) { |
| 159 | + setButton(button, true); |
| 160 | +} |
| 161 | + |
| 162 | +void XInputGamepad::release(XInputControl button) { |
| 163 | + setButton(button, false); |
| 164 | +} |
| 165 | + |
| 166 | +void XInputGamepad::setButton(XInputControl button, boolean state) { |
| 167 | + const XInputMap_Button * buttonData = getButtonFromEnum(button); |
| 168 | + if (buttonData != nullptr) { |
| 169 | + if (state) { tx[buttonData->index] |= buttonData->mask; } // Press |
| 170 | + else { tx[buttonData->index] &= ~(buttonData->mask); } // Release |
| 171 | + } |
| 172 | + else { |
| 173 | + setTrigger(button, state ? 255 : 0); // Treat trigger like a button |
| 174 | + } |
| 175 | +} |
| 176 | + |
| 177 | +// To-do: add SOCD cleaner |
| 178 | +void XInputGamepad::setDpad(XInputControl pad, boolean state) { |
| 179 | + setButton(pad, state); |
| 180 | +} |
| 181 | + |
| 182 | + |
| 183 | +void XInputGamepad::setDpad(boolean up, boolean down, boolean left, boolean right) { |
| 184 | + setDpad(DPAD_UP, up); |
| 185 | + setDpad(DPAD_DOWN, down); |
| 186 | + setDpad(DPAD_LEFT, left); |
| 187 | + setDpad(DPAD_RIGHT, right); |
| 188 | +} |
| 189 | + |
| 190 | +void XInputGamepad::setTrigger(XInputControl trigger, uint8_t val) { |
| 191 | + const XInputMap_Trigger * triggerData = getTriggerFromEnum(trigger); |
| 192 | + if (triggerData == nullptr) return; // Not a trigger |
| 193 | + tx[triggerData->index] = val; |
| 194 | +} |
| 195 | + |
| 196 | +void XInputGamepad::setJoystick(XInputControl joy, int16_t x, int16_t y) { |
| 197 | + const XInputMap_Joystick * joyData = getJoyFromEnum(joy); |
| 198 | + if (joyData == nullptr) return; // Not a joystick |
| 199 | + |
| 200 | + tx[joyData->x_low] = lowByte(x); |
| 201 | + tx[joyData->x_high] = highByte(x); |
| 202 | + |
| 203 | + tx[joyData->y_low] = lowByte(y); |
| 204 | + tx[joyData->y_high] = highByte(y); |
| 205 | +} |
| 206 | + |
| 207 | +uint8_t XInputGamepad::getPlayer() const { |
| 208 | + return player; |
| 209 | +} |
| 210 | + |
| 211 | +uint16_t XInputGamepad::getRumble() const { |
| 212 | + return rumble[RumbleLeft.bufferIndex] << 8 | rumble[RumbleRight.bufferIndex]; |
| 213 | +} |
| 214 | + |
| 215 | +uint8_t XInputGamepad::getRumbleLeft() const { |
| 216 | + return rumble[RumbleLeft.bufferIndex]; |
| 217 | +} |
| 218 | + |
| 219 | +uint8_t XInputGamepad::getRumbleRight() const { |
| 220 | + return rumble[RumbleRight.bufferIndex]; |
| 221 | +} |
| 222 | + |
| 223 | +XInputLEDPattern XInputGamepad::getLEDPattern() const { |
| 224 | + return ledPattern; |
| 225 | +} |
| 226 | + |
| 227 | +uint8_t XInputGamepad::getLEDPatternID() const { |
| 228 | + return (uint8_t)ledPattern; |
| 229 | +} |
| 230 | + |
| 231 | +//Send an update packet to the PC |
| 232 | +void XInputGamepad::send() { |
| 233 | + XInputUSB.send(tx, USB_Timeout); |
| 234 | +} |
| 235 | + |
| 236 | +void XInputGamepad::receive() { |
| 237 | + if (XInputUSB.available() == 0) { |
| 238 | + return; // No packet available |
| 239 | + } |
| 240 | + |
| 241 | + // Grab packet and store it in rx array |
| 242 | + uint8_t rx[8]; |
| 243 | + XInputUSB.recv(rx, USB_Timeout); |
| 244 | + |
| 245 | + // Rumble Packet |
| 246 | + if ((rx[0] == 0x00) & (rx[1] == 0x08)) { |
| 247 | + rumble[RumbleLeft.bufferIndex] = rx[RumbleLeft.rxIndex]; // Big weight (Left grip) |
| 248 | + rumble[RumbleRight.bufferIndex] = rx[RumbleRight.rxIndex]; // Small weight (Right grip) |
| 249 | + } |
| 250 | + // LED Packet |
| 251 | + else if (rx[0] == 0x01) { |
| 252 | + parseLED(rx[2]); |
| 253 | + } |
| 254 | +} |
| 255 | + |
| 256 | +void XInputGamepad::parseLED(uint8_t leds) { |
| 257 | + if (leds > 0x0D) return; // Not a known pattern |
| 258 | + |
| 259 | + ledPattern = (XInputLEDPattern) leds; // Save pattern |
| 260 | + if (ledPattern == XInputLEDPattern::Off || ledPattern == XInputLEDPattern::Blinking) { |
| 261 | + player = 0; // Not connected |
| 262 | + } |
| 263 | + else if (ledPattern == XInputLEDPattern::On1 || ledPattern == XInputLEDPattern::Flash1) { |
| 264 | + player = 1; |
| 265 | + } |
| 266 | + else if (ledPattern == XInputLEDPattern::On2 || ledPattern == XInputLEDPattern::Flash2) { |
| 267 | + player = 2; |
| 268 | + } |
| 269 | + else if (ledPattern == XInputLEDPattern::On3 || ledPattern == XInputLEDPattern::Flash3) { |
| 270 | + player = 3; |
| 271 | + } |
| 272 | + else if (ledPattern == XInputLEDPattern::On4 || ledPattern == XInputLEDPattern::Flash4) { |
| 273 | + player = 4; |
| 274 | + } |
| 275 | + else { |
| 276 | + return; // Pattern doesn't affect player # |
| 277 | + } |
| 278 | +} |
| 279 | + |
| 280 | +XInputGamepad XInput; |
0 commit comments