From af4fd3e5d74041e13141489551e7072213638aac Mon Sep 17 00:00:00 2001 From: Mike Parks Date: Thu, 11 May 2023 13:57:01 -0700 Subject: [PATCH 01/15] Added improved Wii extension detection. Added GHWT touch fret support. Added initial Wii extension hot plugging support. Fixed issue with detecting GHWT guitars. Fixed issue with guitar analog values. --- lib/WiiExtension/WiiExtension.cpp | 169 ++++++++++++++++++------------ lib/WiiExtension/WiiExtension.h | 24 +++++ src/addons/wiiext.cpp | 5 +- 3 files changed, 129 insertions(+), 69 deletions(-) diff --git a/lib/WiiExtension/WiiExtension.cpp b/lib/WiiExtension/WiiExtension.cpp index 16abe32de..3a06ea90f 100644 --- a/lib/WiiExtension/WiiExtension.cpp +++ b/lib/WiiExtension/WiiExtension.cpp @@ -42,6 +42,8 @@ void WiiExtension::start(){ if (result > -1) { doI2CRead(idRead, 6); + if (idRead[2] != 0xA4 || idRead[3] != 0x20) return; + if (idRead[5] == 0x00) { extensionType = WII_EXTENSION_NUNCHUCK; } else if (idRead[5] == 0x01) { @@ -63,8 +65,8 @@ void WiiExtension::start(){ if (dataType == WII_DATA_TYPE_0) dataType = WII_DATA_TYPE_1; #if WII_EXTENSION_DEBUG==true - printf("Extension ID: %02x%02x %02x%02x %02x%02x\n", idRead[0], idRead[1], idRead[2], idRead[3], idRead[4], idRead[5]); - printf("Data Format: %02x\n",idRead[4]); + printf("WiiExtension::Extension ID: %02x%02x %02x%02x %02x%02x\n", idRead[0], idRead[1], idRead[2], idRead[3], idRead[4], idRead[5]); + printf("WiiExtension::Data Format: %02x\n",idRead[4]); #endif if (extensionType != WII_EXTENSION_NONE) { @@ -73,7 +75,7 @@ void WiiExtension::start(){ #endif #if WII_EXTENSION_DEBUG==true - printf("Calibration Data\n"); + printf("WiiExtension::Calibration Data\n"); #endif if (extensionType == WII_EXTENSION_NUNCHUCK) { _analogPrecision1From = WII_ANALOG_PRECISION_2; @@ -251,9 +253,12 @@ void WiiExtension::start(){ drumRight = 0; whammyBar = 0; + touchBar = 0; + + _guitarType = WII_GUITAR_UNSET; } else { #if WII_EXTENSION_DEBUG==true - printf("Unknown Extension: %02x%02x %02x%02x %02x%02x\n", idRead[0], idRead[1], idRead[2], idRead[3], idRead[4], idRead[5]); + printf("WiiExtension::Unknown Extension: %02x%02x %02x%02x %02x%02x\n", idRead[0], idRead[1], idRead[2], idRead[3], idRead[4], idRead[5]); #endif } @@ -457,70 +462,100 @@ void WiiExtension::poll() { break; case WII_EXTENSION_GUITAR: - if (dataType == WII_DATA_TYPE_1) { - joy1X = (regRead[0] & 0x3F); - joy1Y = (regRead[1] & 0x3F); - - whammyBar = (regRead[3] & 0x1F); - joy2X = (regRead[3] & 0x1F); - - directionDown = !((regRead[4] & 0x40) >> 6); - buttonMinus = !((regRead[4] & 0x10) >> 4); - buttonPlus = !((regRead[4] & 0x04) >> 2); - - fretOrange = !((regRead[5] & 0x80) >> 7); - fretRed = !((regRead[5] & 0x40) >> 6); - fretBlue = !((regRead[5] & 0x20) >> 5); - fretGreen = !((regRead[5] & 0x10) >> 4); - fretYellow = !((regRead[5] & 0x08) >> 3); - pedalButton = !((regRead[5] & 0x04) >> 2); - directionUp = !((regRead[5] & 0x01) >> 0); - } else if (dataType == WII_DATA_TYPE_2) { - joy1X = ((regRead[0] << 2) | ((regRead[4] & 0x03) >> 0)); - joy1Y = ((regRead[2] << 2) | ((regRead[4] & 0x30) >> 4)); - - // analog 2 is not used but appears to not change? - //joy2X = ((regRead[1] << 2) | ((regRead[4] & 0x0C) >> 2)); - //joy2Y = ((regRead[3] << 2) | ((regRead[4] & 0xC0) >> 6)); - - whammyBar = (regRead[6] & 0xFF); - joy2X = (regRead[6] & 0xFF); - - directionDown = !((regRead[7] & 0x40) >> 6); - buttonMinus = !((regRead[7] & 0x10) >> 4); - buttonPlus = !((regRead[7] & 0x04) >> 2); - - fretOrange = !((regRead[8] & 0x80) >> 7); - fretRed = !((regRead[8] & 0x40) >> 6); - fretBlue = !((regRead[8] & 0x20) >> 5); - fretGreen = !((regRead[8] & 0x10) >> 4); - fretYellow = !((regRead[8] & 0x08) >> 3); - pedalButton = !((regRead[8] & 0x04) >> 2); - directionUp = !((regRead[8] & 0x01) >> 0); - } else if (dataType == WII_DATA_TYPE_3) { - joy1X = (regRead[0] & 0xFF); - joy1Y = (regRead[2] & 0xFF); - - // analog 2 is not used but appears to not change? - //joy2X = regRead[1]; - //joy2Y = regRead[3]; - - whammyBar = (regRead[5] & 0xFF); - joy2X = (regRead[5] & 0xFF); - - directionDown = !((regRead[6] & 0x40) >> 6); - buttonMinus = !((regRead[6] & 0x10) >> 4); - buttonPlus = !((regRead[6] & 0x04) >> 2); - - fretOrange = !((regRead[7] & 0x80) >> 7); - fretRed = !((regRead[7] & 0x40) >> 6); - fretBlue = !((regRead[7] & 0x20) >> 5); - fretGreen = !((regRead[7] & 0x10) >> 4); - fretYellow = !((regRead[7] & 0x08) >> 3); - pedalButton = !((regRead[7] & 0x04) >> 2); - directionUp = !((regRead[7] & 0x01) >> 0); + // on first read, check the status of the guitar flag + if (_guitarType == WII_GUITAR_UNSET) { + if (((regRead[0] & 0x80) >> 7) == 0) { + _guitarType = WII_GUITAR_GHWT; + } else { + _guitarType = WII_GUITAR_GH3; + } + // force the data type to 1 when a World Tour guitar is detected + if ((_guitarType == WII_GUITAR_GHWT) && (dataType != WII_DATA_TYPE_1)) { + dataType = WII_DATA_TYPE_1; + _analogPrecision1From = WII_ANALOG_PRECISION_1; + _analogPrecision1To = WII_ANALOG_PRECISION_3; + _analogPrecision2From = WII_ANALOG_PRECISION_0; + _analogPrecision2To = WII_ANALOG_PRECISION_3; + } + } + if (_guitarType != WII_GUITAR_UNSET) { + // as defined works for GH3 guitar + if (dataType == WII_DATA_TYPE_1) { + joy1X = (regRead[0] & 0x3F); + joy1Y = (regRead[1] & 0x3F); + + touchBar = ((_guitarType == WII_GUITAR_GHWT) ? (regRead[2] & 0x1F) : 0); + + whammyBar = (regRead[3] & 0x1F); + joy2X = (regRead[3] & 0x1F); + + directionDown = !((regRead[4] & 0x40) >> 6); + buttonMinus = !((regRead[4] & 0x10) >> 4); + buttonPlus = !((regRead[4] & 0x04) >> 2); + + fretOrange = !((regRead[5] & 0x80) >> 7); + fretRed = !((regRead[5] & 0x40) >> 6); + fretBlue = !((regRead[5] & 0x20) >> 5); + fretGreen = !((regRead[5] & 0x10) >> 4); + fretYellow = !((regRead[5] & 0x08) >> 3); + pedalButton = !((regRead[5] & 0x04) >> 2); + directionUp = !((regRead[5] & 0x01) >> 0); + + isTouched = (touchBar != WII_GUITAR_TOUCHPAD_NONE); + + // process the touch bar for button states + // touch only seems to exist in GHWT, and GHWT always reports data type 1 format regardless of setting + if (isTouched) { + // touched + fretGreen = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_GREEN,WII_GUITAR_TOUCHPAD_RED)); + fretRed = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_RED,WII_GUITAR_TOUCHPAD_YELLOW)); + fretYellow = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_YELLOW,WII_GUITAR_TOUCHPAD_BLUE)); + fretBlue = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_BLUE,WII_GUITAR_TOUCHPAD_ORANGE)); + fretOrange = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_ORANGE,WII_GUITAR_TOUCHPAD_MAX)); + directionDown = isTouched; + } + } else if (dataType == WII_DATA_TYPE_2) { + joy1X = ((regRead[0] << 2) | ((regRead[4] & 0x03) >> 0)); + joy1Y = ((regRead[2] << 2) | ((regRead[4] & 0x30) >> 4)); + + touchBar = 0; + + whammyBar = (regRead[6] & 0xFF); + joy2X = (regRead[6] & 0xFF); + + directionDown = !((regRead[7] & 0x40) >> 6); + buttonMinus = !((regRead[7] & 0x10) >> 4); + buttonPlus = !((regRead[7] & 0x04) >> 2); + + fretOrange = !((regRead[8] & 0x80) >> 7); + fretRed = !((regRead[8] & 0x40) >> 6); + fretBlue = !((regRead[8] & 0x20) >> 5); + fretGreen = !((regRead[8] & 0x10) >> 4); + fretYellow = !((regRead[8] & 0x08) >> 3); + pedalButton = !((regRead[8] & 0x04) >> 2); + directionUp = !((regRead[8] & 0x01) >> 0); + } else if (dataType == WII_DATA_TYPE_3) { + joy1X = (regRead[0] & 0xFF); + joy1Y = (regRead[2] & 0xFF); + + touchBar = 0; + + whammyBar = (regRead[5] & 0xFF); + joy2X = (regRead[5] & 0xFF); + + directionDown = !((regRead[6] & 0x40) >> 6); + buttonMinus = !((regRead[6] & 0x10) >> 4); + buttonPlus = !((regRead[6] & 0x04) >> 2); + + fretOrange = !((regRead[7] & 0x80) >> 7); + fretRed = !((regRead[7] & 0x40) >> 6); + fretBlue = !((regRead[7] & 0x20) >> 5); + fretGreen = !((regRead[7] & 0x10) >> 4); + fretYellow = !((regRead[7] & 0x08) >> 3); + pedalButton = !((regRead[7] & 0x04) >> 2); + directionUp = !((regRead[7] & 0x01) >> 0); + } } - #if WII_EXTENSION_DEBUG==true // printf("Joy1 X=%4d Y=%4d Whammy=%4d U=%1d D=%1d -=%1d +=%1d\n", joy1X, joy1Y, whammyBar, directionUp, directionDown, buttonMinus, buttonPlus); // printf("Joy1 X=%4d Y=%4d Whammy=%4d U=%1d D=%1d -=%1d +=%1d\n", joy1X, joy1Y, whammyBar, directionUp, directionDown, buttonMinus, buttonPlus); diff --git a/lib/WiiExtension/WiiExtension.h b/lib/WiiExtension/WiiExtension.h index a869afd54..fd0626f39 100644 --- a/lib/WiiExtension/WiiExtension.h +++ b/lib/WiiExtension/WiiExtension.h @@ -30,6 +30,24 @@ #define WII_ANALOG_PRECISION_2 256 #define WII_ANALOG_PRECISION_3 1024 +#define WII_GUITAR_UNSET 0 +#define WII_GUITAR_GH3 1 +#define WII_GUITAR_GHWT 2 +#define WII_GUITAR_UNDEFINED 3 + +#define WII_GUITAR_TOUCHPAD_GREEN 0x03 +#define WII_GUITAR_TOUCHPAD_RED 0x09 +#define WII_GUITAR_TOUCHPAD_YELLOW 0x0E +#define WII_GUITAR_TOUCHPAD_BLUE 0x15 +#define WII_GUITAR_TOUCHPAD_ORANGE 0x1B +#define WII_GUITAR_TOUCHPAD_MAX 0x1F +#define WII_GUITAR_TOUCHPAD_NONE 0x0F +#define WII_GUITAR_TOUCHPAD_OVERLAP 0x03 + +#ifndef HAS_WII_EXTENSION +#define HAS_WII_EXTENSION 1 +#endif + #ifndef WII_EXTENSION_DEBUG #define WII_EXTENSION_DEBUG false #endif @@ -62,6 +80,8 @@ static volatile bool WiiExtension_alarmFired; ((byte) & 0x02 ? '1' : '0'), \ ((byte) & 0x01 ? '1' : '0') +#define TOUCH_BETWEEN_RANGE(val,beg,end) (((val) >= ((beg)-WII_GUITAR_TOUCHPAD_OVERLAP)) && ((val) < (end))) + class WiiExtension { protected: uint8_t address; @@ -107,6 +127,8 @@ class WiiExtension { bool pedalButton = false; uint16_t whammyBar = 0; + int8_t touchBar = 0; + bool isTouched = 0; bool rimLeft = false; bool rimRight = false; @@ -172,6 +194,8 @@ class WiiExtension { uint16_t _triggerPrecision2From = WII_ANALOG_PRECISION_0; uint16_t _triggerPrecision2To = WII_ANALOG_PRECISION_0; + uint8_t _guitarType = WII_GUITAR_UNSET; + uint16_t map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max); uint16_t calibrate(uint16_t pos, uint16_t min, uint16_t max, uint16_t center); diff --git a/src/addons/wiiext.cpp b/src/addons/wiiext.cpp index a40aa104a..46b681f22 100644 --- a/src/addons/wiiext.cpp +++ b/src/addons/wiiext.cpp @@ -87,10 +87,11 @@ void WiiExtensionInput::process() { // whammy currently maps to Joy2X in addition to the raw whammy value whammyBar = wii->whammyBar; + buttonR = wii->pedalButton; leftX = map(wii->joy1X,0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); leftY = map(wii->joy1Y,WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightX = map(wii->joy2X,0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MID,GAMEPAD_JOYSTICK_MAX); + rightX = map(wii->joy2X,0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); rightY = GAMEPAD_JOYSTICK_MID; triggerLeft = 0; @@ -99,7 +100,7 @@ void WiiExtensionInput::process() { buttonL = wii->rimLeft; buttonR = wii->rimRight; - dpadRight = wii->drumLeft; + dpadLeft = wii->drumLeft; buttonA = wii->drumRight; } From f122ec4d9c11163db8f3dd86b073909db4a2ab85 Mon Sep 17 00:00:00 2001 From: Mike Parks Date: Wed, 24 May 2023 01:38:44 -0700 Subject: [PATCH 02/15] Refactor of WiiExtension library to better separate controller contexts. --- lib/WiiExtension/CMakeLists.txt | 17 +- lib/WiiExtension/WiiExtension.cpp | 558 ++---------------- lib/WiiExtension/WiiExtension.h | 101 +--- .../extensions/ClassicExtension.cpp | 226 +++++++ .../extensions/ClassicExtension.h | 13 + lib/WiiExtension/extensions/DrumExtension.cpp | 28 + lib/WiiExtension/extensions/DrumExtension.h | 20 + lib/WiiExtension/extensions/ExtensionBase.cpp | 81 +++ lib/WiiExtension/extensions/ExtensionBase.h | 90 +++ lib/WiiExtension/extensions/Extensions.h | 12 + .../extensions/GuitarExtension.cpp | 121 ++++ lib/WiiExtension/extensions/GuitarExtension.h | 26 + .../extensions/NunchuckExtension.cpp | 76 +++ .../extensions/NunchuckExtension.h | 13 + .../extensions/TaikoExtension.cpp | 37 ++ lib/WiiExtension/extensions/TaikoExtension.h | 18 + .../extensions/TurntableExtension.cpp | 37 ++ .../extensions/TurntableExtension.h | 28 + src/addons/wiiext.cpp | 94 +-- 19 files changed, 950 insertions(+), 646 deletions(-) create mode 100644 lib/WiiExtension/extensions/ClassicExtension.cpp create mode 100644 lib/WiiExtension/extensions/ClassicExtension.h create mode 100644 lib/WiiExtension/extensions/DrumExtension.cpp create mode 100644 lib/WiiExtension/extensions/DrumExtension.h create mode 100644 lib/WiiExtension/extensions/ExtensionBase.cpp create mode 100644 lib/WiiExtension/extensions/ExtensionBase.h create mode 100644 lib/WiiExtension/extensions/Extensions.h create mode 100644 lib/WiiExtension/extensions/GuitarExtension.cpp create mode 100644 lib/WiiExtension/extensions/GuitarExtension.h create mode 100644 lib/WiiExtension/extensions/NunchuckExtension.cpp create mode 100644 lib/WiiExtension/extensions/NunchuckExtension.h create mode 100644 lib/WiiExtension/extensions/TaikoExtension.cpp create mode 100644 lib/WiiExtension/extensions/TaikoExtension.h create mode 100644 lib/WiiExtension/extensions/TurntableExtension.cpp create mode 100644 lib/WiiExtension/extensions/TurntableExtension.h diff --git a/lib/WiiExtension/CMakeLists.txt b/lib/WiiExtension/CMakeLists.txt index 1048267f6..cc6c49c8c 100644 --- a/lib/WiiExtension/CMakeLists.txt +++ b/lib/WiiExtension/CMakeLists.txt @@ -1,6 +1,13 @@ -add_library(WiiExtension WiiExtension.cpp) -target_link_libraries(WiiExtension PUBLIC BitBang_I2C) -target_include_directories(WiiExtension INTERFACE .) -target_include_directories(WiiExtension PUBLIC -BitBang_I2C +add_library(WiiExtension + WiiExtension.cpp + extensions/ExtensionBase.cpp + extensions/ClassicExtension.cpp + extensions/DrumExtension.cpp + extensions/GuitarExtension.cpp + extensions/NunchuckExtension.cpp + extensions/TaikoExtension.cpp + extensions/TurntableExtension.cpp ) +target_link_libraries(WiiExtension PUBLIC pico_stdlib hardware_i2c) +target_include_directories(WiiExtension INTERFACE .) +target_include_directories(WiiExtension PUBLIC .) diff --git a/lib/WiiExtension/WiiExtension.cpp b/lib/WiiExtension/WiiExtension.cpp index 3a06ea90f..84c0ace0f 100644 --- a/lib/WiiExtension/WiiExtension.cpp +++ b/lib/WiiExtension/WiiExtension.cpp @@ -46,23 +46,34 @@ void WiiExtension::start(){ if (idRead[5] == 0x00) { extensionType = WII_EXTENSION_NUNCHUCK; + extensionController = new NunchuckExtension(); } else if (idRead[5] == 0x01) { extensionType = WII_EXTENSION_CLASSIC; if (idRead[0] == 0x01) { extensionType = WII_EXTENSION_CLASSIC_PRO; } + extensionController = new ClassicExtension(); } else if (idRead[5] == 0x03) { extensionType = WII_EXTENSION_GUITAR; if (idRead[0] == 0x01) { extensionType = WII_EXTENSION_DRUMS; + extensionController = new DrumExtension(); + } else if (idRead[0] == 0x03) { + extensionType = WII_EXTENSION_TURNTABLE; + extensionController = new TurntableExtension(); + } else { + extensionController = new GuitarExtension(); } } else if (idRead[5] == 0x11) { extensionType = WII_EXTENSION_TAIKO; + extensionController = new TaikoExtension(); } // in certain situations (eg. Nunchuck), setting the data type in reset() does not affect what this value will be dataType = idRead[4]; if (dataType == WII_DATA_TYPE_0) dataType = WII_DATA_TYPE_1; + extensionController->init(dataType); + extensionController->setExtensionType(extensionType); #if WII_EXTENSION_DEBUG==true printf("WiiExtension::Extension ID: %02x%02x %02x%02x %02x%02x\n", idRead[0], idRead[1], idRead[2], idRead[3], idRead[4], idRead[5]); @@ -70,192 +81,16 @@ void WiiExtension::start(){ #endif if (extensionType != WII_EXTENSION_NONE) { -#if WII_EXTENSION_DEBUG==true - //printf("Extension Type: %d\n", extensionType); -#endif - -#if WII_EXTENSION_DEBUG==true - printf("WiiExtension::Calibration Data\n"); -#endif - if (extensionType == WII_EXTENSION_NUNCHUCK) { - _analogPrecision1From = WII_ANALOG_PRECISION_2; - _analogPrecision1To = WII_ANALOG_PRECISION_3; - -#if WII_EXTENSION_CALIBRATION==true - // read calibration - regWrite[0] = 0x20; - doI2CWrite(regWrite, 1); - - doI2CRead(idRead, 16); - - _maxX1 = idRead[8]; - _minX1 = idRead[9]; - _cenX1 = idRead[10]; - - _maxY1 = idRead[11]; - _minY1 = idRead[12]; - _cenY1 = idRead[13]; - - _accelX0G = ((idRead[0] << 2) | ((idRead[3] >> 2) & 0x03)); - _accelY0G = ((idRead[1] << 2) | ((idRead[3] >> 4) & 0x03)); - _accelZ0G = ((idRead[2] << 2) | ((idRead[3] >> 6) & 0x03)); - - _accelX1G = ((idRead[4] << 2) | ((idRead[7] >> 2) & 0x03)); - _accelY1G = ((idRead[5] << 2) | ((idRead[7] >> 4) & 0x03)); - _accelZ1G = ((idRead[6] << 2) | ((idRead[7] >> 6) & 0x03)); - -#if WII_EXTENSION_DEBUG==true - //printf("Calibration:\n"); - //printf("X0G: %d\n", _accelX0G); - //printf("Y0G: %d\n", _accelY0G); - //printf("Z0G: %d\n", _accelZ0G); - //printf("X1G: %d\n", _accelX1G); - //printf("Y1G: %d\n", _accelY1G); - //printf("YZG: %d\n", _accelZ1G); - //printf("X Min: %d\n", _minX); - //printf("X Max: %d\n", _maxX); - //printf("X Center: %d\n", _cenX); - //printf("Y Min: %d\n", _minY); - //printf("Y Max: %d\n", _maxY); - //printf("Y Center: %d\n", _cenY); -#endif -#endif - } else { - if (dataType == WII_DATA_TYPE_1) { - _analogPrecision1From = WII_ANALOG_PRECISION_1; - _analogPrecision1To = WII_ANALOG_PRECISION_3; - _analogPrecision2From = WII_ANALOG_PRECISION_0; - _analogPrecision2To = WII_ANALOG_PRECISION_3; - - _triggerPrecision1From = WII_ANALOG_PRECISION_0; - _triggerPrecision1To = WII_ANALOG_PRECISION_2; - _triggerPrecision2From = WII_ANALOG_PRECISION_0; - _triggerPrecision2To = WII_ANALOG_PRECISION_2; - } else if (dataType == WII_DATA_TYPE_2) { - _analogPrecision1From = WII_ANALOG_PRECISION_3; - _analogPrecision1To = WII_ANALOG_PRECISION_3; - _analogPrecision2From = WII_ANALOG_PRECISION_3; - _analogPrecision2To = WII_ANALOG_PRECISION_3; - - _triggerPrecision1From = WII_ANALOG_PRECISION_2; - _triggerPrecision1To = WII_ANALOG_PRECISION_2; - _triggerPrecision2From = WII_ANALOG_PRECISION_2; - _triggerPrecision2To = WII_ANALOG_PRECISION_2; - } else if (dataType == WII_DATA_TYPE_3) { - _analogPrecision1From = WII_ANALOG_PRECISION_2; - _analogPrecision1To = WII_ANALOG_PRECISION_3; - _analogPrecision2From = WII_ANALOG_PRECISION_2; - _analogPrecision2To = WII_ANALOG_PRECISION_3; - - _triggerPrecision1From = WII_ANALOG_PRECISION_2; - _triggerPrecision1To = WII_ANALOG_PRECISION_2; - _triggerPrecision2From = WII_ANALOG_PRECISION_2; - _triggerPrecision2To = WII_ANALOG_PRECISION_2; - } - #if WII_EXTENSION_CALIBRATION==true - regWrite[0] = 0x20; - doI2CWrite(regWrite, 1); - - doI2CRead(idRead, 16); - - if (dataType == WII_DATA_TYPE_1) { - _calibrationPrecision1From = WII_ANALOG_PRECISION_2; - _calibrationPrecision1To = WII_ANALOG_PRECISION_1; - _calibrationPrecision2From = WII_ANALOG_PRECISION_2; - _calibrationPrecision2To = WII_ANALOG_PRECISION_0; - } else if (dataType == WII_DATA_TYPE_2) { - _calibrationPrecision1From = WII_ANALOG_PRECISION_2; - _calibrationPrecision1To = WII_ANALOG_PRECISION_3; - _calibrationPrecision2From = WII_ANALOG_PRECISION_2; - _calibrationPrecision2To = WII_ANALOG_PRECISION_3; - } else if (dataType == WII_DATA_TYPE_3) { - _calibrationPrecision1From = WII_ANALOG_PRECISION_2; - _calibrationPrecision1To = WII_ANALOG_PRECISION_2; - _calibrationPrecision2From = WII_ANALOG_PRECISION_2; - _calibrationPrecision2To = WII_ANALOG_PRECISION_2; - } - - _maxX1 = map(idRead[0],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); - _minX1 = map(idRead[1],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); - _cenX1 = map(idRead[2],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); - - _maxY1 = map(idRead[3],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); - _minY1 = map(idRead[4],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); - _cenY1 = map(idRead[5],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); - - _maxX2 = map(idRead[6],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); - _minX2 = map(idRead[7],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); - _cenX2 = map(idRead[8],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); - - _maxY2 = map(idRead[9],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); - _minY2 = map(idRead[10],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); - _cenY2 = map(idRead[11],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); - #if WII_EXTENSION_DEBUG==true - printf("X1 Min: %d\n", _minX1); - printf("X1 Max: %d\n", _maxX1); - printf("X1 Center: %d\n", _cenX1); - printf("Y1 Min: %d\n", _minY1); - printf("Y1 Max: %d\n", _maxY1); - printf("Y1 Center: %d\n", _cenY1); - printf("X2 Min: %d\n", _minX2); - printf("X2 Max: %d\n", _maxX2); - printf("X2 Center: %d\n", _cenX2); - printf("Y2 Min: %d\n", _minY2); - printf("Y2 Max: %d\n", _maxY2); - printf("Y2 Center: %d\n", _cenY2); + printf("WiiExtension::Calibration Data\n"); #endif + regWrite[0] = 0x20; + doI2CWrite(regWrite, 1); + + doI2CRead(idRead, 16); + extensionController->calibrate(idRead); #endif - } - - // reset to default input values in the event of a removal/hotswap - joy1X = 0; - joy1Y = 0; - joy2X = 0; - joy2Y = 0; - accelX = 0; - accelY = 0; - accelZ = 0; - - buttonZ = 0; - buttonC = 0; - buttonZR = 0; - buttonZL = 0; - buttonA = 0; - buttonB = 0; - buttonX = 0; - buttonY = 0; - buttonPlus = 0; - buttonHome = 0; - buttonMinus = 0; - buttonLT = 0; - buttonRT = 0; - - directionUp = 0; - directionDown = 0; - directionLeft = 0; - directionRight = 0; - - triggerLeft = 0; - triggerRight = 0; - - fretGreen = 0; - fretRed = 0; - fretYellow = 0; - fretBlue = 0; - fretOrange = 0; - pedalButton = 0; - - rimLeft = 0; - rimRight = 0; - drumLeft = 0; - drumRight = 0; - - whammyBar = 0; - touchBar = 0; - - _guitarType = WII_GUITAR_UNSET; } else { #if WII_EXTENSION_DEBUG==true printf("WiiExtension::Unknown Extension: %02x%02x %02x%02x %02x%02x\n", idRead[0], idRead[1], idRead[2], idRead[3], idRead[4], idRead[5]); @@ -272,15 +107,29 @@ void WiiExtension::reset(){ int8_t result; bool canContinue = true; +#if WII_EXTENSION_DEBUG==true + printf("WiiExtension::reset\n"); +#endif + if (canContinue) { result = doI2CTest(); canContinue = (result == 1); } #if WII_EXTENSION_DEBUG==true - printf("WiiExtension::reset\n"); + printf("WiiExtension::i2C tested? %1d\n", result); +#endif + +#if WII_EXTENSION_ENCRYPTION==true + if (canContinue) { + regWrite[0] = 0x40; + regWrite[1] = 0x00; + result = doI2CWrite(regWrite, 2); + canContinue = (result > -1); + } #endif +#if WII_EXTENSION_ENCRYPTION==false if (canContinue) { regWrite[0] = 0xF0; regWrite[1] = 0x55; @@ -288,12 +137,20 @@ void WiiExtension::reset(){ canContinue = (result > -1); } +#if WII_EXTENSION_DEBUG==true + printf("WiiExtension::reset 0xF0? %1d\n", result); +#endif + if (canContinue) { regWrite[0] = 0xFB; regWrite[1] = 0x00; result = doI2CWrite(regWrite, 2); canContinue = (result > -1); } +#if WII_EXTENSION_DEBUG==true + printf("WiiExtension::reset 0xFB? %1d\n", result); +#endif +#endif if (canContinue) { // set data format @@ -304,7 +161,7 @@ void WiiExtension::reset(){ } #if WII_EXTENSION_DEBUG==true - printf("WiiExtension::reset canContinue? %1d\n", canContinue); + printf("WiiExtension::reset 0xFE? %1d\n", result); #endif if (canContinue) { @@ -353,305 +210,13 @@ void WiiExtension::poll() { } if (result > 0) { - switch (extensionType) { - case WII_EXTENSION_NUNCHUCK: - joy1X = (regRead[0] & 0xFF); - joy1Y = (regRead[1] & 0xFF); - - accelX = (((regRead[2] << 2) | ((regRead[5] >> 2) & 0x03))); - accelY = (((regRead[3] << 2) | ((regRead[5] >> 4) & 0x03))); - accelZ = (((regRead[4] << 2) | ((regRead[5] >> 6) & 0x03))); - buttonZ = (!(regRead[5] & 0x01)); - buttonC = (!(regRead[5] & 0x02)); - -#if WII_EXTENSION_DEBUG==true - printf("Joy X=%4d Y=%4d Acc X=%4d Y=%4d Z=%4d Btn Z=%1d C=%1d\n", joy1X, joy1Y, accelX, accelY, accelZ, buttonZ, buttonC); -#endif - - break; - case WII_EXTENSION_CLASSIC: - case WII_EXTENSION_CLASSIC_PRO: - // write data format to return - // see wiki for data types - if (dataType == WII_DATA_TYPE_1) { - joy1X = (regRead[0] & 0x3F); - joy1Y = (regRead[1] & 0x3F); - joy2X = ((regRead[0] & 0xC0) >> 3) | ((regRead[1] & 0xC0) >> 5) | ((regRead[2] & 0x80) >> 7); - joy2Y = (regRead[2] & 0x1F); - - triggerLeft = (((regRead[2] & 0x60) >> 2) | ((regRead[3] & 0xE0) >> 5)); - triggerRight = ((regRead[3] & 0x1F) >> 0); - - directionRight = !((regRead[4] & 0x80) >> 7); - directionDown = !((regRead[4] & 0x40) >> 6); - buttonLT = !((regRead[4] & 0x20) >> 5); - buttonMinus = !((regRead[4] & 0x10) >> 4); - buttonHome = !((regRead[4] & 0x08) >> 3); - buttonPlus = !((regRead[4] & 0x04) >> 2); - buttonRT = !((regRead[4] & 0x02) >> 1); - - buttonZL = !((regRead[5] & 0x80) >> 7); - buttonB = !((regRead[5] & 0x40) >> 6); - buttonY = !((regRead[5] & 0x20) >> 5); - buttonA = !((regRead[5] & 0x10) >> 4); - buttonX = !((regRead[5] & 0x08) >> 3); - buttonZR = !((regRead[5] & 0x04) >> 2); - directionLeft = !((regRead[5] & 0x02) >> 1); - directionUp = !((regRead[5] & 0x01) >> 0); - } else if (dataType == WII_DATA_TYPE_2) { - joy1X = ((regRead[0] << 2) | ((regRead[4] & 0x03) >> 0)); - joy1Y = ((regRead[2] << 2) | ((regRead[4] & 0x30) >> 4)); - joy2X = ((regRead[1] << 2) | ((regRead[4] & 0x0C) >> 2)); - joy2Y = ((regRead[3] << 2) | ((regRead[4] & 0xC0) >> 6)); - - triggerLeft = (regRead[5] & 0xFF); - triggerRight = (regRead[6] & 0xFF); - - directionRight = !((regRead[7] & 0x80) >> 7); - directionDown = !((regRead[7] & 0x40) >> 6); - buttonLT = !((regRead[7] & 0x20) >> 5); - buttonMinus = !((regRead[7] & 0x10) >> 4); - buttonHome = !((regRead[7] & 0x08) >> 3); - buttonPlus = !((regRead[7] & 0x04) >> 2); - buttonRT = !((regRead[7] & 0x02) >> 1); - - buttonZL = !((regRead[8] & 0x80) >> 7); - buttonB = !((regRead[8] & 0x40) >> 6); - buttonY = !((regRead[8] & 0x20) >> 5); - buttonA = !((regRead[8] & 0x10) >> 4); - buttonX = !((regRead[8] & 0x08) >> 3); - buttonZR = !((regRead[8] & 0x04) >> 2); - directionLeft = !((regRead[8] & 0x02) >> 1); - directionUp = !((regRead[8] & 0x01) >> 0); - } else if (dataType == WII_DATA_TYPE_3) { - joy1X = (regRead[0] & 0xFF); - joy1Y = (regRead[2] & 0xFF); - joy2X = (regRead[1] & 0xFF); - joy2Y = (regRead[3] & 0xFF); - - triggerLeft = (regRead[4] & 0xFF); - triggerRight = (regRead[5] & 0xFF); - - directionRight = !((regRead[6] & 0x80) >> 7); - directionDown = !((regRead[6] & 0x40) >> 6); - buttonLT = !((regRead[6] & 0x20) >> 5); - buttonMinus = !((regRead[6] & 0x10) >> 4); - buttonHome = !((regRead[6] & 0x08) >> 3); - buttonPlus = !((regRead[6] & 0x04) >> 2); - buttonRT = !((regRead[6] & 0x02) >> 1); - - buttonZL = !((regRead[7] & 0x80) >> 7); - buttonB = !((regRead[7] & 0x40) >> 6); - buttonY = !((regRead[7] & 0x20) >> 5); - buttonA = !((regRead[7] & 0x10) >> 4); - buttonX = !((regRead[7] & 0x08) >> 3); - buttonZR = !((regRead[7] & 0x04) >> 2); - directionLeft = !((regRead[7] & 0x02) >> 1); - directionUp = !((regRead[7] & 0x01) >> 0); - } else { - // unknown - } - -#if WII_EXTENSION_DEBUG==true - //if ((_lastRead[0] != regRead[0]) || (_lastRead[1] != regRead[1]) || (_lastRead[2] != regRead[2]) || (_lastRead[3] != regRead[3])) { - printf("Joy1 X=%4d Y=%4d Joy2 X=%4d Y=%4d\n", joy1X, joy1Y, joy2X, joy2Y); - //} - //printf("Joy1 X=%4d Y=%4d Joy2 X=%4d Y=%4d U=%1d D=%1d L=%1d R=%1d TL=%4d TR=%4d\n", joy1X, joy1Y, joy2X, joy2Y, directionUp, directionDown, directionLeft, directionRight, triggerLeft, triggerRight); - //printf("A=%1d B=%1d X=%1d Y=%1d ZL=%1d ZR=%1d LT=%1d RT=%1d -=%1d H=%1d +=%1d\n", buttonA, buttonB, buttonX, buttonY, buttonZL, buttonZR, buttonLT, buttonRT, buttonMinus, buttonHome, buttonPlus); -#endif + extensionController->process(regRead); + extensionController->postProcess(); - break; - case WII_EXTENSION_GUITAR: - // on first read, check the status of the guitar flag - if (_guitarType == WII_GUITAR_UNSET) { - if (((regRead[0] & 0x80) >> 7) == 0) { - _guitarType = WII_GUITAR_GHWT; - } else { - _guitarType = WII_GUITAR_GH3; - } - // force the data type to 1 when a World Tour guitar is detected - if ((_guitarType == WII_GUITAR_GHWT) && (dataType != WII_DATA_TYPE_1)) { - dataType = WII_DATA_TYPE_1; - _analogPrecision1From = WII_ANALOG_PRECISION_1; - _analogPrecision1To = WII_ANALOG_PRECISION_3; - _analogPrecision2From = WII_ANALOG_PRECISION_0; - _analogPrecision2To = WII_ANALOG_PRECISION_3; - } - } - if (_guitarType != WII_GUITAR_UNSET) { - // as defined works for GH3 guitar - if (dataType == WII_DATA_TYPE_1) { - joy1X = (regRead[0] & 0x3F); - joy1Y = (regRead[1] & 0x3F); - - touchBar = ((_guitarType == WII_GUITAR_GHWT) ? (regRead[2] & 0x1F) : 0); - - whammyBar = (regRead[3] & 0x1F); - joy2X = (regRead[3] & 0x1F); - - directionDown = !((regRead[4] & 0x40) >> 6); - buttonMinus = !((regRead[4] & 0x10) >> 4); - buttonPlus = !((regRead[4] & 0x04) >> 2); - - fretOrange = !((regRead[5] & 0x80) >> 7); - fretRed = !((regRead[5] & 0x40) >> 6); - fretBlue = !((regRead[5] & 0x20) >> 5); - fretGreen = !((regRead[5] & 0x10) >> 4); - fretYellow = !((regRead[5] & 0x08) >> 3); - pedalButton = !((regRead[5] & 0x04) >> 2); - directionUp = !((regRead[5] & 0x01) >> 0); - - isTouched = (touchBar != WII_GUITAR_TOUCHPAD_NONE); - - // process the touch bar for button states - // touch only seems to exist in GHWT, and GHWT always reports data type 1 format regardless of setting - if (isTouched) { - // touched - fretGreen = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_GREEN,WII_GUITAR_TOUCHPAD_RED)); - fretRed = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_RED,WII_GUITAR_TOUCHPAD_YELLOW)); - fretYellow = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_YELLOW,WII_GUITAR_TOUCHPAD_BLUE)); - fretBlue = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_BLUE,WII_GUITAR_TOUCHPAD_ORANGE)); - fretOrange = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_ORANGE,WII_GUITAR_TOUCHPAD_MAX)); - directionDown = isTouched; - } - } else if (dataType == WII_DATA_TYPE_2) { - joy1X = ((regRead[0] << 2) | ((regRead[4] & 0x03) >> 0)); - joy1Y = ((regRead[2] << 2) | ((regRead[4] & 0x30) >> 4)); - - touchBar = 0; - - whammyBar = (regRead[6] & 0xFF); - joy2X = (regRead[6] & 0xFF); - - directionDown = !((regRead[7] & 0x40) >> 6); - buttonMinus = !((regRead[7] & 0x10) >> 4); - buttonPlus = !((regRead[7] & 0x04) >> 2); - - fretOrange = !((regRead[8] & 0x80) >> 7); - fretRed = !((regRead[8] & 0x40) >> 6); - fretBlue = !((regRead[8] & 0x20) >> 5); - fretGreen = !((regRead[8] & 0x10) >> 4); - fretYellow = !((regRead[8] & 0x08) >> 3); - pedalButton = !((regRead[8] & 0x04) >> 2); - directionUp = !((regRead[8] & 0x01) >> 0); - } else if (dataType == WII_DATA_TYPE_3) { - joy1X = (regRead[0] & 0xFF); - joy1Y = (regRead[2] & 0xFF); - - touchBar = 0; - - whammyBar = (regRead[5] & 0xFF); - joy2X = (regRead[5] & 0xFF); - - directionDown = !((regRead[6] & 0x40) >> 6); - buttonMinus = !((regRead[6] & 0x10) >> 4); - buttonPlus = !((regRead[6] & 0x04) >> 2); - - fretOrange = !((regRead[7] & 0x80) >> 7); - fretRed = !((regRead[7] & 0x40) >> 6); - fretBlue = !((regRead[7] & 0x20) >> 5); - fretGreen = !((regRead[7] & 0x10) >> 4); - fretYellow = !((regRead[7] & 0x08) >> 3); - pedalButton = !((regRead[7] & 0x04) >> 2); - directionUp = !((regRead[7] & 0x01) >> 0); - } - } -#if WII_EXTENSION_DEBUG==true -// printf("Joy1 X=%4d Y=%4d Whammy=%4d U=%1d D=%1d -=%1d +=%1d\n", joy1X, joy1Y, whammyBar, directionUp, directionDown, buttonMinus, buttonPlus); -// printf("Joy1 X=%4d Y=%4d Whammy=%4d U=%1d D=%1d -=%1d +=%1d\n", joy1X, joy1Y, whammyBar, directionUp, directionDown, buttonMinus, buttonPlus); -// printf("O=%1d B=%1d Y=%1d R=%1d G=%1d\n", fretOrange, fretBlue, fretYellow, fretRed, fretGreen); -#endif - break; - case WII_EXTENSION_TAIKO: - if (dataType == WII_DATA_TYPE_1) { - drumLeft = !((regRead[5] & 0x40) >> 6); - rimLeft = !((regRead[5] & 0x20) >> 5); - drumRight = !((regRead[5] & 0x10) >> 4); - rimRight = !((regRead[5] & 0x08) >> 3); - } else if (dataType == WII_DATA_TYPE_2) { - drumLeft = !((regRead[8] & 0x40) >> 6); - rimLeft = !((regRead[8] & 0x20) >> 5); - drumRight = !((regRead[8] & 0x10) >> 4); - rimRight = !((regRead[8] & 0x08) >> 3); - } else if (dataType == WII_DATA_TYPE_3) { - drumLeft = !((regRead[7] & 0x40) >> 6); - rimLeft = !((regRead[7] & 0x20) >> 5); - drumRight = !((regRead[7] & 0x10) >> 4); - rimRight = !((regRead[7] & 0x08) >> 3); - } - -#if WII_EXTENSION_DEBUG==true - //if (_lastRead[0] != regRead[0]) printf("Byte0 " BYTE_TO_BINARY_PATTERN "\n", BYTE_TO_BINARY(regRead[0])); - //if (_lastRead[1] != regRead[1]) printf("Byte1 " BYTE_TO_BINARY_PATTERN "\n", BYTE_TO_BINARY(regRead[1])); - //if (_lastRead[2] != regRead[2]) printf("Byte2 " BYTE_TO_BINARY_PATTERN "\n", BYTE_TO_BINARY(regRead[2])); - //if (_lastRead[3] != regRead[3]) printf("Byte3 " BYTE_TO_BINARY_PATTERN "\n", BYTE_TO_BINARY(regRead[3])); - //if (_lastRead[4] != regRead[4]) printf("Byte4 " BYTE_TO_BINARY_PATTERN "\n", BYTE_TO_BINARY(regRead[4])); - //if (_lastRead[5] != regRead[5]) printf("Byte5 " BYTE_TO_BINARY_PATTERN "\n", BYTE_TO_BINARY(regRead[5])); - // - //if (_lastRead[7] != regRead[7]) { - // printf("DL=%1d RL=%1d DR=%1d RR=%1d\n", drumLeft, rimLeft, drumRight, rimRight); - //} -#endif - - break; - } - - // calibrate and remap - joy1X = map( - calibrate(joy1X, _minX1, _maxX1, _cenX1), - 0+_minX1, - (_analogPrecision1From-_maxX1), - 0, - (_analogPrecision1To-1) - ); - joy1Y = map( - calibrate(joy1Y, _minY1, _maxY1, _cenY1), - 0+_minY1, - (_analogPrecision1From-_maxY1), - 0, - (_analogPrecision1To-1) - ); - - joy2X = map( - calibrate(joy2X, _minX2, _maxX2, _cenX2), - 0+_minX2, - (_analogPrecision2From-_maxX2), - 0, - (_analogPrecision2To-1) - ); - joy2Y = map( - calibrate(joy2Y, _minY2, _maxY2, _cenY2), - 0+_minY2, - (_analogPrecision2From-_maxY2), - 0, - (_analogPrecision2To-1) - ); - - triggerLeft = map( - triggerLeft, - 0, - (_triggerPrecision1From-1), - 0, - (_triggerPrecision1To-1) - ); - triggerRight = map( - triggerRight, - 0, - (_triggerPrecision2From-1), - 0, - (_triggerPrecision2To-1) - ); - -#if WII_EXTENSION_DEBUG==true - //if ((_lastRead[0] != regRead[0]) || (_lastRead[1] != regRead[1]) || (_lastRead[2] != regRead[2]) || (_lastRead[3] != regRead[3])) { - // printf("Joy1 X=%4d Y=%4d Joy2 X=%4d Y=%4d\n", joy1X, joy1Y, joy2X, joy2Y); - //} - //printf("Joy1 X=%4d Y=%4d Joy2 X=%4d Y=%4d U=%1d D=%1d L=%1d R=%1d TL=%4d TR=%4d\n", joy1X, joy1Y, joy2X, joy2Y, directionUp, directionDown, directionLeft, directionRight, triggerLeft, triggerRight); - //printf("A=%1d B=%1d X=%1d Y=%1d ZL=%1d ZR=%1d LT=%1d RT=%1d -=%1d H=%1d +=%1d\n", buttonA, buttonB, buttonX, buttonY, buttonZL, buttonZR, buttonLT, buttonRT, buttonMinus, buttonHome, buttonPlus); for (int i = 0; i < result; ++i) { _lastRead[i] = regRead[i]; } -#endif + // continue poll regWrite[0] = 0x00; result = doI2CWrite(regWrite, 1); @@ -667,32 +232,6 @@ void WiiExtension::poll() { } } -uint16_t WiiExtension::map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max) { - return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; -} - -uint16_t WiiExtension::calibrate(uint16_t pos, uint16_t min, uint16_t max, uint16_t cen) { - uint16_t result; - -#if WII_EXTENSION_CALIBRATION==true - if (pos >= min && pos <= max) { - result = pos; - } else { - if (pos < min) { - result = min; - } else if (pos > max) { - result = max; - } else { - result = cen; - } - } -#else - result = pos; -#endif - - return result; -} - int WiiExtension::doI2CWrite(uint8_t *pData, int iLen) { int result = i2c_write_blocking(picoI2C, address, pData, iLen, false); waitUntil_us(WII_EXTENSION_DELAY); @@ -702,6 +241,11 @@ int WiiExtension::doI2CWrite(uint8_t *pData, int iLen) { int WiiExtension::doI2CRead(uint8_t *pData, int iLen) { int result = i2c_read_blocking(picoI2C, address, pData, iLen, false); waitUntil_us(WII_EXTENSION_DELAY); +#if WII_EXTENSION_ENCRYPTION==true + for (int i = 0; i < iLen; ++i) { + pData[i] = WII_DECRYPT_BYTE(pData[i]); + } +#endif return result; } @@ -709,6 +253,9 @@ uint8_t WiiExtension::doI2CTest() { int result; uint8_t rxdata; result = doI2CRead(&rxdata, 1); +#if WII_EXTENSION_DEBUG==true + printf("WiiExtension::doI2CTest: %d\n", result); +#endif return (result >= 0); } @@ -719,6 +266,7 @@ void WiiExtension::doI2CInit() { i2c_init(picoI2C, iSpeed); gpio_set_function(iSDA, GPIO_FUNC_I2C); gpio_set_function(iSCL, GPIO_FUNC_I2C); + gpio_pull_up(iSDA); gpio_pull_up(iSCL); diff --git a/lib/WiiExtension/WiiExtension.h b/lib/WiiExtension/WiiExtension.h index fd0626f39..6b51c799d 100644 --- a/lib/WiiExtension/WiiExtension.h +++ b/lib/WiiExtension/WiiExtension.h @@ -7,6 +7,8 @@ #include "pico/stdlib.h" #include "hardware/i2c.h" +#include "extensions/Extensions.h" + #define WII_EXTENSION_NONE -1 #define WII_EXTENSION_NUNCHUCK 0 #define WII_EXTENSION_CLASSIC 1 @@ -52,6 +54,10 @@ #define WII_EXTENSION_DEBUG false #endif +#ifndef WII_EXTENSION_ENCRYPTION +#define WII_EXTENSION_ENCRYPTION false +#endif + #ifndef WII_EXTENSION_DELAY #define WII_EXTENSION_DELAY 300 #endif @@ -81,6 +87,7 @@ static volatile bool WiiExtension_alarmFired; ((byte) & 0x01 ? '1' : '0') #define TOUCH_BETWEEN_RANGE(val,beg,end) (((val) >= ((beg)-WII_GUITAR_TOUCHPAD_OVERLAP)) && ((val) < (end))) +#define WII_DECRYPT_BYTE(x) (((x) ^ 0x17) + 0x17) class WiiExtension { protected: @@ -89,52 +96,6 @@ class WiiExtension { int8_t extensionType = WII_EXTENSION_NONE; int8_t dataType = WII_DATA_TYPE_0; - uint16_t joy1X = 0; - uint16_t joy1Y = 0; - uint16_t joy2X = 0; - uint16_t joy2Y = 0; - uint16_t accelX = 0; - uint16_t accelY = 0; - uint16_t accelZ = 0; - - bool buttonZ = false; - bool buttonC = false; - bool buttonZR = false; - bool buttonZL = false; - bool buttonA = false; - bool buttonB = false; - bool buttonX = false; - bool buttonY = false; - bool buttonPlus = false; - bool buttonHome = false; - bool buttonMinus = false; - bool buttonLT = false; - bool buttonRT = false; - - bool directionUp = false; - bool directionDown = false; - bool directionLeft = false; - bool directionRight = false; - - uint16_t triggerLeft = 0; - uint16_t triggerRight = 0; - - bool fretGreen = false; - bool fretRed = false; - bool fretYellow = false; - bool fretBlue = false; - bool fretOrange = false; - bool pedalButton = false; - - uint16_t whammyBar = 0; - int8_t touchBar = 0; - bool isTouched = 0; - - bool rimLeft = false; - bool rimRight = false; - bool drumLeft = false; - bool drumRight = false; - bool isReady = false; // Constructor @@ -145,56 +106,18 @@ class WiiExtension { void reset(); void start(); void poll(); + + ExtensionBase* getController() { return extensionController; }; private: - + ExtensionBase *extensionController = NULL; + uint8_t iSDA; uint8_t iSCL; uint8_t bWire; i2c_inst_t *picoI2C; - int32_t iSpeed; - uint16_t _minX1 = 0; - uint16_t _maxX1 = 1; - uint16_t _cenX1 = 0; - - uint16_t _minY1 = 0; - uint16_t _maxY1 = 1; - uint16_t _cenY1 = 0; - - uint16_t _minX2 = 0; - uint16_t _maxX2 = 1; - uint16_t _cenX2 = 0; - - uint16_t _minY2 = 0; - uint16_t _maxY2 = 1; - uint16_t _cenY2 = 0; - - uint16_t _accelX0G = 0; - uint16_t _accelY0G = 0; - uint16_t _accelZ0G = 0; - uint16_t _accelX1G = 0; - uint16_t _accelY1G = 0; - uint16_t _accelZ1G = 0; - - //uint8_t _lastRead[16]; - - uint16_t _calibrationPrecision1From = WII_ANALOG_PRECISION_0; - uint16_t _calibrationPrecision1To = WII_ANALOG_PRECISION_0; - uint16_t _calibrationPrecision2From = WII_ANALOG_PRECISION_0; - uint16_t _calibrationPrecision2To = WII_ANALOG_PRECISION_0; - - uint16_t _analogPrecision1From = WII_ANALOG_PRECISION_0; - uint16_t _analogPrecision1To = WII_ANALOG_PRECISION_0; - uint16_t _analogPrecision2From = WII_ANALOG_PRECISION_0; - uint16_t _analogPrecision2To = WII_ANALOG_PRECISION_0; - - uint16_t _triggerPrecision1From = WII_ANALOG_PRECISION_0; - uint16_t _triggerPrecision1To = WII_ANALOG_PRECISION_0; - uint16_t _triggerPrecision2From = WII_ANALOG_PRECISION_0; - uint16_t _triggerPrecision2To = WII_ANALOG_PRECISION_0; - - uint8_t _guitarType = WII_GUITAR_UNSET; + uint8_t _lastRead[16] = {0xFF}; uint16_t map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max); uint16_t calibrate(uint16_t pos, uint16_t min, uint16_t max, uint16_t center); diff --git a/lib/WiiExtension/extensions/ClassicExtension.cpp b/lib/WiiExtension/extensions/ClassicExtension.cpp new file mode 100644 index 000000000..711c195ac --- /dev/null +++ b/lib/WiiExtension/extensions/ClassicExtension.cpp @@ -0,0 +1,226 @@ +#include "ExtensionBase.h" +#include "ClassicExtension.h" + +#include "WiiExtension.h" + +void ClassicExtension::init(uint8_t dataType) { + ExtensionBase::init(dataType); + if (getDataType() == WII_DATA_TYPE_1) { + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].origin = WII_ANALOG_PRECISION_1; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].origin = WII_ANALOG_PRECISION_1; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].origin = WII_ANALOG_PRECISION_0; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].destination = WII_ANALOG_PRECISION_3; + + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].origin = WII_ANALOG_PRECISION_0; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_0; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; + + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_LEFT].origin = WII_ANALOG_PRECISION_1; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_LEFT].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_RIGHT].origin = WII_ANALOG_PRECISION_1; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_RIGHT].destination = WII_ANALOG_PRECISION_3; + } else if (getDataType() == WII_DATA_TYPE_2) { + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].origin = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].origin = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_X].origin = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].origin = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].destination = WII_ANALOG_PRECISION_3; + + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; + + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_LEFT].origin = WII_ANALOG_PRECISION_1; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_LEFT].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_RIGHT].origin = WII_ANALOG_PRECISION_1; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_RIGHT].destination = WII_ANALOG_PRECISION_3; + } else if (getDataType() == WII_DATA_TYPE_3) { + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_X].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].destination = WII_ANALOG_PRECISION_3; + + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; + + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_LEFT].origin = WII_ANALOG_PRECISION_1; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_LEFT].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_RIGHT].origin = WII_ANALOG_PRECISION_1; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_RIGHT].destination = WII_ANALOG_PRECISION_3; + } + +#if WII_EXTENSION_CALIBRATION==false + // preseed calibration data with max ranges + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].minimum = 7; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].center = 31; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].maximum = 57; + + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].minimum = 7; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].center = 32; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].maximum = 57; + + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].minimum = 4; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].center = 16; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].maximum = 29; + + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].minimum = 2; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].center = 15; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].maximum = 27; +#endif +} + +void ClassicExtension::calibrate(uint8_t *calibrationData) { +#if WII_EXTENSION_CALIBRATION==true + if (getDataType() == WII_DATA_TYPE_1) { + _calibrationPrecision1From = WII_ANALOG_PRECISION_2; + _calibrationPrecision1To = WII_ANALOG_PRECISION_1; + _calibrationPrecision2From = WII_ANALOG_PRECISION_2; + _calibrationPrecision2To = WII_ANALOG_PRECISION_0; + } else if (getDataType() == WII_DATA_TYPE_2) { + _calibrationPrecision1From = WII_ANALOG_PRECISION_2; + _calibrationPrecision1To = WII_ANALOG_PRECISION_3; + _calibrationPrecision2From = WII_ANALOG_PRECISION_2; + _calibrationPrecision2To = WII_ANALOG_PRECISION_3; + } else if (getDataType() == WII_DATA_TYPE_3) { + _calibrationPrecision1From = WII_ANALOG_PRECISION_2; + _calibrationPrecision1To = WII_ANALOG_PRECISION_2; + _calibrationPrecision2From = WII_ANALOG_PRECISION_2; + _calibrationPrecision2To = WII_ANALOG_PRECISION_2; + } + + _maxX1 = map(idRead[0],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); + _minX1 = map(idRead[1],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); + _cenX1 = map(idRead[2],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); + + _maxY1 = map(idRead[3],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); + _minY1 = map(idRead[4],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); + _cenY1 = map(idRead[5],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); + + _maxX2 = map(idRead[6],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); + _minX2 = map(idRead[7],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); + _cenX2 = map(idRead[8],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); + + _maxY2 = map(idRead[9],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); + _minY2 = map(idRead[10],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); + _cenY2 = map(idRead[11],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); + +#if WII_EXTENSION_DEBUG==true +// printf("X1 Min: %d\n", _minX1); +// printf("X1 Max: %d\n", _maxX1); +// printf("X1 Center: %d\n", _cenX1); +// printf("Y1 Min: %d\n", _minY1); +// printf("Y1 Max: %d\n", _maxY1); +// printf("Y1 Center: %d\n", _cenY1); +// printf("X2 Min: %d\n", _minX2); +// printf("X2 Max: %d\n", _maxX2); +// printf("X2 Center: %d\n", _cenX2); +// printf("Y2 Min: %d\n", _minY2); +// printf("Y2 Max: %d\n", _maxY2); +// printf("Y2 Center: %d\n", _cenY2); +#endif +#endif +} + +void ClassicExtension::process(uint8_t *inputData) { + // write data format to return + // see wiki for data types + if (getDataType() == WII_DATA_TYPE_1) { + analogState[WiiAnalogs::WII_ANALOG_LEFT_X] = (inputData[0] & 0x3F); + analogState[WiiAnalogs::WII_ANALOG_LEFT_Y] = (inputData[1] & 0x3F); + analogState[WiiAnalogs::WII_ANALOG_RIGHT_X] = ((inputData[0] & 0xC0) >> 3) | ((inputData[1] & 0xC0) >> 5) | ((inputData[2] & 0x80) >> 7); + analogState[WiiAnalogs::WII_ANALOG_RIGHT_Y] = (inputData[2] & 0x1F); + + analogState[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT] = (((inputData[2] & 0x60) >> 2) | ((inputData[3] & 0xE0) >> 5)); + analogState[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT] = ((inputData[3] & 0x1F) >> 0); + + directionalPad[WiiDirectionalPad::WII_DIRECTION_RIGHT] = !((inputData[4] & 0x80) >> 7); + directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN] = !((inputData[4] & 0x40) >> 6); + buttons[WiiButtons::WII_BUTTON_L] = !((inputData[4] & 0x20) >> 5); + buttons[WiiButtons::WII_BUTTON_MINUS] = !((inputData[4] & 0x10) >> 4); + buttons[WiiButtons::WII_BUTTON_HOME] = !((inputData[4] & 0x08) >> 3); + buttons[WiiButtons::WII_BUTTON_PLUS] = !((inputData[4] & 0x04) >> 2); + buttons[WiiButtons::WII_BUTTON_R] = !((inputData[4] & 0x02) >> 1); + + buttons[WiiButtons::WII_BUTTON_ZL] = !((inputData[5] & 0x80) >> 7); + buttons[WiiButtons::WII_BUTTON_B] = !((inputData[5] & 0x40) >> 6); + buttons[WiiButtons::WII_BUTTON_Y] = !((inputData[5] & 0x20) >> 5); + buttons[WiiButtons::WII_BUTTON_A] = !((inputData[5] & 0x10) >> 4); + buttons[WiiButtons::WII_BUTTON_X] = !((inputData[5] & 0x08) >> 3); + buttons[WiiButtons::WII_BUTTON_ZR] = !((inputData[5] & 0x04) >> 2); + directionalPad[WiiDirectionalPad::WII_DIRECTION_LEFT] = !((inputData[5] & 0x02) >> 1); + directionalPad[WiiDirectionalPad::WII_DIRECTION_UP] = !((inputData[5] & 0x01) >> 0); + } else if (getDataType() == WII_DATA_TYPE_2) { + analogState[WiiAnalogs::WII_ANALOG_LEFT_X] = ((inputData[0] << 2) | ((inputData[4] & 0x03) >> 0)); + analogState[WiiAnalogs::WII_ANALOG_LEFT_Y] = ((inputData[2] << 2) | ((inputData[4] & 0x30) >> 4)); + analogState[WiiAnalogs::WII_ANALOG_RIGHT_X] = ((inputData[1] << 2) | ((inputData[4] & 0x0C) >> 2)); + analogState[WiiAnalogs::WII_ANALOG_RIGHT_Y] = ((inputData[3] << 2) | ((inputData[4] & 0xC0) >> 6)); + + analogState[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT] = (inputData[5] & 0xFF); + analogState[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT] = (inputData[6] & 0xFF); + + directionalPad[WiiDirectionalPad::WII_DIRECTION_RIGHT] = !((inputData[7] & 0x80) >> 7); + directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN] = !((inputData[7] & 0x40) >> 6); + buttons[WiiButtons::WII_BUTTON_L] = !((inputData[7] & 0x20) >> 5); + buttons[WiiButtons::WII_BUTTON_MINUS] = !((inputData[7] & 0x10) >> 4); + buttons[WiiButtons::WII_BUTTON_HOME] = !((inputData[7] & 0x08) >> 3); + buttons[WiiButtons::WII_BUTTON_PLUS] = !((inputData[7] & 0x04) >> 2); + buttons[WiiButtons::WII_BUTTON_R] = !((inputData[7] & 0x02) >> 1); + + buttons[WiiButtons::WII_BUTTON_ZL] = !((inputData[8] & 0x80) >> 7); + buttons[WiiButtons::WII_BUTTON_B] = !((inputData[8] & 0x40) >> 6); + buttons[WiiButtons::WII_BUTTON_Y] = !((inputData[8] & 0x20) >> 5); + buttons[WiiButtons::WII_BUTTON_A] = !((inputData[8] & 0x10) >> 4); + buttons[WiiButtons::WII_BUTTON_X] = !((inputData[8] & 0x08) >> 3); + buttons[WiiButtons::WII_BUTTON_ZR] = !((inputData[8] & 0x04) >> 2); + directionalPad[WiiDirectionalPad::WII_DIRECTION_LEFT] = !((inputData[8] & 0x02) >> 1); + directionalPad[WiiDirectionalPad::WII_DIRECTION_UP] = !((inputData[8] & 0x01) >> 0); + } else if (getDataType() == WII_DATA_TYPE_3) { + analogState[WiiAnalogs::WII_ANALOG_LEFT_X] = (inputData[0] & 0xFF); + analogState[WiiAnalogs::WII_ANALOG_LEFT_Y] = (inputData[2] & 0xFF); + analogState[WiiAnalogs::WII_ANALOG_RIGHT_X] = (inputData[1] & 0xFF); + analogState[WiiAnalogs::WII_ANALOG_RIGHT_Y] = (inputData[3] & 0xFF); + + analogState[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT] = (inputData[4] & 0xFF); + analogState[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT] = (inputData[5] & 0xFF); + + directionalPad[WiiDirectionalPad::WII_DIRECTION_RIGHT] = !((inputData[6] & 0x80) >> 7); + directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN] = !((inputData[6] & 0x40) >> 6); + buttons[WiiButtons::WII_BUTTON_L] = !((inputData[6] & 0x20) >> 5); + buttons[WiiButtons::WII_BUTTON_MINUS] = !((inputData[6] & 0x10) >> 4); + buttons[WiiButtons::WII_BUTTON_HOME] = !((inputData[6] & 0x08) >> 3); + buttons[WiiButtons::WII_BUTTON_PLUS] = !((inputData[6] & 0x04) >> 2); + buttons[WiiButtons::WII_BUTTON_R] = !((inputData[6] & 0x02) >> 1); + + buttons[WiiButtons::WII_BUTTON_ZL] = !((inputData[7] & 0x80) >> 7); + buttons[WiiButtons::WII_BUTTON_B] = !((inputData[7] & 0x40) >> 6); + buttons[WiiButtons::WII_BUTTON_Y] = !((inputData[7] & 0x20) >> 5); + buttons[WiiButtons::WII_BUTTON_A] = !((inputData[7] & 0x10) >> 4); + buttons[WiiButtons::WII_BUTTON_X] = !((inputData[7] & 0x08) >> 3); + buttons[WiiButtons::WII_BUTTON_ZR] = !((inputData[7] & 0x04) >> 2); + directionalPad[WiiDirectionalPad::WII_DIRECTION_LEFT] = !((inputData[7] & 0x02) >> 1); + directionalPad[WiiDirectionalPad::WII_DIRECTION_UP] = !((inputData[7] & 0x01) >> 0); + } else { + // unknown + } + +#if WII_EXTENSION_DEBUG==true + //if ((_lastRead[0] != inputData[0]) || (_lastRead[1] != inputData[1]) || (_lastRead[2] != inputData[2]) || (_lastRead[3] != inputData[3])) { + // printf("Joy1 X=%4d Y=%4d Joy2 X=%4d Y=%4d\n", joy1X, joy1Y, joy2X, joy2Y); + //} + //printf("Joy1 X=%5d Y=%5d Joy2 X=%5d Y=%5d U=%1d D=%1d L=%1d R=%1d TL=%5d TR=%5d\n", analogState[WiiAnalogs::ANALOG_LEFT_X], analogState[WiiAnalogs::ANALOG_LEFT_Y], analogState[WiiAnalogs::ANALOG_RIGHT_X], analogState[WiiAnalogs::ANALOG_RIGHT_Y], directionalPad[WiiDirectionalPad::DIRECTION_UP], directionalPad[WiiDirectionalPad::DIRECTION_DOWN], directionalPad[WiiDirectionalPad::DIRECTION_LEFT], directionalPad[WiiDirectionalPad::DIRECTION_RIGHT], analogState[WiiAnalogs::ANALOG_TRIGGER_LEFT], analogState[WiiAnalogs::ANALOG_TRIGGER_RIGHT]); + //printf("A=%1d B=%1d X=%1d Y=%1d ZL=%1d ZR=%1d LT=%1d RT=%1d -=%1d H=%1d +=%1d\n", buttonA, buttonB, buttonX, buttonY, buttonZL, buttonZR, buttonLT, buttonRT, buttonMinus, buttonHome, buttonPlus); +#endif +} \ No newline at end of file diff --git a/lib/WiiExtension/extensions/ClassicExtension.h b/lib/WiiExtension/extensions/ClassicExtension.h new file mode 100644 index 000000000..c2d98edcb --- /dev/null +++ b/lib/WiiExtension/extensions/ClassicExtension.h @@ -0,0 +1,13 @@ +#ifndef _CLASSICEXTENSION_H_ +#define _CLASSICEXTENSION_H_ + +#include "ExtensionBase.h" + +class ClassicExtension : public ExtensionBase { + public: + void init(uint8_t dataType) override; + void calibrate(uint8_t *calibrationData) override; + void process(uint8_t *inputData) override; +}; + +#endif \ No newline at end of file diff --git a/lib/WiiExtension/extensions/DrumExtension.cpp b/lib/WiiExtension/extensions/DrumExtension.cpp new file mode 100644 index 000000000..ff9f206d7 --- /dev/null +++ b/lib/WiiExtension/extensions/DrumExtension.cpp @@ -0,0 +1,28 @@ +#include "ExtensionBase.h" +#include "DrumExtension.h" + +#include "WiiExtension.h" + +void DrumExtension::process(uint8_t *inputData) { + // drums accept three data modes, but will only report as type 1, much like GHWT + analogState[WiiAnalogs::WII_ANALOG_LEFT_X] = (inputData[0] & 0x3F); + analogState[WiiAnalogs::WII_ANALOG_LEFT_Y] = (inputData[1] & 0x3F); + + buttons[WiiButtons::WII_BUTTON_MINUS] = !((inputData[4] & 0x10) >> 4); + buttons[WiiButtons::WII_BUTTON_PLUS] = !((inputData[4] & 0x04) >> 2); + + buttons[DrumButtons::DRUM_ORANGE] = !((inputData[5] & 0x80) >> 7); + buttons[DrumButtons::DRUM_RED] = !((inputData[5] & 0x40) >> 6); + buttons[DrumButtons::DRUM_YELLOW] = !((inputData[5] & 0x20) >> 5); + buttons[DrumButtons::DRUM_GREEN] = !((inputData[5] & 0x10) >> 4); + buttons[DrumButtons::DRUM_BLUE] = !((inputData[5] & 0x08) >> 3); + buttons[DrumButtons::DRUM_PEDAL] = !((inputData[5] & 0x04) >> 2); + +#if WII_EXTENSION_DEBUG==true + //printf("O=%1d R=%1d Y=%1d G=%1d B=%1d P=%1d\n", buttons[DrumButtons::DRUM_ORANGE], buttons[DrumButtons::DRUM_RED], buttons[DrumButtons::DRUM_YELLOW], buttons[DrumButtons::DRUM_GREEN], buttons[DrumButtons::DRUM_BLUE], buttons[DrumButtons::DRUM_PEDAL]); + //printf("-=%1d +=%1d Joy X=%4d Y=%4d\n", buttons[WiiButtons::BUTTON_MINUS], buttons[WiiButtons::BUTTON_PLUS], analogState[WiiAnalogs::ANALOG_LEFT_X], analogState[WiiAnalogs::ANALOG_LEFT_Y]); + //for (int i = 0; i < result; ++i) { + // if (_lastRead[i] != regRead[i]) printf("Byte%2d " BYTE_TO_BINARY_PATTERN "\n", i, BYTE_TO_BINARY(regRead[i])); + //} +#endif +} \ No newline at end of file diff --git a/lib/WiiExtension/extensions/DrumExtension.h b/lib/WiiExtension/extensions/DrumExtension.h new file mode 100644 index 000000000..3227159d5 --- /dev/null +++ b/lib/WiiExtension/extensions/DrumExtension.h @@ -0,0 +1,20 @@ +#ifndef _DRUMEXTENSION_H_ +#define _DRUMEXTENSION_H_ + +#include "ExtensionBase.h" + +enum DrumButtons { + DRUM_ORANGE = WiiButtons::WII_BUTTON_ZL, + DRUM_RED = WiiButtons::WII_BUTTON_B, + DRUM_BLUE = WiiButtons::WII_BUTTON_Y, + DRUM_GREEN = WiiButtons::WII_BUTTON_A, + DRUM_YELLOW = WiiButtons::WII_BUTTON_X, + DRUM_PEDAL = WiiButtons::WII_BUTTON_ZR +}; + +class DrumExtension : public ExtensionBase { + public: + void process(uint8_t *inputData) override; +}; + +#endif \ No newline at end of file diff --git a/lib/WiiExtension/extensions/ExtensionBase.cpp b/lib/WiiExtension/extensions/ExtensionBase.cpp new file mode 100644 index 000000000..9d2c857ee --- /dev/null +++ b/lib/WiiExtension/extensions/ExtensionBase.cpp @@ -0,0 +1,81 @@ +#include "ExtensionBase.h" + +#include +#include + +#include "WiiExtension.h" + +void ExtensionBase::init(uint8_t dataType) { + uint8_t i; + + setDataType(dataType); + + for (i = 0; i < WiiDirectionalPad::WII_MAX_DIRECTIONS; ++i) directionalPad[i] = 0; + for (i = 0; i < WiiButtons::WII_MAX_BUTTONS; ++i) buttons[i] = 0; + for (i = 0; i < WiiMotions::WII_MAX_MOTIONS; ++i) motionState[i] = 0; + for (i = 0; i < WiiAnalogs::WII_MAX_ANALOGS; ++i) { + analogState[i] = 0; + _analogCalibration[i].minimum = 0; + _analogCalibration[i].center = 0; + _analogCalibration[i].maximum = 0; + } +} + +void ExtensionBase::calibrate(uint8_t *calibrationData) { +} + +void ExtensionBase::postProcess() { + uint8_t i; + + for (i = 0; i < WiiAnalogs::WII_MAX_ANALOGS; ++i) { + analogState[i] = map( + applyCalibration(analogState[i], _analogCalibration[i].minimum, _analogCalibration[i].maximum, _analogCalibration[i].center), + 0+_analogCalibration[i].minimum, + (_analogPrecision[i].origin-_analogCalibration[i].maximum), + 0, + (_analogPrecision[i].destination-1) + ); + } + +#if WII_EXTENSION_DEBUG==true + //if ((_lastRead[0] != regRead[0]) || (_lastRead[1] != regRead[1]) || (_lastRead[2] != regRead[2]) || (_lastRead[3] != regRead[3])) { + // printf("Joy1 X=%4d Y=%4d Joy2 X=%4d Y=%4d\n", joy1X, joy1Y, joy2X, joy2Y); + //} + //printf("C Joy1 X=%5d Y=%5d Joy2 X=%5d Y=%5d U=%1d D=%1d L=%1d R=%1d TL=%5d TR=%4d\n", analogState[WiiAnalogs::ANALOG_LEFT_X], analogState[WiiAnalogs::ANALOG_LEFT_Y], analogState[WiiAnalogs::ANALOG_RIGHT_X], analogState[WiiAnalogs::ANALOG_RIGHT_Y], directionalPad[WiiDirectionalPad::DIRECTION_UP], directionalPad[WiiDirectionalPad::DIRECTION_DOWN], directionalPad[WiiDirectionalPad::DIRECTION_LEFT], directionalPad[WiiDirectionalPad::DIRECTION_RIGHT], analogState[WiiAnalogs::ANALOG_TRIGGER_LEFT], analogState[WiiAnalogs::ANALOG_TRIGGER_RIGHT]); + //printf("A=%1d B=%1d X=%1d Y=%1d ZL=%1d ZR=%1d LT=%1d RT=%1d -=%1d H=%1d +=%1d\n", buttonA, buttonB, buttonX, buttonY, buttonZL, buttonZR, buttonLT, buttonRT, buttonMinus, buttonHome, buttonPlus); +#endif +} + +void ExtensionBase::setDataType(uint8_t dataType) { + _dataType = dataType; +} + +void ExtensionBase::setExtensionType(uint8_t extensionType) { + _extensionType = extensionType; +} + +uint16_t ExtensionBase::map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +uint16_t ExtensionBase::applyCalibration(uint16_t pos, uint16_t min, uint16_t max, uint16_t cen) { + uint16_t result; + +#if WII_EXTENSION_CALIBRATION==true + if (pos >= min && pos <= max) { + result = pos; + } else { + if (pos < min) { + result = min; + } else if (pos > max) { + result = max; + } else { + result = cen; + } + } +#else + result = pos; +#endif + + return result; +} \ No newline at end of file diff --git a/lib/WiiExtension/extensions/ExtensionBase.h b/lib/WiiExtension/extensions/ExtensionBase.h new file mode 100644 index 000000000..7d3f45aac --- /dev/null +++ b/lib/WiiExtension/extensions/ExtensionBase.h @@ -0,0 +1,90 @@ +#ifndef _EXTENSIONBASE_H_ +#define _EXTENSIONBASE_H_ + +#include +#include + +enum WiiDirectionalPad { + WII_DIRECTION_UP, + WII_DIRECTION_DOWN, + WII_DIRECTION_LEFT, + WII_DIRECTION_RIGHT, + WII_MAX_DIRECTIONS +}; + +enum WiiButtons { + WII_BUTTON_A, + WII_BUTTON_B, + WII_BUTTON_C, + WII_BUTTON_X, + WII_BUTTON_Y, + WII_BUTTON_Z, + WII_BUTTON_L, + WII_BUTTON_R, + WII_BUTTON_ZL, + WII_BUTTON_ZR, + WII_BUTTON_MINUS, + WII_BUTTON_HOME, + WII_BUTTON_PLUS, + WII_MAX_BUTTONS +}; + +enum WiiAnalogs { + WII_ANALOG_LEFT_X, + WII_ANALOG_LEFT_Y, + WII_ANALOG_RIGHT_X, + WII_ANALOG_RIGHT_Y, + WII_ANALOG_TRIGGER_LEFT, + WII_ANALOG_TRIGGER_RIGHT, + WII_ANALOG_CALIBRATION_PRECISION_LEFT, + WII_ANALOG_CALIBRATION_PRECISION_RIGHT, + WII_MAX_ANALOGS +}; + +enum WiiMotions { + WII_MOTION_X, + WII_MOTION_Y, + WII_MOTION_Z, + WII_MAX_MOTIONS +}; + +typedef struct { + uint16_t origin; + uint16_t destination; +} WiiAnalogPrecision; + +typedef struct { + uint16_t minimum; + uint16_t center; + uint16_t maximum; +} WiiAnalogCalibration; + +class ExtensionBase { + protected: + uint8_t _dataType; + uint8_t _extensionType; + + WiiAnalogPrecision _analogPrecision[WiiAnalogs::WII_MAX_ANALOGS]; + WiiAnalogCalibration _analogCalibration[WiiAnalogs::WII_MAX_ANALOGS]; + private: + uint16_t map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max); + uint16_t applyCalibration(uint16_t pos, uint16_t min, uint16_t max, uint16_t cen); + public: + bool directionalPad[WiiDirectionalPad::WII_MAX_DIRECTIONS]; + bool buttons[WiiButtons::WII_MAX_BUTTONS]; + uint16_t analogState[WiiAnalogs::WII_MAX_ANALOGS]; + uint16_t motionState[WiiMotions::WII_MAX_MOTIONS]; + + virtual void init(uint8_t dataType); + virtual void calibrate(uint8_t *calibrationData); + virtual void process(uint8_t *inputData); + virtual void postProcess(); + + void setDataType(uint8_t dataType); + uint8_t getDataType() { return _dataType; }; + + void setExtensionType(uint8_t extensionType); + uint8_t getExtensionType() { return _extensionType; }; +}; + +#endif diff --git a/lib/WiiExtension/extensions/Extensions.h b/lib/WiiExtension/extensions/Extensions.h new file mode 100644 index 000000000..1d439e599 --- /dev/null +++ b/lib/WiiExtension/extensions/Extensions.h @@ -0,0 +1,12 @@ +#ifndef _EXTENSIONS_H_ +#define _EXTENSIONS_H_ + +#include "ExtensionBase.h" +#include "ClassicExtension.h" +#include "DrumExtension.h" +#include "GuitarExtension.h" +#include "NunchuckExtension.h" +#include "TaikoExtension.h" +#include "TurntableExtension.h" + +#endif diff --git a/lib/WiiExtension/extensions/GuitarExtension.cpp b/lib/WiiExtension/extensions/GuitarExtension.cpp new file mode 100644 index 000000000..4afd6259b --- /dev/null +++ b/lib/WiiExtension/extensions/GuitarExtension.cpp @@ -0,0 +1,121 @@ +#include "ExtensionBase.h" +#include "GuitarExtension.h" + +#include "WiiExtension.h" + +void GuitarExtension::init(uint8_t dataType) { + ExtensionBase::init(dataType); + _guitarType = WII_GUITAR_UNSET; +} + +void GuitarExtension::process(uint8_t *inputData) { + // on first read, check the status of the guitar flag + if (_guitarType == WII_GUITAR_UNSET) { + if (((inputData[0] & 0x80) >> 7) == 0) { + _guitarType = WII_GUITAR_GHWT; + } else { + _guitarType = WII_GUITAR_GH3; + } + // force the data type to 1 when a World Tour guitar is detected + if ((_guitarType == WII_GUITAR_GHWT) && (getDataType() != WII_DATA_TYPE_1)) { + setDataType(WII_DATA_TYPE_1); + } + } + if (_guitarType != WII_GUITAR_UNSET) { + // as defined works for GH3 guitar + if (getDataType() == WII_DATA_TYPE_1) { + analogState[WiiAnalogs::WII_ANALOG_LEFT_X] = (inputData[0] & 0x3F); + analogState[WiiAnalogs::WII_ANALOG_LEFT_Y] = (inputData[1] & 0x3F); + + touchBar = ((_guitarType == WII_GUITAR_GHWT) ? (inputData[2] & 0x1F) : 0); + + whammyBar = (inputData[3] & 0x1F); + analogState[WiiAnalogs::WII_ANALOG_RIGHT_X] = (inputData[3] & 0x1F); + + directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN] = !((inputData[4] & 0x40) >> 6); + buttons[WiiButtons::WII_BUTTON_MINUS] = !((inputData[4] & 0x10) >> 4); + buttons[WiiButtons::WII_BUTTON_PLUS] = !((inputData[4] & 0x04) >> 2); + + buttons[GuitarButtons::GUITAR_ORANGE] = !((inputData[5] & 0x80) >> 7); + buttons[GuitarButtons::GUITAR_RED] = !((inputData[5] & 0x40) >> 6); + buttons[GuitarButtons::GUITAR_BLUE] = !((inputData[5] & 0x20) >> 5); + buttons[GuitarButtons::GUITAR_GREEN] = !((inputData[5] & 0x10) >> 4); + buttons[GuitarButtons::GUITAR_YELLOW] = !((inputData[5] & 0x08) >> 3); + buttons[GuitarButtons::GUITAR_PEDAL] = !((inputData[5] & 0x04) >> 2); + directionalPad[WiiDirectionalPad::WII_DIRECTION_UP] = !((inputData[5] & 0x01) >> 0); + + isTouched = (touchBar != WII_GUITAR_TOUCHPAD_NONE); + + // process the touch bar for button states + // touch only seems to exist in GHWT, and GHWT always reports data type 1 format regardless of setting + if (isTouched) { + // touched + buttons[GuitarButtons::GUITAR_GREEN] = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_GREEN,WII_GUITAR_TOUCHPAD_RED)); + buttons[GuitarButtons::GUITAR_RED] = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_RED,WII_GUITAR_TOUCHPAD_YELLOW)); + buttons[GuitarButtons::GUITAR_YELLOW] = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_YELLOW,WII_GUITAR_TOUCHPAD_BLUE)); + buttons[GuitarButtons::GUITAR_BLUE] = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_BLUE,WII_GUITAR_TOUCHPAD_ORANGE)); + buttons[GuitarButtons::GUITAR_ORANGE] = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_ORANGE,WII_GUITAR_TOUCHPAD_MAX)); + directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN] = isTouched; + } + } else if (getDataType() == WII_DATA_TYPE_2) { + analogState[WiiAnalogs::WII_ANALOG_LEFT_X] = ((inputData[0] << 2) | ((inputData[4] & 0x03) >> 0)); + analogState[WiiAnalogs::WII_ANALOG_LEFT_Y] = ((inputData[2] << 2) | ((inputData[4] & 0x30) >> 4)); + + touchBar = 0; + + whammyBar = (inputData[6] & 0xFF); + analogState[WiiAnalogs::WII_ANALOG_RIGHT_X] = (inputData[6] & 0xFF); + + directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN] = !((inputData[7] & 0x40) >> 6); + buttons[WiiButtons::WII_BUTTON_MINUS] = !((inputData[7] & 0x10) >> 4); + buttons[WiiButtons::WII_BUTTON_PLUS] = !((inputData[7] & 0x04) >> 2); + + buttons[GuitarButtons::GUITAR_ORANGE] = !((inputData[8] & 0x80) >> 7); + buttons[GuitarButtons::GUITAR_RED] = !((inputData[8] & 0x40) >> 6); + buttons[GuitarButtons::GUITAR_BLUE] = !((inputData[8] & 0x20) >> 5); + buttons[GuitarButtons::GUITAR_GREEN] = !((inputData[8] & 0x10) >> 4); + buttons[GuitarButtons::GUITAR_YELLOW] = !((inputData[8] & 0x08) >> 3); + buttons[GuitarButtons::GUITAR_PEDAL] = !((inputData[8] & 0x04) >> 2); + directionalPad[WiiDirectionalPad::WII_DIRECTION_UP] = !((inputData[8] & 0x01) >> 0); + } else if (getDataType() == WII_DATA_TYPE_3) { + analogState[WiiAnalogs::WII_ANALOG_LEFT_X] = (inputData[0] & 0xFF); + analogState[WiiAnalogs::WII_ANALOG_LEFT_Y] = (inputData[2] & 0xFF); + + touchBar = 0; + + whammyBar = (inputData[5] & 0xFF); + analogState[WiiAnalogs::WII_ANALOG_RIGHT_X] = (inputData[5] & 0xFF); + + directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN] = !((inputData[6] & 0x40) >> 6); + buttons[WiiButtons::WII_BUTTON_MINUS] = !((inputData[6] & 0x10) >> 4); + buttons[WiiButtons::WII_BUTTON_PLUS] = !((inputData[6] & 0x04) >> 2); + + buttons[GuitarButtons::GUITAR_ORANGE] = !((inputData[7] & 0x80) >> 7); + buttons[GuitarButtons::GUITAR_RED] = !((inputData[7] & 0x40) >> 6); + buttons[GuitarButtons::GUITAR_BLUE] = !((inputData[7] & 0x20) >> 5); + buttons[GuitarButtons::GUITAR_GREEN] = !((inputData[7] & 0x10) >> 4); + buttons[GuitarButtons::GUITAR_YELLOW] = !((inputData[7] & 0x08) >> 3); + buttons[GuitarButtons::GUITAR_PEDAL] = !((inputData[7] & 0x04) >> 2); + directionalPad[WiiDirectionalPad::WII_DIRECTION_UP] = !((inputData[7] & 0x01) >> 0); + } + } +#if WII_EXTENSION_DEBUG==true + //if (_lastRead[0] != regRead[0]) { + // printf("GH: %d Byte0 = %d\n", _guitarType, (regRead[0])); + //} + //if (_lastRead[1] != regRead[1]) { + // printf("GH: %d Byte1 = %d\n", _guitarType, (regRead[1])); + //} + //printf("Byte2 " BYTE_TO_BINARY_PATTERN " : " BYTE_TO_BINARY_PATTERN " = %02x\n", BYTE_TO_BINARY(regRead[2]), BYTE_TO_BINARY(regRead[2]^0x1F), regRead[2]); + //printf("G=%1d R=%1d Y=%1d B=%1d O=%1d Touched=%1d\n", fretGreen, fretRed, fretYellow, fretBlue, fretOrange, isTouched); + //} +// for (int i = 0; i < result; ++i) { +// if ((i != 0) && (i != 1) && (i != 3)) { +// if (_lastRead[i] != regRead[i]) printf("Byte%2d " BYTE_TO_BINARY_PATTERN "\n", i, BYTE_TO_BINARY(regRead[i])); +// } +// } +// printf("Joy1 X=%4d Y=%4d Whammy=%4d U=%1d D=%1d -=%1d +=%1d\n", joy1X, joy1Y, whammyBar, directionUp, directionDown, buttonMinus, buttonPlus); +// printf("Joy1 X=%4d Y=%4d Whammy=%4d U=%1d D=%1d -=%1d +=%1d\n", joy1X, joy1Y, whammyBar, directionUp, directionDown, buttonMinus, buttonPlus); +// printf("O=%1d B=%1d Y=%1d R=%1d G=%1d\n", buttons[GuitarButtons::GUITAR_ORANGE], buttons[GuitarButtons::GUITAR_BLUE], buttons[GuitarButtons::GUITAR_YELLOW], buttons[GuitarButtons::GUITAR_RED], buttons[GuitarButtons::GUITAR_GREEN]); +#endif +} \ No newline at end of file diff --git a/lib/WiiExtension/extensions/GuitarExtension.h b/lib/WiiExtension/extensions/GuitarExtension.h new file mode 100644 index 000000000..12d95e367 --- /dev/null +++ b/lib/WiiExtension/extensions/GuitarExtension.h @@ -0,0 +1,26 @@ +#ifndef _GUITAREXTENSION_H_ +#define _GUITAREXTENSION_H_ + +#include "ExtensionBase.h" + +enum GuitarButtons { + GUITAR_ORANGE = WiiButtons::WII_BUTTON_ZL, + GUITAR_RED = WiiButtons::WII_BUTTON_B, + GUITAR_BLUE = WiiButtons::WII_BUTTON_Y, + GUITAR_GREEN = WiiButtons::WII_BUTTON_A, + GUITAR_YELLOW = WiiButtons::WII_BUTTON_X, + GUITAR_PEDAL = WiiButtons::WII_BUTTON_ZR +}; + +class GuitarExtension : public ExtensionBase { + public: + void init(uint8_t dataType) override; + void process(uint8_t *inputData) override; + private: + uint8_t _guitarType = 0; + uint16_t whammyBar = 0; + int8_t touchBar = 0; + bool isTouched = 0; +}; + +#endif \ No newline at end of file diff --git a/lib/WiiExtension/extensions/NunchuckExtension.cpp b/lib/WiiExtension/extensions/NunchuckExtension.cpp new file mode 100644 index 000000000..0899224f8 --- /dev/null +++ b/lib/WiiExtension/extensions/NunchuckExtension.cpp @@ -0,0 +1,76 @@ +#include "ExtensionBase.h" +#include "NunchuckExtension.h" + +#include "WiiExtension.h" + +void NunchuckExtension::init(uint8_t dataType) { + ExtensionBase::init(dataType); + _extensionType = WII_EXTENSION_NUNCHUCK; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].destination = WII_ANALOG_PRECISION_3; + +#if WII_EXTENSION_CALIBRATION==false + // preseed calibration data with max ranges + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].minimum = 35; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].center = 128; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].maximum = 228; + + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].minimum = 27; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].center = 128; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].maximum = 220; +#endif +} + +void NunchuckExtension::calibrate(uint8_t *calibrationData) { +#if WII_EXTENSION_CALIBRATION==true +// _maxX1 = idRead[8]; +// _minX1 = idRead[9]; +// _cenX1 = idRead[10]; +// +// _maxY1 = idRead[11]; +// _minY1 = idRead[12]; +// _cenY1 = idRead[13]; +// +// _accelX0G = ((idRead[0] << 2) | ((idRead[3] >> 2) & 0x03)); +// _accelY0G = ((idRead[1] << 2) | ((idRead[3] >> 4) & 0x03)); +// _accelZ0G = ((idRead[2] << 2) | ((idRead[3] >> 6) & 0x03)); +// +// _accelX1G = ((idRead[4] << 2) | ((idRead[7] >> 2) & 0x03)); +// _accelY1G = ((idRead[5] << 2) | ((idRead[7] >> 4) & 0x03)); +// _accelZ1G = ((idRead[6] << 2) | ((idRead[7] >> 6) & 0x03)); + +#if WII_EXTENSION_DEBUG==true + //printf("Calibration:\n"); + //printf("X0G: %d\n", _accelX0G); + //printf("Y0G: %d\n", _accelY0G); + //printf("Z0G: %d\n", _accelZ0G); + //printf("X1G: %d\n", _accelX1G); + //printf("Y1G: %d\n", _accelY1G); + //printf("YZG: %d\n", _accelZ1G); + //printf("X Min: %d\n", _minX); + //printf("X Max: %d\n", _maxX); + //printf("X Center: %d\n", _cenX); + //printf("Y Min: %d\n", _minY); + //printf("Y Max: %d\n", _maxY); + //printf("Y Center: %d\n", _cenY); +#endif +#endif +} + +void NunchuckExtension::process(uint8_t *inputData) { + analogState[WiiAnalogs::WII_ANALOG_LEFT_X] = (inputData[0] & 0xFF); + analogState[WiiAnalogs::WII_ANALOG_LEFT_Y] = (inputData[1] & 0xFF); + + buttons[WiiButtons::WII_BUTTON_Z] = (!(inputData[5] & 0x01)); + buttons[WiiButtons::WII_BUTTON_C] = (!(inputData[5] & 0x02)); + + motionState[WiiMotions::WII_MOTION_X] = (((inputData[2] << 2) | ((inputData[5] >> 2) & 0x03))); + motionState[WiiMotions::WII_MOTION_Y] = (((inputData[3] << 2) | ((inputData[5] >> 4) & 0x03))); + motionState[WiiMotions::WII_MOTION_Z] = (((inputData[4] << 2) | ((inputData[5] >> 6) & 0x03))); + +#if WII_EXTENSION_DEBUG==true + printf("Joy X=%4d Y=%4d Acc X=%4d Y=%4d Z=%4d Btn Z=%1d C=%1d\n", analogState[WiiAnalogs::ANALOG_LEFT_X], analogState[WiiAnalogs::ANALOG_LEFT_Y], motionState[WiiMotions::MOTION_X], motionState[WiiMotions::MOTION_Y], motionState[WiiMotions::MOTION_Z], buttons[WiiButtons::BUTTON_Z], buttons[WiiButtons::BUTTON_C]); +#endif +} \ No newline at end of file diff --git a/lib/WiiExtension/extensions/NunchuckExtension.h b/lib/WiiExtension/extensions/NunchuckExtension.h new file mode 100644 index 000000000..c62432160 --- /dev/null +++ b/lib/WiiExtension/extensions/NunchuckExtension.h @@ -0,0 +1,13 @@ +#ifndef _NUNCHUCKEXTENSION_H_ +#define _NUNCHUCKEXTENSION_H_ + +#include "ExtensionBase.h" + +class NunchuckExtension : public ExtensionBase { + public: + void init(uint8_t dataType) override; + void calibrate(uint8_t *calibrationData) override; + void process(uint8_t *inputData) override; +}; + +#endif \ No newline at end of file diff --git a/lib/WiiExtension/extensions/TaikoExtension.cpp b/lib/WiiExtension/extensions/TaikoExtension.cpp new file mode 100644 index 000000000..2d2cea55d --- /dev/null +++ b/lib/WiiExtension/extensions/TaikoExtension.cpp @@ -0,0 +1,37 @@ +#include "ExtensionBase.h" +#include "TaikoExtension.h" + +#include "WiiExtension.h" + +void TaikoExtension::process(uint8_t *inputData) { + if (getDataType() == WII_DATA_TYPE_1) { + buttons[TaikoButtons::TATA_DON_LEFT] = !((inputData[5] & 0x40) >> 6); + buttons[TaikoButtons::TATA_KAT_LEFT] = !((inputData[5] & 0x20) >> 5); + buttons[TaikoButtons::TATA_DON_RIGHT] = !((inputData[5] & 0x10) >> 4); + buttons[TaikoButtons::TATA_KAT_RIGHT] = !((inputData[5] & 0x08) >> 3); + } else if (getDataType() == WII_DATA_TYPE_2) { + buttons[TaikoButtons::TATA_DON_LEFT] = !((inputData[8] & 0x40) >> 6); + buttons[TaikoButtons::TATA_KAT_LEFT] = !((inputData[8] & 0x20) >> 5); + buttons[TaikoButtons::TATA_DON_RIGHT] = !((inputData[8] & 0x10) >> 4); + buttons[TaikoButtons::TATA_KAT_RIGHT] = !((inputData[8] & 0x08) >> 3); + } else if (getDataType() == WII_DATA_TYPE_3) { + buttons[TaikoButtons::TATA_DON_LEFT] = !((inputData[7] & 0x40) >> 6); + buttons[TaikoButtons::TATA_KAT_LEFT] = !((inputData[7] & 0x20) >> 5); + buttons[TaikoButtons::TATA_DON_RIGHT] = !((inputData[7] & 0x10) >> 4); + buttons[TaikoButtons::TATA_KAT_RIGHT] = !((inputData[7] & 0x08) >> 3); + } + +#if WII_EXTENSION_DEBUG==true +// //if (_lastRead[0] != regRead[0]) printf("Byte0 " BYTE_TO_BINARY_PATTERN "\n", BYTE_TO_BINARY(regRead[0])); +// //if (_lastRead[1] != regRead[1]) printf("Byte1 " BYTE_TO_BINARY_PATTERN "\n", BYTE_TO_BINARY(regRead[1])); +// //if (_lastRead[2] != regRead[2]) printf("Byte2 " BYTE_TO_BINARY_PATTERN "\n", BYTE_TO_BINARY(regRead[2])); +// //if (_lastRead[3] != regRead[3]) printf("Byte3 " BYTE_TO_BINARY_PATTERN "\n", BYTE_TO_BINARY(regRead[3])); +// //if (_lastRead[4] != regRead[4]) printf("Byte4 " BYTE_TO_BINARY_PATTERN "\n", BYTE_TO_BINARY(regRead[4])); +// //if (_lastRead[5] != regRead[5]) printf("Byte5 " BYTE_TO_BINARY_PATTERN "\n", BYTE_TO_BINARY(regRead[5])); +// // +// if (_lastRead[7] != regRead[7]) { +// printf("DL=%1d RL=%1d DR=%1d RR=%1d\n", drumLeft, rimLeft, drumRight, rimRight); +// } + printf("DL=%1d RL=%1d DR=%1d RR=%1d\n", buttons[TaikoButtons::TATA_DON_LEFT], buttons[TaikoButtons::TATA_KAT_LEFT], buttons[TaikoButtons::TATA_DON_RIGHT], buttons[TaikoButtons::TATA_KAT_RIGHT]); +#endif +} \ No newline at end of file diff --git a/lib/WiiExtension/extensions/TaikoExtension.h b/lib/WiiExtension/extensions/TaikoExtension.h new file mode 100644 index 000000000..743ba659b --- /dev/null +++ b/lib/WiiExtension/extensions/TaikoExtension.h @@ -0,0 +1,18 @@ +#ifndef _TAIKOEXTENSION_H_ +#define _TAIKOEXTENSION_H_ + +#include "ExtensionBase.h" + +enum TaikoButtons { + TATA_DON_LEFT = WiiButtons::WII_BUTTON_B, + TATA_KAT_LEFT = WiiButtons::WII_BUTTON_Y, + TATA_DON_RIGHT = WiiButtons::WII_BUTTON_A, + TATA_KAT_RIGHT = WiiButtons::WII_BUTTON_X +}; + +class TaikoExtension : public ExtensionBase { + public: + void process(uint8_t *inputData) override; +}; + +#endif \ No newline at end of file diff --git a/lib/WiiExtension/extensions/TurntableExtension.cpp b/lib/WiiExtension/extensions/TurntableExtension.cpp new file mode 100644 index 000000000..ebeacd04c --- /dev/null +++ b/lib/WiiExtension/extensions/TurntableExtension.cpp @@ -0,0 +1,37 @@ +#include "ExtensionBase.h" +#include "TurntableExtension.h" + +#include "WiiExtension.h" + +void TurntableExtension::process(uint8_t *inputData) { + // turntable accepts three data modes, but will only report as type 1, much like GHWT + analogState[WiiAnalogs::WII_ANALOG_LEFT_X] = (inputData[0] & 0x3F); + analogState[WiiAnalogs::WII_ANALOG_LEFT_Y] = (inputData[1] & 0x3F); + + analogState[TurntableAnalogs::TURNTABLE_LEFT] = ((inputData[4] & 0x01) << 5) | ((inputData[3] & 0x1F) >> 0); + analogState[TurntableAnalogs::TURNTABLE_RIGHT] = ((inputData[0] & 0xC0) >> 3) | ((inputData[1] & 0xC0) >> 5) | ((inputData[2] & 0x80) >> 7); + analogState[TurntableAnalogs::TURNTABLE_EFFECTS] = (((inputData[2] & 0x60) >> 2) | ((inputData[3] & 0xE0) >> 5)); + analogState[TurntableAnalogs::TURNTABLE_CROSSFADE] = ((inputData[2] & 0x1E) >> 1); + + buttons[TurntableButtons::TURNTABLE_LEFT_RED] = !((inputData[4] & 0x20) >> 5); + buttons[WiiButtons::WII_BUTTON_MINUS] = !((inputData[4] & 0x10) >> 4); + buttons[WiiButtons::WII_BUTTON_PLUS] = !((inputData[4] & 0x04) >> 2); + buttons[TurntableButtons::TURNTABLE_RIGHT_RED] = !((inputData[4] & 0x02) >> 1); + + buttons[TurntableButtons::TURNTABLE_LEFT_BLUE] = !((inputData[5] & 0x80) >> 7); + buttons[TurntableButtons::TURNTABLE_RIGHT_GREEN] = !((inputData[5] & 0x20) >> 5); + buttons[TurntableButtons::TURNTABLE_EUPHORIA] = !((inputData[5] & 0x10) >> 4); + buttons[TurntableButtons::TURNTABLE_LEFT_GREEN] = !((inputData[5] & 0x08) >> 3); + buttons[TurntableButtons::TURNTABLE_RIGHT_BLUE] = !((inputData[5] & 0x04) >> 2); + +#if WII_EXTENSION_DEBUG==true + //printf("LR=%1d LG=%1d LB=%1d\n", buttons[TurntableButtons::TURNTABLE_LEFT_RED], buttons[TurntableButtons::TURNTABLE_LEFT_GREEN], buttons[TurntableButtons::TURNTABLE_LEFT_BLUE]); + //printf("RR=%1d RG=%1d RB=%1d\n", buttons[TurntableButtons::TURNTABLE_RIGHT_RED], buttons[TurntableButtons::TURNTABLE_RIGHT_GREEN], buttons[TurntableButtons::TURNTABLE_RIGHT_BLUE]); + //printf("-=%1d EU=%1d +=%1d\n", buttons[WiiButtons::BUTTON_MINUS], buttons[TurntableButtons::TURNTABLE_EUPHORIA], buttons[WiiButtons::BUTTON_PLUS]); + //printf("LTT=%4d RTT=%4d CF=%4d ED=%4d\n", analogState[TurntableAnalogs::TURNTABLE_LEFT], analogState[TurntableAnalogs::TURNTABLE_RIGHT], analogState[TurntableAnalogs::TURNTABLE_CROSSFADE], analogState[TurntableAnalogs::TURNTABLE_EFFECTS]); + + //for (int i = 0; i < result; ++i) { + // if (_lastRead[i] != inputData[i]) printf("Byte%2d " BYTE_TO_BINARY_PATTERN "\n", i, BYTE_TO_BINARY(inputData[i])); + //} +#endif +} \ No newline at end of file diff --git a/lib/WiiExtension/extensions/TurntableExtension.h b/lib/WiiExtension/extensions/TurntableExtension.h new file mode 100644 index 000000000..f9e56dc2d --- /dev/null +++ b/lib/WiiExtension/extensions/TurntableExtension.h @@ -0,0 +1,28 @@ +#ifndef _TURNTABLEEXTENSION_H_ +#define _TURNTABLEEXTENSION_H_ + +#include "ExtensionBase.h" + +enum TurntableButtons { + TURNTABLE_LEFT_RED = WiiButtons::WII_BUTTON_L, + TURNTABLE_RIGHT_RED = WiiButtons::WII_BUTTON_R, + TURNTABLE_LEFT_GREEN = WiiButtons::WII_BUTTON_X, + TURNTABLE_LEFT_BLUE = WiiButtons::WII_BUTTON_ZL, + TURNTABLE_RIGHT_GREEN = WiiButtons::WII_BUTTON_Y, + TURNTABLE_RIGHT_BLUE = WiiButtons::WII_BUTTON_ZR, + TURNTABLE_EUPHORIA = WiiButtons::WII_BUTTON_A +}; + +enum TurntableAnalogs { + TURNTABLE_LEFT = WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT, + TURNTABLE_RIGHT = WiiAnalogs::WII_ANALOG_RIGHT_X, + TURNTABLE_EFFECTS = WiiAnalogs::WII_ANALOG_TRIGGER_LEFT, + TURNTABLE_CROSSFADE = WiiAnalogs::WII_ANALOG_RIGHT_Y +}; + +class TurntableExtension : public ExtensionBase { + public: + void process(uint8_t *inputData) override; +}; + +#endif \ No newline at end of file diff --git a/src/addons/wiiext.cpp b/src/addons/wiiext.cpp index 46b681f22..63b33d5f8 100644 --- a/src/addons/wiiext.cpp +++ b/src/addons/wiiext.cpp @@ -36,72 +36,72 @@ void WiiExtensionInput::process() { wii->poll(); if (wii->extensionType == WII_EXTENSION_NUNCHUCK) { - buttonZ = wii->buttonZ; - buttonC = wii->buttonC; + buttonZ = wii->getController()->buttons[WiiButtons::WII_BUTTON_Z]; + buttonC = wii->getController()->buttons[WiiButtons::WII_BUTTON_C]; - leftX = map(wii->joy1X,0,1023,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - leftY = map(wii->joy1Y,1023,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,1023,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],1023,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); rightX = GAMEPAD_JOYSTICK_MID; rightY = GAMEPAD_JOYSTICK_MID; triggerLeft = 0; triggerRight = 0; } else if ((wii->extensionType == WII_EXTENSION_CLASSIC) || (wii->extensionType == WII_EXTENSION_CLASSIC_PRO)) { - buttonA = wii->buttonA; - buttonB = wii->buttonB; - buttonX = wii->buttonX; - buttonY = wii->buttonY; - buttonL = wii->buttonZL; - buttonZL = wii->buttonLT; - buttonR = wii->buttonZR; - buttonZR = wii->buttonRT; - dpadUp = wii->directionUp; - dpadDown = wii->directionDown; - dpadLeft = wii->directionLeft; - dpadRight = wii->directionRight; - buttonSelect = wii->buttonMinus; - buttonStart = wii->buttonPlus; - buttonHome = wii->buttonHome; + buttonA = wii->getController()->buttons[WiiButtons::WII_BUTTON_A]; + buttonB = wii->getController()->buttons[WiiButtons::WII_BUTTON_B]; + buttonX = wii->getController()->buttons[WiiButtons::WII_BUTTON_X]; + buttonY = wii->getController()->buttons[WiiButtons::WII_BUTTON_Y]; + buttonL = wii->getController()->buttons[WiiButtons::WII_BUTTON_L]; + buttonZL = wii->getController()->buttons[WiiButtons::WII_BUTTON_ZL]; + buttonR = wii->getController()->buttons[WiiButtons::WII_BUTTON_R]; + buttonZR = wii->getController()->buttons[WiiButtons::WII_BUTTON_ZR]; + dpadUp = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_UP]; + dpadDown = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN]; + dpadLeft = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_LEFT]; + dpadRight = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_RIGHT]; + buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; + buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; + buttonHome = wii->getController()->buttons[WiiButtons::WII_BUTTON_HOME]; if (wii->extensionType == WII_EXTENSION_CLASSIC) { - triggerLeft = wii->triggerLeft; - triggerRight = wii->triggerRight; + triggerLeft = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT]; + triggerRight = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT]; } - leftX = map(wii->joy1X,0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - leftY = map(wii->joy1Y,WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightX = map(wii->joy2X,0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightY = map(wii->joy2Y,WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); } else if (wii->extensionType == WII_EXTENSION_GUITAR) { - buttonSelect = wii->buttonMinus; - buttonStart = wii->buttonPlus; + buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; + buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; - dpadUp = wii->directionUp; - dpadDown = wii->directionDown; + dpadUp = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_UP]; + dpadDown = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN]; - buttonB = wii->fretGreen; - buttonA = wii->fretRed; - buttonX = wii->fretYellow; - buttonY = wii->fretBlue; - buttonL = wii->fretOrange; + buttonB = wii->getController()->buttons[GuitarButtons::GUITAR_GREEN]; + buttonA = wii->getController()->buttons[GuitarButtons::GUITAR_RED]; + buttonX = wii->getController()->buttons[GuitarButtons::GUITAR_YELLOW]; + buttonY = wii->getController()->buttons[GuitarButtons::GUITAR_BLUE]; + buttonL = wii->getController()->buttons[GuitarButtons::GUITAR_ORANGE]; // whammy currently maps to Joy2X in addition to the raw whammy value - whammyBar = wii->whammyBar; - buttonR = wii->pedalButton; + whammyBar = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_X]; + buttonR = wii->getController()->buttons[GuitarButtons::GUITAR_PEDAL]; - leftX = map(wii->joy1X,0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - leftY = map(wii->joy1Y,WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightX = map(wii->joy2X,0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); rightY = GAMEPAD_JOYSTICK_MID; triggerLeft = 0; triggerRight = 0; } else if (wii->extensionType == WII_EXTENSION_TAIKO) { - buttonL = wii->rimLeft; - buttonR = wii->rimRight; + buttonL = wii->getController()->buttons[TaikoButtons::TATA_KAT_LEFT]; + buttonR = wii->getController()->buttons[TaikoButtons::TATA_KAT_RIGHT]; - dpadLeft = wii->drumLeft; - buttonA = wii->drumRight; + dpadLeft = wii->getController()->buttons[TaikoButtons::TATA_DON_LEFT]; + buttonA = wii->getController()->buttons[TaikoButtons::TATA_DON_RIGHT]; } nextTimer = getMillis() + uIntervalMS; @@ -129,10 +129,10 @@ void WiiExtensionInput::process() { if (buttonB) gamepad->state.buttons |= GAMEPAD_MASK_B1; if (buttonX) gamepad->state.buttons |= GAMEPAD_MASK_B4; if (buttonY) gamepad->state.buttons |= GAMEPAD_MASK_B3; - if (buttonL) gamepad->state.buttons |= GAMEPAD_MASK_L1; - if (buttonZL) gamepad->state.buttons |= GAMEPAD_MASK_L2; - if (buttonR) gamepad->state.buttons |= GAMEPAD_MASK_R1; - if (buttonZR) gamepad->state.buttons |= GAMEPAD_MASK_R2; + if (buttonL) gamepad->state.buttons |= GAMEPAD_MASK_L2; + if (buttonZL) gamepad->state.buttons |= GAMEPAD_MASK_L1; + if (buttonR) gamepad->state.buttons |= GAMEPAD_MASK_R2; + if (buttonZR) gamepad->state.buttons |= GAMEPAD_MASK_R1; if (buttonSelect) gamepad->state.buttons |= GAMEPAD_MASK_S1; if (buttonStart) gamepad->state.buttons |= GAMEPAD_MASK_S2; if (buttonHome) gamepad->state.buttons |= GAMEPAD_MASK_A1; From 0f4df7fb1d60e6d12cdc46f508670279cc3e6689 Mon Sep 17 00:00:00 2001 From: Mike Parks Date: Thu, 25 May 2023 01:32:02 -0700 Subject: [PATCH 03/15] Added analog calibration. If controller does not have stored calibration, default is used (implemented per-controller). Verifies calibration checksum to determine which is used. --- lib/WiiExtension/WiiExtension.h | 6 +- .../extensions/ClassicExtension.cpp | 130 ++++++++---------- .../extensions/ClassicExtension.h | 6 +- lib/WiiExtension/extensions/ExtensionBase.cpp | 78 +++++++++-- lib/WiiExtension/extensions/ExtensionBase.h | 9 +- .../extensions/NunchuckExtension.cpp | 63 +++++---- .../extensions/NunchuckExtension.h | 5 +- 7 files changed, 179 insertions(+), 118 deletions(-) diff --git a/lib/WiiExtension/WiiExtension.h b/lib/WiiExtension/WiiExtension.h index 6b51c799d..b9467fe6d 100644 --- a/lib/WiiExtension/WiiExtension.h +++ b/lib/WiiExtension/WiiExtension.h @@ -67,12 +67,16 @@ #endif #ifndef WII_EXTENSION_CALIBRATION -#define WII_EXTENSION_CALIBRATION false +#define WII_EXTENSION_CALIBRATION true #endif #define WII_ALARM_NUM 0 #define WII_ALARM_IRQ TIMER_IRQ_0 +#define WII_CHECKSUM_MAGIC 0x55 +#define WII_CALIBRATION_SIZE 0x10 +#define WII_CALIBRATION_CHECKSUM_SIZE 0x02 + static volatile bool WiiExtension_alarmFired; #define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c" diff --git a/lib/WiiExtension/extensions/ClassicExtension.cpp b/lib/WiiExtension/extensions/ClassicExtension.cpp index 711c195ac..75b223709 100644 --- a/lib/WiiExtension/extensions/ClassicExtension.cpp +++ b/lib/WiiExtension/extensions/ClassicExtension.cpp @@ -10,6 +10,8 @@ void ClassicExtension::init(uint8_t dataType) { _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].destination = WII_ANALOG_PRECISION_3; _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].origin = WII_ANALOG_PRECISION_1; _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_X].origin = WII_ANALOG_PRECISION_0; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_X].destination = WII_ANALOG_PRECISION_3; _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].origin = WII_ANALOG_PRECISION_0; _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].destination = WII_ANALOG_PRECISION_3; @@ -18,10 +20,8 @@ void ClassicExtension::init(uint8_t dataType) { _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_0; _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_LEFT].origin = WII_ANALOG_PRECISION_1; - _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_LEFT].destination = WII_ANALOG_PRECISION_3; - _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_RIGHT].origin = WII_ANALOG_PRECISION_1; - _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_RIGHT].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination = WII_ANALOG_PRECISION_3; } else if (getDataType() == WII_DATA_TYPE_2) { _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].origin = WII_ANALOG_PRECISION_3; _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].destination = WII_ANALOG_PRECISION_3; @@ -37,10 +37,8 @@ void ClassicExtension::init(uint8_t dataType) { _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_LEFT].origin = WII_ANALOG_PRECISION_1; - _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_LEFT].destination = WII_ANALOG_PRECISION_3; - _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_RIGHT].origin = WII_ANALOG_PRECISION_1; - _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_RIGHT].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination = WII_ANALOG_PRECISION_3; } else if (getDataType() == WII_DATA_TYPE_3) { _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].origin = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].destination = WII_ANALOG_PRECISION_3; @@ -56,82 +54,68 @@ void ClassicExtension::init(uint8_t dataType) { _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_LEFT].origin = WII_ANALOG_PRECISION_1; - _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_LEFT].destination = WII_ANALOG_PRECISION_3; - _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_RIGHT].origin = WII_ANALOG_PRECISION_1; - _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION_RIGHT].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination = WII_ANALOG_PRECISION_3; } -#if WII_EXTENSION_CALIBRATION==false // preseed calibration data with max ranges - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].minimum = 7; - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].center = 31; - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].maximum = 57; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].minimum = WII_CLASSIC_GATE_CENTER-WII_CLASSIC_GATE_SIZE; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].center = WII_CLASSIC_GATE_CENTER; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].maximum = WII_CLASSIC_GATE_CENTER+WII_CLASSIC_GATE_SIZE; - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].minimum = 7; - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].center = 32; - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].maximum = 57; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].minimum = WII_CLASSIC_GATE_CENTER-WII_CLASSIC_GATE_SIZE; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].center = WII_CLASSIC_GATE_CENTER; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].maximum = WII_CLASSIC_GATE_CENTER+WII_CLASSIC_GATE_SIZE; - _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].minimum = 4; - _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].center = 16; - _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].maximum = 29; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].minimum = WII_CLASSIC_GATE_CENTER-WII_CLASSIC_GATE_SIZE; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].center = WII_CLASSIC_GATE_CENTER; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].maximum = WII_CLASSIC_GATE_CENTER+WII_CLASSIC_GATE_SIZE; - _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].minimum = 2; - _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].center = 15; - _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].maximum = 27; -#endif + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].minimum = WII_CLASSIC_GATE_CENTER-WII_CLASSIC_GATE_SIZE; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].center = WII_CLASSIC_GATE_CENTER; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].maximum = WII_CLASSIC_GATE_CENTER+WII_CLASSIC_GATE_SIZE; + + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].minimum = 1; + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].center = WII_CLASSIC_TRIGGER_MAX/2; + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].maximum = WII_CLASSIC_TRIGGER_MAX; + + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].minimum = 1; + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].center = WII_CLASSIC_TRIGGER_MAX/2; + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].maximum = WII_CLASSIC_TRIGGER_MAX; } -void ClassicExtension::calibrate(uint8_t *calibrationData) { +bool ClassicExtension::calibrate(uint8_t *calibrationData) { #if WII_EXTENSION_CALIBRATION==true - if (getDataType() == WII_DATA_TYPE_1) { - _calibrationPrecision1From = WII_ANALOG_PRECISION_2; - _calibrationPrecision1To = WII_ANALOG_PRECISION_1; - _calibrationPrecision2From = WII_ANALOG_PRECISION_2; - _calibrationPrecision2To = WII_ANALOG_PRECISION_0; - } else if (getDataType() == WII_DATA_TYPE_2) { - _calibrationPrecision1From = WII_ANALOG_PRECISION_2; - _calibrationPrecision1To = WII_ANALOG_PRECISION_3; - _calibrationPrecision2From = WII_ANALOG_PRECISION_2; - _calibrationPrecision2To = WII_ANALOG_PRECISION_3; - } else if (getDataType() == WII_DATA_TYPE_3) { - _calibrationPrecision1From = WII_ANALOG_PRECISION_2; - _calibrationPrecision1To = WII_ANALOG_PRECISION_2; - _calibrationPrecision2From = WII_ANALOG_PRECISION_2; - _calibrationPrecision2To = WII_ANALOG_PRECISION_2; + if (ExtensionBase::calibrate(calibrationData)) { + // calibration passed checksum + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].maximum = calibrationData[0]; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].minimum = calibrationData[1]; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].center = calibrationData[2]; + + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].maximum = calibrationData[3]; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].minimum = calibrationData[4]; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].center = calibrationData[5]; + + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].maximum = calibrationData[6]; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].minimum = calibrationData[7]; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].center = calibrationData[8]; + + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].maximum = calibrationData[9]; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].minimum = calibrationData[10]; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].center = calibrationData[11]; + + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].maximum = calibrationData[12]; + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].minimum = 1; + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].center = calibrationData[12]/2; + + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].maximum = calibrationData[13]; + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].minimum = 1; + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].center = calibrationData[12]/2; + return true; + } else { } - - _maxX1 = map(idRead[0],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); - _minX1 = map(idRead[1],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); - _cenX1 = map(idRead[2],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); - - _maxY1 = map(idRead[3],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); - _minY1 = map(idRead[4],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); - _cenY1 = map(idRead[5],0,(_calibrationPrecision1From-1),0,(_calibrationPrecision1To-1)); - - _maxX2 = map(idRead[6],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); - _minX2 = map(idRead[7],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); - _cenX2 = map(idRead[8],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); - - _maxY2 = map(idRead[9],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); - _minY2 = map(idRead[10],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); - _cenY2 = map(idRead[11],0,(_calibrationPrecision2From-1),0,(_calibrationPrecision2To-1)); - -#if WII_EXTENSION_DEBUG==true -// printf("X1 Min: %d\n", _minX1); -// printf("X1 Max: %d\n", _maxX1); -// printf("X1 Center: %d\n", _cenX1); -// printf("Y1 Min: %d\n", _minY1); -// printf("Y1 Max: %d\n", _maxY1); -// printf("Y1 Center: %d\n", _cenY1); -// printf("X2 Min: %d\n", _minX2); -// printf("X2 Max: %d\n", _maxX2); -// printf("X2 Center: %d\n", _cenX2); -// printf("Y2 Min: %d\n", _minY2); -// printf("Y2 Max: %d\n", _maxY2); -// printf("Y2 Center: %d\n", _cenY2); -#endif #endif + return false; } void ClassicExtension::process(uint8_t *inputData) { diff --git a/lib/WiiExtension/extensions/ClassicExtension.h b/lib/WiiExtension/extensions/ClassicExtension.h index c2d98edcb..343135604 100644 --- a/lib/WiiExtension/extensions/ClassicExtension.h +++ b/lib/WiiExtension/extensions/ClassicExtension.h @@ -3,10 +3,14 @@ #include "ExtensionBase.h" +#define WII_CLASSIC_GATE_SIZE 97 +#define WII_CLASSIC_GATE_CENTER 128 +#define WII_CLASSIC_TRIGGER_MAX 32 + class ClassicExtension : public ExtensionBase { public: void init(uint8_t dataType) override; - void calibrate(uint8_t *calibrationData) override; + bool calibrate(uint8_t *calibrationData) override; void process(uint8_t *inputData) override; }; diff --git a/lib/WiiExtension/extensions/ExtensionBase.cpp b/lib/WiiExtension/extensions/ExtensionBase.cpp index 9d2c857ee..48ea047d6 100644 --- a/lib/WiiExtension/extensions/ExtensionBase.cpp +++ b/lib/WiiExtension/extensions/ExtensionBase.cpp @@ -9,39 +9,91 @@ void ExtensionBase::init(uint8_t dataType) { uint8_t i; setDataType(dataType); + isFirstRead = true; for (i = 0; i < WiiDirectionalPad::WII_MAX_DIRECTIONS; ++i) directionalPad[i] = 0; for (i = 0; i < WiiButtons::WII_MAX_BUTTONS; ++i) buttons[i] = 0; for (i = 0; i < WiiMotions::WII_MAX_MOTIONS; ++i) motionState[i] = 0; for (i = 0; i < WiiAnalogs::WII_MAX_ANALOGS; ++i) { analogState[i] = 0; + initialAnalogState[i] = 0; _analogCalibration[i].minimum = 0; _analogCalibration[i].center = 0; - _analogCalibration[i].maximum = 0; + _analogCalibration[i].maximum = 1; } } -void ExtensionBase::calibrate(uint8_t *calibrationData) { +bool ExtensionBase::calibrate(uint8_t *calibrationData) { + // stores calibration data. no calculation is done here. + uint8_t checksum = WII_CHECKSUM_MAGIC; + uint8_t i = 0; + uint8_t checksumBytes[2] = {0x00,0x00}; + +#if WII_EXTENSION_DEBUG==true + for (int i = 0; i < 16; ++i) { + printf("%02x", calibrationData[i]); + } + printf("\n"); +#endif + + for (i = 0; i < (WII_CALIBRATION_SIZE-WII_CALIBRATION_CHECKSUM_SIZE); ++i) { + checksum += calibrationData[i]; + checksumBytes[0] = checksum; + } + checksum += WII_CHECKSUM_MAGIC; + checksumBytes[1] = checksum; + +#if WII_EXTENSION_DEBUG==true + printf("Checksum: %02x%02x=%02x%02x\n", checksumBytes[0], checksumBytes[1], calibrationData[WII_CALIBRATION_SIZE-2], calibrationData[WII_CALIBRATION_SIZE-1]); +#endif + + // if the checksum passes, the extensions can use the data. if not, it will fall back to the extension defaults + return ((checksumBytes[0] == calibrationData[WII_CALIBRATION_SIZE-2]) && (checksumBytes[1] == calibrationData[WII_CALIBRATION_SIZE-1])); } void ExtensionBase::postProcess() { uint8_t i; + uint16_t minVal, maxVal, cenVal, outVal; + int16_t centerOffset = 0; + for (i = 0; i < WiiAnalogs::WII_MAX_ANALOGS; ++i) { - analogState[i] = map( - applyCalibration(analogState[i], _analogCalibration[i].minimum, _analogCalibration[i].maximum, _analogCalibration[i].center), - 0+_analogCalibration[i].minimum, - (_analogPrecision[i].origin-_analogCalibration[i].maximum), - 0, - (_analogPrecision[i].destination-1) - ); + // scale calibration values before using + if (i != WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION) { + minVal = map(_analogCalibration[i].minimum-1, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin-1, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination-1); + maxVal = map(_analogCalibration[i].maximum-1, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin-1, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination-1); + cenVal = map(_analogCalibration[i].center-1, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin-1, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination-1); + + if (isFirstRead) { + // stash the first read as the initial orientation. will reset on hotswap. + initialAnalogState[i] = map(analogState[i], 0, (_analogPrecision[i].origin-1), 0,(_analogPrecision[i].destination-1)); + } + + centerOffset = cenVal-initialAnalogState[i]; + + outVal = map(analogState[i], 0, (_analogPrecision[i].origin-1), 0,(_analogPrecision[i].destination-1)); + if ((i != WiiAnalogs::WII_ANALOG_TRIGGER_LEFT) && (i != WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT)) { + outVal = map(outVal+centerOffset, minVal+centerOffset, maxVal+centerOffset, 0, (_analogPrecision[i].destination-1)); + } + outVal = bounds(outVal, 0, (_analogPrecision[i].destination-1)); + + //applyCalibration(analogState[i], minVal, maxVal, cenVal), + +#if WII_EXTENSION_DEBUG==true + printf("cur:%5d min=%5d:%5d max=%5d:%5d cen=%5d:%5d out:%5d off:%5d\n", analogState[i], _analogCalibration[i].minimum, minVal, _analogCalibration[i].maximum, maxVal, _analogCalibration[i].center, cenVal, outVal, centerOffset); +#endif + + analogState[i] = outVal; + } } + if (isFirstRead) isFirstRead = false; + #if WII_EXTENSION_DEBUG==true //if ((_lastRead[0] != regRead[0]) || (_lastRead[1] != regRead[1]) || (_lastRead[2] != regRead[2]) || (_lastRead[3] != regRead[3])) { // printf("Joy1 X=%4d Y=%4d Joy2 X=%4d Y=%4d\n", joy1X, joy1Y, joy2X, joy2Y); //} - //printf("C Joy1 X=%5d Y=%5d Joy2 X=%5d Y=%5d U=%1d D=%1d L=%1d R=%1d TL=%5d TR=%4d\n", analogState[WiiAnalogs::ANALOG_LEFT_X], analogState[WiiAnalogs::ANALOG_LEFT_Y], analogState[WiiAnalogs::ANALOG_RIGHT_X], analogState[WiiAnalogs::ANALOG_RIGHT_Y], directionalPad[WiiDirectionalPad::DIRECTION_UP], directionalPad[WiiDirectionalPad::DIRECTION_DOWN], directionalPad[WiiDirectionalPad::DIRECTION_LEFT], directionalPad[WiiDirectionalPad::DIRECTION_RIGHT], analogState[WiiAnalogs::ANALOG_TRIGGER_LEFT], analogState[WiiAnalogs::ANALOG_TRIGGER_RIGHT]); + //printf("C Joy1 X=%5d Y=%5d Joy2 X=%5d Y=%5d U=%1d D=%1d L=%1d R=%1d TL=%5d TR=%4d\n", analogState[WiiAnalogs::WII_ANALOG_LEFT_X], analogState[WiiAnalogs::WII_ANALOG_LEFT_Y], analogState[WiiAnalogs::WII_ANALOG_RIGHT_X], analogState[WiiAnalogs::WII_ANALOG_RIGHT_Y], directionalPad[WiiDirectionalPad::WII_DIRECTION_UP], directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN], directionalPad[WiiDirectionalPad::WII_DIRECTION_LEFT], directionalPad[WiiDirectionalPad::WII_DIRECTION_RIGHT], analogState[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT], analogState[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT]); //printf("A=%1d B=%1d X=%1d Y=%1d ZL=%1d ZR=%1d LT=%1d RT=%1d -=%1d H=%1d +=%1d\n", buttonA, buttonB, buttonX, buttonY, buttonZL, buttonZR, buttonLT, buttonRT, buttonMinus, buttonHome, buttonPlus); #endif } @@ -58,6 +110,12 @@ uint16_t ExtensionBase::map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16 return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } +uint16_t ExtensionBase::bounds(uint16_t x, uint16_t out_min, uint16_t out_max) { + if (x > out_max) x = out_max; + if (x < out_min) x = out_min; + return x; +} + uint16_t ExtensionBase::applyCalibration(uint16_t pos, uint16_t min, uint16_t max, uint16_t cen) { uint16_t result; diff --git a/lib/WiiExtension/extensions/ExtensionBase.h b/lib/WiiExtension/extensions/ExtensionBase.h index 7d3f45aac..06a557b3f 100644 --- a/lib/WiiExtension/extensions/ExtensionBase.h +++ b/lib/WiiExtension/extensions/ExtensionBase.h @@ -36,8 +36,7 @@ enum WiiAnalogs { WII_ANALOG_RIGHT_Y, WII_ANALOG_TRIGGER_LEFT, WII_ANALOG_TRIGGER_RIGHT, - WII_ANALOG_CALIBRATION_PRECISION_LEFT, - WII_ANALOG_CALIBRATION_PRECISION_RIGHT, + WII_ANALOG_CALIBRATION_PRECISION, WII_MAX_ANALOGS }; @@ -68,6 +67,7 @@ class ExtensionBase { WiiAnalogCalibration _analogCalibration[WiiAnalogs::WII_MAX_ANALOGS]; private: uint16_t map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max); + uint16_t bounds(uint16_t x, uint16_t out_min, uint16_t out_max); uint16_t applyCalibration(uint16_t pos, uint16_t min, uint16_t max, uint16_t cen); public: bool directionalPad[WiiDirectionalPad::WII_MAX_DIRECTIONS]; @@ -75,8 +75,11 @@ class ExtensionBase { uint16_t analogState[WiiAnalogs::WII_MAX_ANALOGS]; uint16_t motionState[WiiMotions::WII_MAX_MOTIONS]; + uint16_t initialAnalogState[WiiAnalogs::WII_MAX_ANALOGS]; + bool isFirstRead = true; + virtual void init(uint8_t dataType); - virtual void calibrate(uint8_t *calibrationData); + virtual bool calibrate(uint8_t *calibrationData); virtual void process(uint8_t *inputData); virtual void postProcess(); diff --git a/lib/WiiExtension/extensions/NunchuckExtension.cpp b/lib/WiiExtension/extensions/NunchuckExtension.cpp index 0899224f8..0666646f9 100644 --- a/lib/WiiExtension/extensions/NunchuckExtension.cpp +++ b/lib/WiiExtension/extensions/NunchuckExtension.cpp @@ -6,40 +6,44 @@ void NunchuckExtension::init(uint8_t dataType) { ExtensionBase::init(dataType); _extensionType = WII_EXTENSION_NUNCHUCK; - _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].origin = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].destination = WII_ANALOG_PRECISION_3; - _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].origin = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].destination = WII_ANALOG_PRECISION_3; -#if WII_EXTENSION_CALIBRATION==false // preseed calibration data with max ranges - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].minimum = 35; - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].center = 128; - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].maximum = 228; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].minimum = WII_NUNCHUCK_GATE_CENTER-WII_NUNCHUCK_GATE_SIZE; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].center = WII_NUNCHUCK_GATE_CENTER; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].maximum = WII_NUNCHUCK_GATE_CENTER+WII_NUNCHUCK_GATE_SIZE; - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].minimum = 27; - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].center = 128; - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].maximum = 220; -#endif + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].minimum = WII_NUNCHUCK_GATE_CENTER-WII_NUNCHUCK_GATE_SIZE; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].center = WII_NUNCHUCK_GATE_CENTER; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].maximum = WII_NUNCHUCK_GATE_CENTER+WII_NUNCHUCK_GATE_SIZE; + + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination = WII_ANALOG_PRECISION_3; } -void NunchuckExtension::calibrate(uint8_t *calibrationData) { +bool NunchuckExtension::calibrate(uint8_t *calibrationData) { #if WII_EXTENSION_CALIBRATION==true -// _maxX1 = idRead[8]; -// _minX1 = idRead[9]; -// _cenX1 = idRead[10]; -// -// _maxY1 = idRead[11]; -// _minY1 = idRead[12]; -// _cenY1 = idRead[13]; -// -// _accelX0G = ((idRead[0] << 2) | ((idRead[3] >> 2) & 0x03)); -// _accelY0G = ((idRead[1] << 2) | ((idRead[3] >> 4) & 0x03)); -// _accelZ0G = ((idRead[2] << 2) | ((idRead[3] >> 6) & 0x03)); -// -// _accelX1G = ((idRead[4] << 2) | ((idRead[7] >> 2) & 0x03)); -// _accelY1G = ((idRead[5] << 2) | ((idRead[7] >> 4) & 0x03)); -// _accelZ1G = ((idRead[6] << 2) | ((idRead[7] >> 6) & 0x03)); + if (ExtensionBase::calibrate(calibrationData)) { + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].maximum = calibrationData[8]; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].minimum = calibrationData[9]; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].center = calibrationData[10]; + + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].maximum = calibrationData[11]; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].minimum = calibrationData[12]; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].center = calibrationData[13]; + + //_accelX0G = ((idRead[0] << 2) | ((idRead[3] >> 2) & 0x03)); + //_accelY0G = ((idRead[1] << 2) | ((idRead[3] >> 4) & 0x03)); + //_accelZ0G = ((idRead[2] << 2) | ((idRead[3] >> 6) & 0x03)); + // + //_accelX1G = ((idRead[4] << 2) | ((idRead[7] >> 2) & 0x03)); + //_accelY1G = ((idRead[5] << 2) | ((idRead[7] >> 4) & 0x03)); + //_accelZ1G = ((idRead[6] << 2) | ((idRead[7] >> 6) & 0x03)); + return true; + } #if WII_EXTENSION_DEBUG==true //printf("Calibration:\n"); @@ -57,6 +61,7 @@ void NunchuckExtension::calibrate(uint8_t *calibrationData) { //printf("Y Center: %d\n", _cenY); #endif #endif + return false; } void NunchuckExtension::process(uint8_t *inputData) { @@ -71,6 +76,6 @@ void NunchuckExtension::process(uint8_t *inputData) { motionState[WiiMotions::WII_MOTION_Z] = (((inputData[4] << 2) | ((inputData[5] >> 6) & 0x03))); #if WII_EXTENSION_DEBUG==true - printf("Joy X=%4d Y=%4d Acc X=%4d Y=%4d Z=%4d Btn Z=%1d C=%1d\n", analogState[WiiAnalogs::ANALOG_LEFT_X], analogState[WiiAnalogs::ANALOG_LEFT_Y], motionState[WiiMotions::MOTION_X], motionState[WiiMotions::MOTION_Y], motionState[WiiMotions::MOTION_Z], buttons[WiiButtons::BUTTON_Z], buttons[WiiButtons::BUTTON_C]); + //printf("Joy X=%4d Y=%4d Acc X=%4d Y=%4d Z=%4d Btn Z=%1d C=%1d\n", analogState[WiiAnalogs::ANALOG_LEFT_X], analogState[WiiAnalogs::ANALOG_LEFT_Y], motionState[WiiMotions::MOTION_X], motionState[WiiMotions::MOTION_Y], motionState[WiiMotions::MOTION_Z], buttons[WiiButtons::BUTTON_Z], buttons[WiiButtons::BUTTON_C]); #endif } \ No newline at end of file diff --git a/lib/WiiExtension/extensions/NunchuckExtension.h b/lib/WiiExtension/extensions/NunchuckExtension.h index c62432160..78939e7a0 100644 --- a/lib/WiiExtension/extensions/NunchuckExtension.h +++ b/lib/WiiExtension/extensions/NunchuckExtension.h @@ -3,10 +3,13 @@ #include "ExtensionBase.h" +#define WII_NUNCHUCK_GATE_SIZE 96 +#define WII_NUNCHUCK_GATE_CENTER 128 + class NunchuckExtension : public ExtensionBase { public: void init(uint8_t dataType) override; - void calibrate(uint8_t *calibrationData) override; + bool calibrate(uint8_t *calibrationData) override; void process(uint8_t *inputData) override; }; From 60570f5813503e4c6500b00dfe2ad7add88006f9 Mon Sep 17 00:00:00 2001 From: Mike Parks Date: Fri, 26 May 2023 01:46:39 -0700 Subject: [PATCH 04/15] Adjust default analog calibration when data is unavailable on the device. Fixed issue where adjusted input values would overflow after calibration. --- headers/addons/wiiext.h | 1 + .../extensions/ClassicExtension.cpp | 30 +++---- .../extensions/ClassicExtension.h | 1 + lib/WiiExtension/extensions/ExtensionBase.cpp | 27 +++++- lib/WiiExtension/extensions/ExtensionBase.h | 12 +-- .../extensions/GuitarExtension.cpp | 82 ++++++++++++++++++- lib/WiiExtension/extensions/GuitarExtension.h | 5 ++ src/addons/wiiext.cpp | 14 +++- 8 files changed, 143 insertions(+), 29 deletions(-) diff --git a/headers/addons/wiiext.h b/headers/addons/wiiext.h index 823488f2e..a39b31bd7 100644 --- a/headers/addons/wiiext.h +++ b/headers/addons/wiiext.h @@ -80,6 +80,7 @@ class WiiExtensionInput : public GPAddon { uint16_t rightY = 0; uint16_t map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max); + uint16_t bounds(uint16_t x, uint16_t out_min, uint16_t out_max); }; #endif // _WIIExtensionAddon_H diff --git a/lib/WiiExtension/extensions/ClassicExtension.cpp b/lib/WiiExtension/extensions/ClassicExtension.cpp index 75b223709..060fe15bd 100644 --- a/lib/WiiExtension/extensions/ClassicExtension.cpp +++ b/lib/WiiExtension/extensions/ClassicExtension.cpp @@ -59,25 +59,25 @@ void ClassicExtension::init(uint8_t dataType) { } // preseed calibration data with max ranges - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].minimum = WII_CLASSIC_GATE_CENTER-WII_CLASSIC_GATE_SIZE; - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].center = WII_CLASSIC_GATE_CENTER; - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].maximum = WII_CLASSIC_GATE_CENTER+WII_CLASSIC_GATE_SIZE; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].minimum = WII_CLASSIC_ANALOG_GAP; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].center = WII_CLASSIC_GATE_CENTER; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].maximum = WII_CLASSIC_GATE_CENTER+WII_CLASSIC_GATE_SIZE; - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].minimum = WII_CLASSIC_GATE_CENTER-WII_CLASSIC_GATE_SIZE; - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].center = WII_CLASSIC_GATE_CENTER; - _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].maximum = WII_CLASSIC_GATE_CENTER+WII_CLASSIC_GATE_SIZE; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].minimum = WII_CLASSIC_ANALOG_GAP; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].center = WII_CLASSIC_GATE_CENTER; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].maximum = WII_CLASSIC_GATE_CENTER+WII_CLASSIC_GATE_SIZE; - _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].minimum = WII_CLASSIC_GATE_CENTER-WII_CLASSIC_GATE_SIZE; - _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].center = WII_CLASSIC_GATE_CENTER; - _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].maximum = WII_CLASSIC_GATE_CENTER+WII_CLASSIC_GATE_SIZE; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].minimum = WII_CLASSIC_ANALOG_GAP; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].center = WII_CLASSIC_GATE_CENTER; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].maximum = WII_CLASSIC_GATE_CENTER+WII_CLASSIC_GATE_SIZE; - _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].minimum = WII_CLASSIC_GATE_CENTER-WII_CLASSIC_GATE_SIZE; - _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].center = WII_CLASSIC_GATE_CENTER; - _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].maximum = WII_CLASSIC_GATE_CENTER+WII_CLASSIC_GATE_SIZE; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].minimum = WII_CLASSIC_ANALOG_GAP; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].center = WII_CLASSIC_GATE_CENTER; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].maximum = WII_CLASSIC_GATE_CENTER+WII_CLASSIC_GATE_SIZE; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].minimum = 1; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].center = WII_CLASSIC_TRIGGER_MAX/2; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].maximum = WII_CLASSIC_TRIGGER_MAX; + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].minimum = 1; + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].center = WII_CLASSIC_TRIGGER_MAX/2; + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].maximum = WII_CLASSIC_TRIGGER_MAX; _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].minimum = 1; _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].center = WII_CLASSIC_TRIGGER_MAX/2; diff --git a/lib/WiiExtension/extensions/ClassicExtension.h b/lib/WiiExtension/extensions/ClassicExtension.h index 343135604..c2b33f375 100644 --- a/lib/WiiExtension/extensions/ClassicExtension.h +++ b/lib/WiiExtension/extensions/ClassicExtension.h @@ -6,6 +6,7 @@ #define WII_CLASSIC_GATE_SIZE 97 #define WII_CLASSIC_GATE_CENTER 128 #define WII_CLASSIC_TRIGGER_MAX 32 +#define WII_CLASSIC_ANALOG_GAP (WII_CLASSIC_GATE_CENTER-WII_CLASSIC_GATE_SIZE) class ClassicExtension : public ExtensionBase { public: diff --git a/lib/WiiExtension/extensions/ExtensionBase.cpp b/lib/WiiExtension/extensions/ExtensionBase.cpp index 48ea047d6..6c4b69589 100644 --- a/lib/WiiExtension/extensions/ExtensionBase.cpp +++ b/lib/WiiExtension/extensions/ExtensionBase.cpp @@ -11,6 +11,7 @@ void ExtensionBase::init(uint8_t dataType) { setDataType(dataType); isFirstRead = true; + _hasCalibrationData = false; for (i = 0; i < WiiDirectionalPad::WII_MAX_DIRECTIONS; ++i) directionalPad[i] = 0; for (i = 0; i < WiiButtons::WII_MAX_BUTTONS; ++i) buttons[i] = 0; for (i = 0; i < WiiMotions::WII_MAX_MOTIONS; ++i) motionState[i] = 0; @@ -28,6 +29,7 @@ bool ExtensionBase::calibrate(uint8_t *calibrationData) { uint8_t checksum = WII_CHECKSUM_MAGIC; uint8_t i = 0; uint8_t checksumBytes[2] = {0x00,0x00}; + bool result = false; #if WII_EXTENSION_DEBUG==true for (int i = 0; i < 16; ++i) { @@ -48,7 +50,11 @@ bool ExtensionBase::calibrate(uint8_t *calibrationData) { #endif // if the checksum passes, the extensions can use the data. if not, it will fall back to the extension defaults - return ((checksumBytes[0] == calibrationData[WII_CALIBRATION_SIZE-2]) && (checksumBytes[1] == calibrationData[WII_CALIBRATION_SIZE-1])); + result = ((checksumBytes[0] == calibrationData[WII_CALIBRATION_SIZE-2]) && (checksumBytes[1] == calibrationData[WII_CALIBRATION_SIZE-1])); + + _hasCalibrationData = result; + + return result; } void ExtensionBase::postProcess() { @@ -67,20 +73,33 @@ void ExtensionBase::postProcess() { if (isFirstRead) { // stash the first read as the initial orientation. will reset on hotswap. initialAnalogState[i] = map(analogState[i], 0, (_analogPrecision[i].origin-1), 0,(_analogPrecision[i].destination-1)); + + if (!_hasCalibrationData) { + cenVal = initialAnalogState[i]; + } } - centerOffset = cenVal-initialAnalogState[i]; + if (_analogCalibration[i].useOffset) { + centerOffset = cenVal-initialAnalogState[i]; + } else { + centerOffset = 0; + } outVal = map(analogState[i], 0, (_analogPrecision[i].origin-1), 0,(_analogPrecision[i].destination-1)); if ((i != WiiAnalogs::WII_ANALOG_TRIGGER_LEFT) && (i != WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT)) { + outVal = bounds(outVal, minVal, maxVal); outVal = map(outVal+centerOffset, minVal+centerOffset, maxVal+centerOffset, 0, (_analogPrecision[i].destination-1)); + outVal = bounds(outVal, minVal, maxVal); + } else { + outVal = bounds(outVal, 0, (_analogPrecision[i].destination-1)); } - outVal = bounds(outVal, 0, (_analogPrecision[i].destination-1)); //applyCalibration(analogState[i], minVal, maxVal, cenVal), #if WII_EXTENSION_DEBUG==true - printf("cur:%5d min=%5d:%5d max=%5d:%5d cen=%5d:%5d out:%5d off:%5d\n", analogState[i], _analogCalibration[i].minimum, minVal, _analogCalibration[i].maximum, maxVal, _analogCalibration[i].center, cenVal, outVal, centerOffset); + if (i == WiiAnalogs::WII_ANALOG_RIGHT_X) { + //printf("cur:%5d min=%5d:%5d max=%5d:%5d cen=%5d:%5d out:%5d off:%5d\n", analogState[i], _analogCalibration[i].minimum, minVal, _analogCalibration[i].maximum, maxVal, _analogCalibration[i].center, cenVal, outVal, centerOffset); + } #endif analogState[i] = outVal; diff --git a/lib/WiiExtension/extensions/ExtensionBase.h b/lib/WiiExtension/extensions/ExtensionBase.h index 06a557b3f..01cfbf38b 100644 --- a/lib/WiiExtension/extensions/ExtensionBase.h +++ b/lib/WiiExtension/extensions/ExtensionBase.h @@ -48,20 +48,22 @@ enum WiiMotions { }; typedef struct { - uint16_t origin; - uint16_t destination; + uint16_t origin = 0; + uint16_t destination = 0; } WiiAnalogPrecision; typedef struct { - uint16_t minimum; - uint16_t center; - uint16_t maximum; + uint16_t minimum = 0; + uint16_t center = 0; + uint16_t maximum = 0; + bool useOffset = true; } WiiAnalogCalibration; class ExtensionBase { protected: uint8_t _dataType; uint8_t _extensionType; + bool _hasCalibrationData = false; WiiAnalogPrecision _analogPrecision[WiiAnalogs::WII_MAX_ANALOGS]; WiiAnalogCalibration _analogCalibration[WiiAnalogs::WII_MAX_ANALOGS]; diff --git a/lib/WiiExtension/extensions/GuitarExtension.cpp b/lib/WiiExtension/extensions/GuitarExtension.cpp index 4afd6259b..6ce501dbd 100644 --- a/lib/WiiExtension/extensions/GuitarExtension.cpp +++ b/lib/WiiExtension/extensions/GuitarExtension.cpp @@ -6,6 +6,78 @@ void GuitarExtension::init(uint8_t dataType) { ExtensionBase::init(dataType); _guitarType = WII_GUITAR_UNSET; + + if (getDataType() == WII_DATA_TYPE_1) { + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].origin = WII_ANALOG_PRECISION_1; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].origin = WII_ANALOG_PRECISION_1; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_X].origin = WII_ANALOG_PRECISION_0; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].origin = WII_ANALOG_PRECISION_0; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].destination = WII_ANALOG_PRECISION_3; + + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].origin = WII_ANALOG_PRECISION_0; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_0; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; + + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination = WII_ANALOG_PRECISION_3; + } else if (getDataType() == WII_DATA_TYPE_2) { + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].origin = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].origin = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_X].origin = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].origin = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].destination = WII_ANALOG_PRECISION_3; + + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; + + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination = WII_ANALOG_PRECISION_3; + } else if (getDataType() == WII_DATA_TYPE_3) { + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_X].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].destination = WII_ANALOG_PRECISION_3; + + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; + + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination = WII_ANALOG_PRECISION_3; + } + + // preseed calibration data with max ranges + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].minimum = WII_GUITAR_ANALOG_GAP; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].center = WII_GUITAR_GATE_CENTER; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].maximum = WII_GUITAR_GATE_CENTER+WII_GUITAR_GATE_SIZE; + + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].minimum = WII_GUITAR_ANALOG_GAP; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].center = WII_GUITAR_GATE_CENTER; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].maximum = WII_GUITAR_GATE_CENTER+WII_GUITAR_GATE_SIZE; + + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].minimum = 15; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].center = WII_GUITAR_GATE_CENTER; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].maximum = WII_GUITAR_GATE_CENTER*2; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].useOffset = false; + + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].minimum = 0; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].center = WII_GUITAR_GATE_CENTER; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].maximum = WII_GUITAR_GATE_CENTER*2; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].useOffset = false; } void GuitarExtension::process(uint8_t *inputData) { @@ -19,6 +91,14 @@ void GuitarExtension::process(uint8_t *inputData) { // force the data type to 1 when a World Tour guitar is detected if ((_guitarType == WII_GUITAR_GHWT) && (getDataType() != WII_DATA_TYPE_1)) { setDataType(WII_DATA_TYPE_1); + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].origin = WII_ANALOG_PRECISION_1; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].origin = WII_ANALOG_PRECISION_1; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_X].origin = WII_ANALOG_PRECISION_0; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].origin = WII_ANALOG_PRECISION_0; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].destination = WII_ANALOG_PRECISION_3; } } if (_guitarType != WII_GUITAR_UNSET) { @@ -115,7 +195,7 @@ void GuitarExtension::process(uint8_t *inputData) { // } // } // printf("Joy1 X=%4d Y=%4d Whammy=%4d U=%1d D=%1d -=%1d +=%1d\n", joy1X, joy1Y, whammyBar, directionUp, directionDown, buttonMinus, buttonPlus); -// printf("Joy1 X=%4d Y=%4d Whammy=%4d U=%1d D=%1d -=%1d +=%1d\n", joy1X, joy1Y, whammyBar, directionUp, directionDown, buttonMinus, buttonPlus); +// printf("Whammy=%4d\n", analogState[WiiAnalogs::WII_ANALOG_RIGHT_X]); // printf("O=%1d B=%1d Y=%1d R=%1d G=%1d\n", buttons[GuitarButtons::GUITAR_ORANGE], buttons[GuitarButtons::GUITAR_BLUE], buttons[GuitarButtons::GUITAR_YELLOW], buttons[GuitarButtons::GUITAR_RED], buttons[GuitarButtons::GUITAR_GREEN]); #endif } \ No newline at end of file diff --git a/lib/WiiExtension/extensions/GuitarExtension.h b/lib/WiiExtension/extensions/GuitarExtension.h index 12d95e367..db8cc9dc9 100644 --- a/lib/WiiExtension/extensions/GuitarExtension.h +++ b/lib/WiiExtension/extensions/GuitarExtension.h @@ -3,6 +3,11 @@ #include "ExtensionBase.h" +#define WII_GUITAR_GATE_SIZE 97 +#define WII_GUITAR_GATE_CENTER 128 +#define WII_GUITAR_TRIGGER_MAX 32 +#define WII_GUITAR_ANALOG_GAP (WII_GUITAR_GATE_CENTER-WII_GUITAR_GATE_SIZE) + enum GuitarButtons { GUITAR_ORANGE = WiiButtons::WII_BUTTON_ZL, GUITAR_RED = WiiButtons::WII_BUTTON_B, diff --git a/src/addons/wiiext.cpp b/src/addons/wiiext.cpp index 63b33d5f8..af085e272 100644 --- a/src/addons/wiiext.cpp +++ b/src/addons/wiiext.cpp @@ -109,10 +109,10 @@ void WiiExtensionInput::process() { Gamepad * gamepad = Storage::getInstance().GetGamepad(); - gamepad->state.lx = leftX; - gamepad->state.ly = leftY; - gamepad->state.rx = rightX; - gamepad->state.ry = rightY; + gamepad->state.lx = bounds(leftX,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + gamepad->state.ly = bounds(leftY,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + gamepad->state.rx = bounds(rightX,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + gamepad->state.ry = bounds(rightY,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); if (wii->extensionType == WII_EXTENSION_CLASSIC) { gamepad->hasAnalogTriggers = true; @@ -145,3 +145,9 @@ void WiiExtensionInput::process() { uint16_t WiiExtensionInput::map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } + +uint16_t WiiExtensionInput::bounds(uint16_t x, uint16_t out_min, uint16_t out_max) { + if (x > out_max) x = out_max; + if (x < out_min) x = out_min; + return x; +} From fb21dbf5a0c691671a8618d3fb374734f119ea1d Mon Sep 17 00:00:00 2001 From: Mike Parks Date: Mon, 29 May 2023 13:30:37 -0700 Subject: [PATCH 05/15] Added default calibration for Drums and Turntable extensions since no calibration exists on device. Implemented default button mapping config in prep for web-config mapping options. --- headers/addons/wiiext.h | 109 ++++++++ lib/WiiExtension/WiiExtension.h | 30 +-- lib/WiiExtension/extensions/DrumExtension.cpp | 8 +- lib/WiiExtension/extensions/ExtensionBase.cpp | 2 +- lib/WiiExtension/extensions/ExtensionBase.h | 23 +- .../extensions/TurntableExtension.cpp | 70 ++++- .../extensions/TurntableExtension.h | 26 +- src/addons/wiiext.cpp | 250 +++++++++++------- 8 files changed, 377 insertions(+), 141 deletions(-) diff --git a/headers/addons/wiiext.h b/headers/addons/wiiext.h index a39b31bd7..664d5b5bf 100644 --- a/headers/addons/wiiext.h +++ b/headers/addons/wiiext.h @@ -2,6 +2,7 @@ #define _WIIExtensionAddon_H #include +#include #include #include #include "BoardConfig.h" @@ -37,6 +38,14 @@ #define WII_EXTENSION_I2C_SPEED 400000 #endif +#define WII_SET_MASK(bits, check, val) ((check) ? ((bits) |= (val)) : ((bits) &= ~(val))) + +typedef struct { + // button ID = gamepad mask value + std::unordered_map buttonMap; + std::unordered_map dpadMap; +} WiiExtensionConfig; + class WiiExtensionInput : public GPAddon { public: virtual bool available(); @@ -49,6 +58,106 @@ class WiiExtensionInput : public GPAddon { uint32_t uIntervalMS; uint32_t nextTimer; + // controller ID = config + // defaults if no defined config + std::unordered_map extensionConfigs = { + { + WII_EXTENSION_NUNCHUCK, + { + { + {WiiButtons::WII_BUTTON_C,GAMEPAD_MASK_B1}, + {WiiButtons::WII_BUTTON_C,GAMEPAD_MASK_B2}, + },{/* No D-Pad */} + } + }, + { + WII_EXTENSION_CLASSIC, + { + { + {WiiButtons::WII_BUTTON_B, GAMEPAD_MASK_B1}, + {WiiButtons::WII_BUTTON_A, GAMEPAD_MASK_B2}, + {WiiButtons::WII_BUTTON_X, GAMEPAD_MASK_B4}, + {WiiButtons::WII_BUTTON_Y, GAMEPAD_MASK_B3}, + {WiiButtons::WII_BUTTON_L, GAMEPAD_MASK_L2}, + {WiiButtons::WII_BUTTON_ZL, GAMEPAD_MASK_L1}, + {WiiButtons::WII_BUTTON_R, GAMEPAD_MASK_R2}, + {WiiButtons::WII_BUTTON_ZR, GAMEPAD_MASK_R1}, + {WiiButtons::WII_BUTTON_MINUS, GAMEPAD_MASK_S1}, + {WiiButtons::WII_BUTTON_PLUS, GAMEPAD_MASK_S2}, + {WiiButtons::WII_BUTTON_HOME, GAMEPAD_MASK_A1}, + }, + { + {WiiDirectionalPad::WII_DIRECTION_UP, GAMEPAD_MASK_UP}, + {WiiDirectionalPad::WII_DIRECTION_DOWN, GAMEPAD_MASK_DOWN}, + {WiiDirectionalPad::WII_DIRECTION_LEFT, GAMEPAD_MASK_LEFT}, + {WiiDirectionalPad::WII_DIRECTION_RIGHT, GAMEPAD_MASK_RIGHT}, + } + } + }, + { + WII_EXTENSION_TAIKO, + { + { + {TaikoButtons::TATA_KAT_LEFT, GAMEPAD_MASK_L2}, + {TaikoButtons::TATA_KAT_RIGHT, GAMEPAD_MASK_R2}, + {TaikoButtons::TATA_DON_RIGHT, GAMEPAD_MASK_B1}, + }, + { + {TaikoButtons::TATA_DON_LEFT, GAMEPAD_MASK_LEFT}, + } + } + }, + { + WII_EXTENSION_GUITAR, + { + { + {GuitarButtons::GUITAR_RED, GAMEPAD_MASK_B2}, + {GuitarButtons::GUITAR_GREEN, GAMEPAD_MASK_B1}, + {GuitarButtons::GUITAR_YELLOW, GAMEPAD_MASK_B4}, + {GuitarButtons::GUITAR_BLUE, GAMEPAD_MASK_B3}, + {GuitarButtons::GUITAR_ORANGE, GAMEPAD_MASK_L2}, + {GuitarButtons::GUITAR_PEDAL, GAMEPAD_MASK_R2}, + {WiiButtons::WII_BUTTON_MINUS, GAMEPAD_MASK_S1}, + {WiiButtons::WII_BUTTON_PLUS, GAMEPAD_MASK_S2}, + },{/* No D-Pad */} + } + }, + { + WII_EXTENSION_DRUMS, + { + { + {DrumButtons::DRUM_RED, GAMEPAD_MASK_B2}, + {DrumButtons::DRUM_GREEN, GAMEPAD_MASK_B1}, + {DrumButtons::DRUM_BLUE, GAMEPAD_MASK_B4}, + {DrumButtons::DRUM_YELLOW, GAMEPAD_MASK_B3}, + {DrumButtons::DRUM_ORANGE, GAMEPAD_MASK_L2}, + {DrumButtons::DRUM_PEDAL, GAMEPAD_MASK_R2}, + {WiiButtons::WII_BUTTON_MINUS, GAMEPAD_MASK_S1}, + {WiiButtons::WII_BUTTON_PLUS, GAMEPAD_MASK_S2}, + },{/* No D-Pad */} + } + }, + { + WII_EXTENSION_TURNTABLE, + { + { + {TurntableButtons::TURNTABLE_RIGHT_GREEN, GAMEPAD_MASK_B3}, + {TurntableButtons::TURNTABLE_RIGHT_RED, GAMEPAD_MASK_B4}, + {TurntableButtons::TURNTABLE_RIGHT_BLUE, GAMEPAD_MASK_B2}, + {TurntableButtons::TURNTABLE_EUPHORIA, GAMEPAD_MASK_R1}, + {WiiButtons::WII_BUTTON_MINUS, GAMEPAD_MASK_S1}, + {WiiButtons::WII_BUTTON_PLUS, GAMEPAD_MASK_S2}, + }, + { + {TurntableDirectionalPad::TURNTABLE_LEFT_GREEN, GAMEPAD_MASK_LEFT}, + {TurntableDirectionalPad::TURNTABLE_LEFT_RED, GAMEPAD_MASK_UP}, + {TurntableDirectionalPad::TURNTABLE_LEFT_BLUE, GAMEPAD_MASK_RIGHT}, + } + } + }, + }; + WiiExtensionConfig* currentConfig = NULL; + bool buttonC = false; bool buttonZ = false; diff --git a/lib/WiiExtension/WiiExtension.h b/lib/WiiExtension/WiiExtension.h index b9467fe6d..9aa60237b 100644 --- a/lib/WiiExtension/WiiExtension.h +++ b/lib/WiiExtension/WiiExtension.h @@ -9,18 +9,21 @@ #include "extensions/Extensions.h" -#define WII_EXTENSION_NONE -1 -#define WII_EXTENSION_NUNCHUCK 0 -#define WII_EXTENSION_CLASSIC 1 -#define WII_EXTENSION_CLASSIC_PRO 2 -#define WII_EXTENSION_DRAWSOME 3 -#define WII_EXTENSION_GUITAR 4 -#define WII_EXTENSION_DRUMS 5 -#define WII_EXTENSION_TURNTABLE 6 -#define WII_EXTENSION_TAIKO 7 -#define WII_EXTENSION_UDRAW 8 -#define WII_EXTENSION_BALANCE_BOARD 9 -#define WII_EXTENSION_MOTION_PLUS 10 +enum WiiExtensionController { + WII_EXTENSION_NONE = -1, + WII_EXTENSION_NUNCHUCK, + WII_EXTENSION_CLASSIC, + WII_EXTENSION_CLASSIC_PRO, + WII_EXTENSION_DRAWSOME, + WII_EXTENSION_GUITAR, + WII_EXTENSION_DRUMS, + WII_EXTENSION_TURNTABLE, + WII_EXTENSION_TAIKO, + WII_EXTENSION_UDRAW, + WII_EXTENSION_BALANCE_BOARD, + WII_EXTENSION_MOTION_PLUS, + WII_EXTENSION_COUNT +}; #define WII_DATA_TYPE_0 0 #define WII_DATA_TYPE_1 1 @@ -123,9 +126,6 @@ class WiiExtension { uint8_t _lastRead[16] = {0xFF}; - uint16_t map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max); - uint16_t calibrate(uint16_t pos, uint16_t min, uint16_t max, uint16_t center); - int doI2CWrite(uint8_t *pData, int iLen); int doI2CRead(uint8_t *pData, int iLen); uint8_t doI2CTest(); diff --git a/lib/WiiExtension/extensions/DrumExtension.cpp b/lib/WiiExtension/extensions/DrumExtension.cpp index ff9f206d7..481100325 100644 --- a/lib/WiiExtension/extensions/DrumExtension.cpp +++ b/lib/WiiExtension/extensions/DrumExtension.cpp @@ -21,8 +21,12 @@ void DrumExtension::process(uint8_t *inputData) { #if WII_EXTENSION_DEBUG==true //printf("O=%1d R=%1d Y=%1d G=%1d B=%1d P=%1d\n", buttons[DrumButtons::DRUM_ORANGE], buttons[DrumButtons::DRUM_RED], buttons[DrumButtons::DRUM_YELLOW], buttons[DrumButtons::DRUM_GREEN], buttons[DrumButtons::DRUM_BLUE], buttons[DrumButtons::DRUM_PEDAL]); //printf("-=%1d +=%1d Joy X=%4d Y=%4d\n", buttons[WiiButtons::BUTTON_MINUS], buttons[WiiButtons::BUTTON_PLUS], analogState[WiiAnalogs::ANALOG_LEFT_X], analogState[WiiAnalogs::ANALOG_LEFT_Y]); - //for (int i = 0; i < result; ++i) { - // if (_lastRead[i] != regRead[i]) printf("Byte%2d " BYTE_TO_BINARY_PATTERN "\n", i, BYTE_TO_BINARY(regRead[i])); + //if (_lastRead[2] != inputData[2]) printf("Byte%2d " BYTE_TO_BINARY_PATTERN "\n", 2, BYTE_TO_BINARY(inputData[2])); + //if (_lastRead[3] != inputData[3]) printf("Byte%2d " BYTE_TO_BINARY_PATTERN "\n", 3, BYTE_TO_BINARY(inputData[3])); + //if (_lastRead[5] != inputData[5]) printf("Byte%2d " BYTE_TO_BINARY_PATTERN "\n", 5, BYTE_TO_BINARY(inputData[5])); + //for (int i = 0; i < 8; ++i) { + // //if (_lastRead[i] != inputData[i]) printf("Byte%2d " BYTE_TO_BINARY_PATTERN "\n", i, BYTE_TO_BINARY(inputData[i])); + // _lastRead[i] = inputData[i]; //} #endif } \ No newline at end of file diff --git a/lib/WiiExtension/extensions/ExtensionBase.cpp b/lib/WiiExtension/extensions/ExtensionBase.cpp index 6c4b69589..1d3bcc4d0 100644 --- a/lib/WiiExtension/extensions/ExtensionBase.cpp +++ b/lib/WiiExtension/extensions/ExtensionBase.cpp @@ -97,7 +97,7 @@ void ExtensionBase::postProcess() { //applyCalibration(analogState[i], minVal, maxVal, cenVal), #if WII_EXTENSION_DEBUG==true - if (i == WiiAnalogs::WII_ANALOG_RIGHT_X) { + if (i == WiiAnalogs::WII_ANALOG_RIGHT_Y) { //printf("cur:%5d min=%5d:%5d max=%5d:%5d cen=%5d:%5d out:%5d off:%5d\n", analogState[i], _analogCalibration[i].minimum, minVal, _analogCalibration[i].maximum, maxVal, _analogCalibration[i].center, cenVal, outVal, centerOffset); } #endif diff --git a/lib/WiiExtension/extensions/ExtensionBase.h b/lib/WiiExtension/extensions/ExtensionBase.h index 01cfbf38b..14078a292 100644 --- a/lib/WiiExtension/extensions/ExtensionBase.h +++ b/lib/WiiExtension/extensions/ExtensionBase.h @@ -4,15 +4,15 @@ #include #include -enum WiiDirectionalPad { +typedef enum { WII_DIRECTION_UP, WII_DIRECTION_DOWN, WII_DIRECTION_LEFT, WII_DIRECTION_RIGHT, WII_MAX_DIRECTIONS -}; +} WiiDirectionalPad; -enum WiiButtons { +typedef enum { WII_BUTTON_A, WII_BUTTON_B, WII_BUTTON_C, @@ -27,9 +27,9 @@ enum WiiButtons { WII_BUTTON_HOME, WII_BUTTON_PLUS, WII_MAX_BUTTONS -}; +} WiiButtons; -enum WiiAnalogs { +typedef enum { WII_ANALOG_LEFT_X, WII_ANALOG_LEFT_Y, WII_ANALOG_RIGHT_X, @@ -38,14 +38,14 @@ enum WiiAnalogs { WII_ANALOG_TRIGGER_RIGHT, WII_ANALOG_CALIBRATION_PRECISION, WII_MAX_ANALOGS -}; +} WiiAnalogs; -enum WiiMotions { +typedef enum { WII_MOTION_X, WII_MOTION_Y, WII_MOTION_Z, WII_MAX_MOTIONS -}; +} WiiMotions; typedef struct { uint16_t origin = 0; @@ -67,9 +67,10 @@ class ExtensionBase { WiiAnalogPrecision _analogPrecision[WiiAnalogs::WII_MAX_ANALOGS]; WiiAnalogCalibration _analogCalibration[WiiAnalogs::WII_MAX_ANALOGS]; - private: + uint16_t map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max); uint16_t bounds(uint16_t x, uint16_t out_min, uint16_t out_max); + private: uint16_t applyCalibration(uint16_t pos, uint16_t min, uint16_t max, uint16_t cen); public: bool directionalPad[WiiDirectionalPad::WII_MAX_DIRECTIONS]; @@ -85,6 +86,10 @@ class ExtensionBase { virtual void process(uint8_t *inputData); virtual void postProcess(); +#if WII_EXTENSION_DEBUG==true + uint8_t _lastRead[16] = {0xFF}; +#endif + void setDataType(uint8_t dataType); uint8_t getDataType() { return _dataType; }; diff --git a/lib/WiiExtension/extensions/TurntableExtension.cpp b/lib/WiiExtension/extensions/TurntableExtension.cpp index ebeacd04c..23bb03198 100644 --- a/lib/WiiExtension/extensions/TurntableExtension.cpp +++ b/lib/WiiExtension/extensions/TurntableExtension.cpp @@ -3,32 +3,88 @@ #include "WiiExtension.h" +void TurntableExtension::init(uint8_t dataType) { + ExtensionBase::init(dataType); + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].origin = WII_ANALOG_PRECISION_1; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].origin = WII_ANALOG_PRECISION_1; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_Y].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_X].origin = WII_ANALOG_PRECISION_1; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_X].destination = WII_ANALOG_PRECISION_3; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].origin = WII_ANALOG_PRECISION_1; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].destination = WII_ANALOG_PRECISION_3; + + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].origin = WII_ANALOG_PRECISION_0; // 32 + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_0; // 32 + _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; + + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination = WII_ANALOG_PRECISION_3; + + + // preseed calibration data with max ranges + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].minimum = WII_TURNTABLE_ANALOG_GAP; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].center = WII_TURNTABLE_GATE_CENTER; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_X].maximum = WII_TURNTABLE_GATE_CENTER+WII_TURNTABLE_GATE_SIZE; + + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].minimum = WII_TURNTABLE_ANALOG_GAP; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].center = WII_TURNTABLE_GATE_CENTER; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_Y].maximum = WII_TURNTABLE_GATE_CENTER+WII_TURNTABLE_GATE_SIZE; + + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].minimum = 1; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].center = WII_TURNTABLE_TRIGGER_MAX*2; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].maximum = 256; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_X].useOffset = false; + + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].minimum = 1; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].center = WII_TURNTABLE_TRIGGER_MAX*2; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].maximum = 256; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].useOffset = false; + + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].minimum = 1; + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].center = WII_TURNTABLE_TRIGGER_MAX/2; + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].maximum = WII_TURNTABLE_TRIGGER_MAX; + + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].minimum = 1; + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].center = WII_TURNTABLE_TRIGGER_MAX/2; + _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].maximum = WII_TURNTABLE_TRIGGER_MAX; +} + void TurntableExtension::process(uint8_t *inputData) { // turntable accepts three data modes, but will only report as type 1, much like GHWT analogState[WiiAnalogs::WII_ANALOG_LEFT_X] = (inputData[0] & 0x3F); analogState[WiiAnalogs::WII_ANALOG_LEFT_Y] = (inputData[1] & 0x3F); - analogState[TurntableAnalogs::TURNTABLE_LEFT] = ((inputData[4] & 0x01) << 5) | ((inputData[3] & 0x1F) >> 0); - analogState[TurntableAnalogs::TURNTABLE_RIGHT] = ((inputData[0] & 0xC0) >> 3) | ((inputData[1] & 0xC0) >> 5) | ((inputData[2] & 0x80) >> 7); + analogState[TurntableAnalogs::TURNTABLE_LEFT] = (((inputData[4] & 0x00) << 5) | ((inputData[3] & 0x1F) >> 0)); + analogState[TurntableAnalogs::TURNTABLE_RIGHT] = (((inputData[0] & 0xC0) >> 3) | ((inputData[1] & 0xC0) >> 5) | ((inputData[2] & 0x80) >> 7)); analogState[TurntableAnalogs::TURNTABLE_EFFECTS] = (((inputData[2] & 0x60) >> 2) | ((inputData[3] & 0xE0) >> 5)); analogState[TurntableAnalogs::TURNTABLE_CROSSFADE] = ((inputData[2] & 0x1E) >> 1); - buttons[TurntableButtons::TURNTABLE_LEFT_RED] = !((inputData[4] & 0x20) >> 5); + // turntables report 0-5 for CW, 27-32 for CCW (as unsigned) + analogState[TurntableAnalogs::TURNTABLE_LEFT] = ((analogState[TurntableAnalogs::TURNTABLE_LEFT] < 15) ? WII_TURNTABLE_MAX_PRECISION-analogState[TurntableAnalogs::TURNTABLE_LEFT] : WII_TURNTABLE_MAX_PRECISION+(WII_TURNTABLE_MAX_PRECISION-analogState[TurntableAnalogs::TURNTABLE_LEFT])); + analogState[TurntableAnalogs::TURNTABLE_RIGHT] = ((analogState[TurntableAnalogs::TURNTABLE_RIGHT] < 15) ? WII_TURNTABLE_MAX_PRECISION-analogState[TurntableAnalogs::TURNTABLE_RIGHT] : WII_TURNTABLE_MAX_PRECISION+(WII_TURNTABLE_MAX_PRECISION-analogState[TurntableAnalogs::TURNTABLE_RIGHT])); + buttons[WiiButtons::WII_BUTTON_MINUS] = !((inputData[4] & 0x10) >> 4); buttons[WiiButtons::WII_BUTTON_PLUS] = !((inputData[4] & 0x04) >> 2); - buttons[TurntableButtons::TURNTABLE_RIGHT_RED] = !((inputData[4] & 0x02) >> 1); - buttons[TurntableButtons::TURNTABLE_LEFT_BLUE] = !((inputData[5] & 0x80) >> 7); buttons[TurntableButtons::TURNTABLE_RIGHT_GREEN] = !((inputData[5] & 0x20) >> 5); - buttons[TurntableButtons::TURNTABLE_EUPHORIA] = !((inputData[5] & 0x10) >> 4); - buttons[TurntableButtons::TURNTABLE_LEFT_GREEN] = !((inputData[5] & 0x08) >> 3); + buttons[TurntableButtons::TURNTABLE_RIGHT_RED] = !((inputData[4] & 0x02) >> 1); buttons[TurntableButtons::TURNTABLE_RIGHT_BLUE] = !((inputData[5] & 0x04) >> 2); + buttons[TurntableButtons::TURNTABLE_EUPHORIA] = !((inputData[5] & 0x10) >> 4); + + directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_GREEN] = !((inputData[5] & 0x08) >> 3); + directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_RED] = !((inputData[4] & 0x20) >> 5); + directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_BLUE] = !((inputData[5] & 0x80) >> 7); + + #if WII_EXTENSION_DEBUG==true //printf("LR=%1d LG=%1d LB=%1d\n", buttons[TurntableButtons::TURNTABLE_LEFT_RED], buttons[TurntableButtons::TURNTABLE_LEFT_GREEN], buttons[TurntableButtons::TURNTABLE_LEFT_BLUE]); //printf("RR=%1d RG=%1d RB=%1d\n", buttons[TurntableButtons::TURNTABLE_RIGHT_RED], buttons[TurntableButtons::TURNTABLE_RIGHT_GREEN], buttons[TurntableButtons::TURNTABLE_RIGHT_BLUE]); //printf("-=%1d EU=%1d +=%1d\n", buttons[WiiButtons::BUTTON_MINUS], buttons[TurntableButtons::TURNTABLE_EUPHORIA], buttons[WiiButtons::BUTTON_PLUS]); //printf("LTT=%4d RTT=%4d CF=%4d ED=%4d\n", analogState[TurntableAnalogs::TURNTABLE_LEFT], analogState[TurntableAnalogs::TURNTABLE_RIGHT], analogState[TurntableAnalogs::TURNTABLE_CROSSFADE], analogState[TurntableAnalogs::TURNTABLE_EFFECTS]); + //printf("X=%4d Y=%4d\n", analogState[WiiAnalogs::WII_ANALOG_LEFT_X], analogState[WiiAnalogs::WII_ANALOG_LEFT_Y]); //for (int i = 0; i < result; ++i) { // if (_lastRead[i] != inputData[i]) printf("Byte%2d " BYTE_TO_BINARY_PATTERN "\n", i, BYTE_TO_BINARY(inputData[i])); diff --git a/lib/WiiExtension/extensions/TurntableExtension.h b/lib/WiiExtension/extensions/TurntableExtension.h index f9e56dc2d..2a6ff086c 100644 --- a/lib/WiiExtension/extensions/TurntableExtension.h +++ b/lib/WiiExtension/extensions/TurntableExtension.h @@ -4,24 +4,34 @@ #include "ExtensionBase.h" enum TurntableButtons { - TURNTABLE_LEFT_RED = WiiButtons::WII_BUTTON_L, - TURNTABLE_RIGHT_RED = WiiButtons::WII_BUTTON_R, - TURNTABLE_LEFT_GREEN = WiiButtons::WII_BUTTON_X, - TURNTABLE_LEFT_BLUE = WiiButtons::WII_BUTTON_ZL, TURNTABLE_RIGHT_GREEN = WiiButtons::WII_BUTTON_Y, - TURNTABLE_RIGHT_BLUE = WiiButtons::WII_BUTTON_ZR, - TURNTABLE_EUPHORIA = WiiButtons::WII_BUTTON_A + TURNTABLE_RIGHT_RED = WiiButtons::WII_BUTTON_X, + TURNTABLE_RIGHT_BLUE = WiiButtons::WII_BUTTON_A, + TURNTABLE_EUPHORIA = WiiButtons::WII_BUTTON_ZR +}; + +enum TurntableDirectionalPad { + TURNTABLE_LEFT_GREEN = WiiDirectionalPad::WII_DIRECTION_LEFT, + TURNTABLE_LEFT_RED = WiiDirectionalPad::WII_DIRECTION_UP, + TURNTABLE_LEFT_BLUE = WiiDirectionalPad::WII_DIRECTION_RIGHT, }; enum TurntableAnalogs { - TURNTABLE_LEFT = WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT, + TURNTABLE_LEFT = WiiAnalogs::WII_ANALOG_RIGHT_Y, TURNTABLE_RIGHT = WiiAnalogs::WII_ANALOG_RIGHT_X, TURNTABLE_EFFECTS = WiiAnalogs::WII_ANALOG_TRIGGER_LEFT, - TURNTABLE_CROSSFADE = WiiAnalogs::WII_ANALOG_RIGHT_Y + TURNTABLE_CROSSFADE = WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT }; +#define WII_TURNTABLE_GATE_SIZE 97 +#define WII_TURNTABLE_GATE_CENTER 128 +#define WII_TURNTABLE_TRIGGER_MAX 64 +#define WII_TURNTABLE_ANALOG_GAP (WII_TURNTABLE_GATE_CENTER-WII_TURNTABLE_GATE_SIZE) +#define WII_TURNTABLE_MAX_PRECISION WII_ANALOG_PRECISION_0 + class TurntableExtension : public ExtensionBase { public: + void init(uint8_t dataType) override; void process(uint8_t *inputData) override; }; diff --git a/src/addons/wiiext.cpp b/src/addons/wiiext.cpp index af085e272..ea5a4f239 100644 --- a/src/addons/wiiext.cpp +++ b/src/addons/wiiext.cpp @@ -20,6 +20,8 @@ void WiiExtensionInput::setup() { #endif uIntervalMS = 0; + + currentConfig = NULL; wii = new WiiExtension( options.wiiExtensionSDAPin, @@ -32,114 +34,164 @@ void WiiExtensionInput::setup() { } void WiiExtensionInput::process() { + Gamepad * gamepad = Storage::getInstance().GetGamepad(); + if (nextTimer < getMillis()) { wii->poll(); - - if (wii->extensionType == WII_EXTENSION_NUNCHUCK) { - buttonZ = wii->getController()->buttons[WiiButtons::WII_BUTTON_Z]; - buttonC = wii->getController()->buttons[WiiButtons::WII_BUTTON_C]; - - leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,1023,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],1023,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightX = GAMEPAD_JOYSTICK_MID; - rightY = GAMEPAD_JOYSTICK_MID; - - triggerLeft = 0; - triggerRight = 0; - } else if ((wii->extensionType == WII_EXTENSION_CLASSIC) || (wii->extensionType == WII_EXTENSION_CLASSIC_PRO)) { - buttonA = wii->getController()->buttons[WiiButtons::WII_BUTTON_A]; - buttonB = wii->getController()->buttons[WiiButtons::WII_BUTTON_B]; - buttonX = wii->getController()->buttons[WiiButtons::WII_BUTTON_X]; - buttonY = wii->getController()->buttons[WiiButtons::WII_BUTTON_Y]; - buttonL = wii->getController()->buttons[WiiButtons::WII_BUTTON_L]; - buttonZL = wii->getController()->buttons[WiiButtons::WII_BUTTON_ZL]; - buttonR = wii->getController()->buttons[WiiButtons::WII_BUTTON_R]; - buttonZR = wii->getController()->buttons[WiiButtons::WII_BUTTON_ZR]; - dpadUp = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_UP]; - dpadDown = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN]; - dpadLeft = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_LEFT]; - dpadRight = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_RIGHT]; - buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; - buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; - buttonHome = wii->getController()->buttons[WiiButtons::WII_BUTTON_HOME]; - - if (wii->extensionType == WII_EXTENSION_CLASSIC) { - triggerLeft = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT]; - triggerRight = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT]; + if (wii->extensionType != WII_EXTENSION_NONE) { + currentConfig = &extensionConfigs[wii->extensionType]; + + //for (const auto& [extensionButton, value] : currentConfig->buttonMap) { + // WII_SET_MASK(buttonState, wii->getController()->buttons[extensionButton], value); + //} + + if (wii->extensionType == WII_EXTENSION_NUNCHUCK) { + buttonZ = wii->getController()->buttons[WiiButtons::WII_BUTTON_Z]; + buttonC = wii->getController()->buttons[WiiButtons::WII_BUTTON_C]; + + leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,1023,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],1023,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightX = GAMEPAD_JOYSTICK_MID; + rightY = GAMEPAD_JOYSTICK_MID; + + triggerLeft = 0; + triggerRight = 0; + } else if ((wii->extensionType == WII_EXTENSION_CLASSIC) || (wii->extensionType == WII_EXTENSION_CLASSIC_PRO)) { + buttonA = wii->getController()->buttons[WiiButtons::WII_BUTTON_A]; + buttonB = wii->getController()->buttons[WiiButtons::WII_BUTTON_B]; + buttonX = wii->getController()->buttons[WiiButtons::WII_BUTTON_X]; + buttonY = wii->getController()->buttons[WiiButtons::WII_BUTTON_Y]; + buttonL = wii->getController()->buttons[WiiButtons::WII_BUTTON_L]; + buttonZL = wii->getController()->buttons[WiiButtons::WII_BUTTON_ZL]; + buttonR = wii->getController()->buttons[WiiButtons::WII_BUTTON_R]; + buttonZR = wii->getController()->buttons[WiiButtons::WII_BUTTON_ZR]; + buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; + buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; + buttonHome = wii->getController()->buttons[WiiButtons::WII_BUTTON_HOME]; + dpadUp = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_UP]; + dpadDown = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN]; + dpadLeft = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_LEFT]; + dpadRight = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_RIGHT]; + + if (wii->extensionType == WII_EXTENSION_CLASSIC) { + triggerLeft = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT]; + triggerRight = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT]; + } + + leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + } else if (wii->extensionType == WII_EXTENSION_GUITAR) { + buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; + buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; + + dpadUp = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_UP]; + dpadDown = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN]; + + buttonB = wii->getController()->buttons[GuitarButtons::GUITAR_GREEN]; + buttonA = wii->getController()->buttons[GuitarButtons::GUITAR_RED]; + buttonX = wii->getController()->buttons[GuitarButtons::GUITAR_YELLOW]; + buttonY = wii->getController()->buttons[GuitarButtons::GUITAR_BLUE]; + buttonL = wii->getController()->buttons[GuitarButtons::GUITAR_ORANGE]; + + // whammy currently maps to Joy2X in addition to the raw whammy value + whammyBar = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_X]; + buttonR = wii->getController()->buttons[GuitarButtons::GUITAR_PEDAL]; + + leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightY = GAMEPAD_JOYSTICK_MID; + + triggerLeft = 0; + triggerRight = 0; + } else if (wii->extensionType == WII_EXTENSION_TAIKO) { + buttonL = wii->getController()->buttons[TaikoButtons::TATA_KAT_LEFT]; + buttonR = wii->getController()->buttons[TaikoButtons::TATA_KAT_RIGHT]; + + dpadLeft = wii->getController()->buttons[TaikoButtons::TATA_DON_LEFT]; + buttonA = wii->getController()->buttons[TaikoButtons::TATA_DON_RIGHT]; + } else if (wii->extensionType == WII_EXTENSION_DRUMS) { + buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; + buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; + + buttonB = wii->getController()->buttons[DrumButtons::DRUM_RED]; + buttonA = wii->getController()->buttons[DrumButtons::DRUM_GREEN]; + buttonX = wii->getController()->buttons[DrumButtons::DRUM_YELLOW]; + buttonY = wii->getController()->buttons[DrumButtons::DRUM_BLUE]; + buttonL = wii->getController()->buttons[DrumButtons::DRUM_ORANGE]; + buttonZR = wii->getController()->buttons[DrumButtons::DRUM_PEDAL]; + + leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightX = GAMEPAD_JOYSTICK_MID; + rightY = GAMEPAD_JOYSTICK_MID; + + triggerLeft = 0; + triggerRight = 0; + } else if (wii->extensionType == WII_EXTENSION_TURNTABLE) { + buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; + buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; + + dpadLeft = wii->getController()->directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_GREEN]; + dpadUp = wii->getController()->directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_RED]; + dpadRight = wii->getController()->directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_BLUE]; + + buttonY = wii->getController()->buttons[TurntableButtons::TURNTABLE_RIGHT_GREEN]; + buttonX = wii->getController()->buttons[TurntableButtons::TURNTABLE_RIGHT_RED]; + buttonA = wii->getController()->buttons[TurntableButtons::TURNTABLE_RIGHT_BLUE]; + + buttonZR = wii->getController()->buttons[TurntableButtons::TURNTABLE_EUPHORIA]; + + leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightX = map(wii->getController()->analogState[TurntableAnalogs::TURNTABLE_RIGHT],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightY = map(wii->getController()->analogState[TurntableAnalogs::TURNTABLE_LEFT],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + + triggerLeft = wii->getController()->analogState[TurntableAnalogs::TURNTABLE_EFFECTS]; + triggerRight = wii->getController()->analogState[TurntableAnalogs::TURNTABLE_CROSSFADE]; } - - leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - } else if (wii->extensionType == WII_EXTENSION_GUITAR) { - buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; - buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; - - dpadUp = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_UP]; - dpadDown = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN]; - - buttonB = wii->getController()->buttons[GuitarButtons::GUITAR_GREEN]; - buttonA = wii->getController()->buttons[GuitarButtons::GUITAR_RED]; - buttonX = wii->getController()->buttons[GuitarButtons::GUITAR_YELLOW]; - buttonY = wii->getController()->buttons[GuitarButtons::GUITAR_BLUE]; - buttonL = wii->getController()->buttons[GuitarButtons::GUITAR_ORANGE]; - - // whammy currently maps to Joy2X in addition to the raw whammy value - whammyBar = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_X]; - buttonR = wii->getController()->buttons[GuitarButtons::GUITAR_PEDAL]; - - leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightY = GAMEPAD_JOYSTICK_MID; - - triggerLeft = 0; - triggerRight = 0; - } else if (wii->extensionType == WII_EXTENSION_TAIKO) { - buttonL = wii->getController()->buttons[TaikoButtons::TATA_KAT_LEFT]; - buttonR = wii->getController()->buttons[TaikoButtons::TATA_KAT_RIGHT]; - - dpadLeft = wii->getController()->buttons[TaikoButtons::TATA_DON_LEFT]; - buttonA = wii->getController()->buttons[TaikoButtons::TATA_DON_RIGHT]; + } else { + currentConfig = NULL; } - + nextTimer = getMillis() + uIntervalMS; } - Gamepad * gamepad = Storage::getInstance().GetGamepad(); + if (currentConfig != NULL) { + gamepad->state.lx = bounds(leftX,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + gamepad->state.ly = bounds(leftY,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + gamepad->state.rx = bounds(rightX,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + gamepad->state.ry = bounds(rightY,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + + if ((wii->extensionType == WII_EXTENSION_CLASSIC) || (wii->extensionType == WII_EXTENSION_TURNTABLE)) { + gamepad->hasAnalogTriggers = true; + gamepad->state.lt = triggerLeft; + gamepad->state.rt = triggerRight; + } else { + gamepad->hasAnalogTriggers = false; + } - gamepad->state.lx = bounds(leftX,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - gamepad->state.ly = bounds(leftY,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - gamepad->state.rx = bounds(rightX,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - gamepad->state.ry = bounds(rightY,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - - if (wii->extensionType == WII_EXTENSION_CLASSIC) { - gamepad->hasAnalogTriggers = true; - gamepad->state.lt = triggerLeft; - gamepad->state.rt = triggerRight; - } else { - gamepad->hasAnalogTriggers = false; + if (buttonC) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_C]; + if (buttonZ) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_Z]; + + if (buttonA) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_A]; + if (buttonB) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_B]; + if (buttonX) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_X]; + if (buttonY) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_Y]; + if (buttonL) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_L]; + if (buttonZL) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_ZL]; + if (buttonR) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_R]; + if (buttonZR) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_ZR]; + if (buttonSelect) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_MINUS]; + if (buttonStart) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_PLUS]; + if (buttonHome) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_HOME]; + if (dpadUp) gamepad->state.dpad |= currentConfig->dpadMap[WiiDirectionalPad::WII_DIRECTION_UP]; + if (dpadDown) gamepad->state.dpad |= currentConfig->dpadMap[WiiDirectionalPad::WII_DIRECTION_DOWN]; + if (dpadLeft) gamepad->state.dpad |= currentConfig->dpadMap[WiiDirectionalPad::WII_DIRECTION_LEFT]; + if (dpadRight) gamepad->state.dpad |= currentConfig->dpadMap[WiiDirectionalPad::WII_DIRECTION_RIGHT]; } - - if (buttonC) gamepad->state.buttons |= GAMEPAD_MASK_B1; - if (buttonZ) gamepad->state.buttons |= GAMEPAD_MASK_B2; - - if (buttonA) gamepad->state.buttons |= GAMEPAD_MASK_B2; - if (buttonB) gamepad->state.buttons |= GAMEPAD_MASK_B1; - if (buttonX) gamepad->state.buttons |= GAMEPAD_MASK_B4; - if (buttonY) gamepad->state.buttons |= GAMEPAD_MASK_B3; - if (buttonL) gamepad->state.buttons |= GAMEPAD_MASK_L2; - if (buttonZL) gamepad->state.buttons |= GAMEPAD_MASK_L1; - if (buttonR) gamepad->state.buttons |= GAMEPAD_MASK_R2; - if (buttonZR) gamepad->state.buttons |= GAMEPAD_MASK_R1; - if (buttonSelect) gamepad->state.buttons |= GAMEPAD_MASK_S1; - if (buttonStart) gamepad->state.buttons |= GAMEPAD_MASK_S2; - if (buttonHome) gamepad->state.buttons |= GAMEPAD_MASK_A1; - if (dpadUp) gamepad->state.dpad |= GAMEPAD_MASK_UP; - if (dpadDown) gamepad->state.dpad |= GAMEPAD_MASK_DOWN; - if (dpadLeft) gamepad->state.dpad |= GAMEPAD_MASK_LEFT; - if (dpadRight) gamepad->state.dpad |= GAMEPAD_MASK_RIGHT; } uint16_t WiiExtensionInput::map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max) { From ece63dfc281e767c4bea63a35d306aea32e110c8 Mon Sep 17 00:00:00 2001 From: Mike Parks Date: Mon, 29 May 2023 23:50:13 -0700 Subject: [PATCH 06/15] Moved WiiExtensionController enum to a type. --- lib/WiiExtension/WiiExtension.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/WiiExtension/WiiExtension.h b/lib/WiiExtension/WiiExtension.h index 9aa60237b..7458d31a1 100644 --- a/lib/WiiExtension/WiiExtension.h +++ b/lib/WiiExtension/WiiExtension.h @@ -9,21 +9,22 @@ #include "extensions/Extensions.h" -enum WiiExtensionController { - WII_EXTENSION_NONE = -1, +#define WII_EXTENSION_NONE -1 + +typedef enum { WII_EXTENSION_NUNCHUCK, WII_EXTENSION_CLASSIC, WII_EXTENSION_CLASSIC_PRO, - WII_EXTENSION_DRAWSOME, WII_EXTENSION_GUITAR, WII_EXTENSION_DRUMS, WII_EXTENSION_TURNTABLE, WII_EXTENSION_TAIKO, + WII_EXTENSION_TRAIN, + WII_EXTENSION_DRAWSOME, WII_EXTENSION_UDRAW, - WII_EXTENSION_BALANCE_BOARD, WII_EXTENSION_MOTION_PLUS, WII_EXTENSION_COUNT -}; +} WiiExtensionController; #define WII_DATA_TYPE_0 0 #define WII_DATA_TYPE_1 1 From 2f9f916e1995955427790b2a37e489670f60a2d7 Mon Sep 17 00:00:00 2001 From: Mike Parks Date: Fri, 2 Jun 2023 11:45:57 -0700 Subject: [PATCH 07/15] Added toggle state for DJ Hero Euphoria button LED. --- lib/WiiExtension/WiiExtension.cpp | 6 ++++++ lib/WiiExtension/extensions/TurntableExtension.cpp | 1 - lib/WiiExtension/extensions/TurntableExtension.h | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/WiiExtension/WiiExtension.cpp b/lib/WiiExtension/WiiExtension.cpp index 84c0ace0f..82e8e7b0f 100644 --- a/lib/WiiExtension/WiiExtension.cpp +++ b/lib/WiiExtension/WiiExtension.cpp @@ -217,6 +217,12 @@ void WiiExtension::poll() { _lastRead[i] = regRead[i]; } + if (extensionType == WII_EXTENSION_TURNTABLE) { + regWrite[0] = 0xFB; + regWrite[1] = ((TurntableExtension*)extensionController)->getLED(); + result = doI2CWrite(regWrite, 2); + } + // continue poll regWrite[0] = 0x00; result = doI2CWrite(regWrite, 1); diff --git a/lib/WiiExtension/extensions/TurntableExtension.cpp b/lib/WiiExtension/extensions/TurntableExtension.cpp index 23bb03198..137f458b5 100644 --- a/lib/WiiExtension/extensions/TurntableExtension.cpp +++ b/lib/WiiExtension/extensions/TurntableExtension.cpp @@ -78,7 +78,6 @@ void TurntableExtension::process(uint8_t *inputData) { directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_RED] = !((inputData[4] & 0x20) >> 5); directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_BLUE] = !((inputData[5] & 0x80) >> 7); - #if WII_EXTENSION_DEBUG==true //printf("LR=%1d LG=%1d LB=%1d\n", buttons[TurntableButtons::TURNTABLE_LEFT_RED], buttons[TurntableButtons::TURNTABLE_LEFT_GREEN], buttons[TurntableButtons::TURNTABLE_LEFT_BLUE]); //printf("RR=%1d RG=%1d RB=%1d\n", buttons[TurntableButtons::TURNTABLE_RIGHT_RED], buttons[TurntableButtons::TURNTABLE_RIGHT_GREEN], buttons[TurntableButtons::TURNTABLE_RIGHT_BLUE]); diff --git a/lib/WiiExtension/extensions/TurntableExtension.h b/lib/WiiExtension/extensions/TurntableExtension.h index 2a6ff086c..b7677daf4 100644 --- a/lib/WiiExtension/extensions/TurntableExtension.h +++ b/lib/WiiExtension/extensions/TurntableExtension.h @@ -30,9 +30,14 @@ enum TurntableAnalogs { #define WII_TURNTABLE_MAX_PRECISION WII_ANALOG_PRECISION_0 class TurntableExtension : public ExtensionBase { + private: + bool _ledState = false; public: void init(uint8_t dataType) override; void process(uint8_t *inputData) override; + + void setLED(bool ledOn) {_ledState = ledOn;}; + bool getLED() {return _ledState;}; }; #endif \ No newline at end of file From 1d648f6365f7f12e78631bdf0a657e15b8baa6f5 Mon Sep 17 00:00:00 2001 From: Mike Parks Date: Sat, 19 Aug 2023 12:19:32 -0700 Subject: [PATCH 08/15] Refactored polling logic to allow for options on boot --- headers/addons/wiiext.h | 2 + src/addons/wiiext.cpp | 308 ++++++++++++++++------------------------ 2 files changed, 125 insertions(+), 185 deletions(-) diff --git a/headers/addons/wiiext.h b/headers/addons/wiiext.h index 664d5b5bf..564385d11 100644 --- a/headers/addons/wiiext.h +++ b/headers/addons/wiiext.h @@ -190,6 +190,8 @@ class WiiExtensionInput : public GPAddon { uint16_t map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max); uint16_t bounds(uint16_t x, uint16_t out_min, uint16_t out_max); + + void update(); }; #endif // _WIIExtensionAddon_H diff --git a/src/addons/wiiext.cpp b/src/addons/wiiext.cpp index 2b94e6459..109690ad3 100644 --- a/src/addons/wiiext.cpp +++ b/src/addons/wiiext.cpp @@ -35,74 +35,7 @@ void WiiExtensionInput::setup() { // Run during setup to catch boot selection mode wii->poll(); - if (wii->extensionType == WII_EXTENSION_NUNCHUCK) { - buttonZ = wii->buttonZ; - buttonC = wii->buttonC; - - leftX = map(wii->joy1X,0,1023,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - leftY = map(wii->joy1Y,1023,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightX = GAMEPAD_JOYSTICK_MID; - rightY = GAMEPAD_JOYSTICK_MID; - - triggerLeft = 0; - triggerRight = 0; - } else if ((wii->extensionType == WII_EXTENSION_CLASSIC) || (wii->extensionType == WII_EXTENSION_CLASSIC_PRO)) { - buttonA = wii->buttonA; - buttonB = wii->buttonB; - buttonX = wii->buttonX; - buttonY = wii->buttonY; - buttonL = wii->buttonZL; - buttonZL = wii->buttonLT; - buttonR = wii->buttonZR; - buttonZR = wii->buttonRT; - dpadUp = wii->directionUp; - dpadDown = wii->directionDown; - dpadLeft = wii->directionLeft; - dpadRight = wii->directionRight; - buttonSelect = wii->buttonMinus; - buttonStart = wii->buttonPlus; - buttonHome = wii->buttonHome; - - if (wii->extensionType == WII_EXTENSION_CLASSIC) { - triggerLeft = wii->triggerLeft; - triggerRight = wii->triggerRight; - } - - leftX = map(wii->joy1X,0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - leftY = map(wii->joy1Y,WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightX = map(wii->joy2X,0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightY = map(wii->joy2Y,WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - } else if (wii->extensionType == WII_EXTENSION_GUITAR) { - buttonSelect = wii->buttonMinus; - buttonStart = wii->buttonPlus; - - dpadUp = wii->directionUp; - dpadDown = wii->directionDown; - - buttonB = wii->fretGreen; - buttonA = wii->fretRed; - buttonX = wii->fretYellow; - buttonY = wii->fretBlue; - buttonL = wii->fretOrange; - - // whammy currently maps to Joy2X in addition to the raw whammy value - whammyBar = wii->whammyBar; - buttonR = wii->pedalButton; - - leftX = map(wii->joy1X,0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - leftY = map(wii->joy1Y,WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightX = map(wii->joy2X,0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightY = GAMEPAD_JOYSTICK_MID; - - triggerLeft = 0; - triggerRight = 0; - } else if (wii->extensionType == WII_EXTENSION_TAIKO) { - buttonL = wii->rimLeft; - buttonR = wii->rimRight; - - dpadLeft = wii->drumLeft; - buttonA = wii->drumRight; - } + update(); } void WiiExtensionInput::process() { @@ -110,123 +43,8 @@ void WiiExtensionInput::process() { if (nextTimer < getMillis()) { wii->poll(); - if (wii->extensionType != WII_EXTENSION_NONE) { - currentConfig = &extensionConfigs[wii->extensionType]; - - //for (const auto& [extensionButton, value] : currentConfig->buttonMap) { - // WII_SET_MASK(buttonState, wii->getController()->buttons[extensionButton], value); - //} - - if (wii->extensionType == WII_EXTENSION_NUNCHUCK) { - buttonZ = wii->getController()->buttons[WiiButtons::WII_BUTTON_Z]; - buttonC = wii->getController()->buttons[WiiButtons::WII_BUTTON_C]; - - leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,1023,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],1023,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightX = GAMEPAD_JOYSTICK_MID; - rightY = GAMEPAD_JOYSTICK_MID; - - triggerLeft = 0; - triggerRight = 0; - } else if ((wii->extensionType == WII_EXTENSION_CLASSIC) || (wii->extensionType == WII_EXTENSION_CLASSIC_PRO)) { - buttonA = wii->getController()->buttons[WiiButtons::WII_BUTTON_A]; - buttonB = wii->getController()->buttons[WiiButtons::WII_BUTTON_B]; - buttonX = wii->getController()->buttons[WiiButtons::WII_BUTTON_X]; - buttonY = wii->getController()->buttons[WiiButtons::WII_BUTTON_Y]; - buttonL = wii->getController()->buttons[WiiButtons::WII_BUTTON_L]; - buttonZL = wii->getController()->buttons[WiiButtons::WII_BUTTON_ZL]; - buttonR = wii->getController()->buttons[WiiButtons::WII_BUTTON_R]; - buttonZR = wii->getController()->buttons[WiiButtons::WII_BUTTON_ZR]; - buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; - buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; - buttonHome = wii->getController()->buttons[WiiButtons::WII_BUTTON_HOME]; - dpadUp = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_UP]; - dpadDown = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN]; - dpadLeft = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_LEFT]; - dpadRight = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_RIGHT]; - - if (wii->extensionType == WII_EXTENSION_CLASSIC) { - triggerLeft = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT]; - triggerRight = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT]; - } - - leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - } else if (wii->extensionType == WII_EXTENSION_GUITAR) { - buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; - buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; - - dpadUp = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_UP]; - dpadDown = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN]; - - buttonB = wii->getController()->buttons[GuitarButtons::GUITAR_GREEN]; - buttonA = wii->getController()->buttons[GuitarButtons::GUITAR_RED]; - buttonX = wii->getController()->buttons[GuitarButtons::GUITAR_YELLOW]; - buttonY = wii->getController()->buttons[GuitarButtons::GUITAR_BLUE]; - buttonL = wii->getController()->buttons[GuitarButtons::GUITAR_ORANGE]; - - // whammy currently maps to Joy2X in addition to the raw whammy value - whammyBar = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_X]; - buttonR = wii->getController()->buttons[GuitarButtons::GUITAR_PEDAL]; - - leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightY = GAMEPAD_JOYSTICK_MID; - - triggerLeft = 0; - triggerRight = 0; - } else if (wii->extensionType == WII_EXTENSION_TAIKO) { - buttonL = wii->getController()->buttons[TaikoButtons::TATA_KAT_LEFT]; - buttonR = wii->getController()->buttons[TaikoButtons::TATA_KAT_RIGHT]; - - dpadLeft = wii->getController()->buttons[TaikoButtons::TATA_DON_LEFT]; - buttonA = wii->getController()->buttons[TaikoButtons::TATA_DON_RIGHT]; - } else if (wii->extensionType == WII_EXTENSION_DRUMS) { - buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; - buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; - - buttonB = wii->getController()->buttons[DrumButtons::DRUM_RED]; - buttonA = wii->getController()->buttons[DrumButtons::DRUM_GREEN]; - buttonX = wii->getController()->buttons[DrumButtons::DRUM_YELLOW]; - buttonY = wii->getController()->buttons[DrumButtons::DRUM_BLUE]; - buttonL = wii->getController()->buttons[DrumButtons::DRUM_ORANGE]; - buttonZR = wii->getController()->buttons[DrumButtons::DRUM_PEDAL]; - - leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightX = GAMEPAD_JOYSTICK_MID; - rightY = GAMEPAD_JOYSTICK_MID; - - triggerLeft = 0; - triggerRight = 0; - } else if (wii->extensionType == WII_EXTENSION_TURNTABLE) { - buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; - buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; - - dpadLeft = wii->getController()->directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_GREEN]; - dpadUp = wii->getController()->directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_RED]; - dpadRight = wii->getController()->directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_BLUE]; - - buttonY = wii->getController()->buttons[TurntableButtons::TURNTABLE_RIGHT_GREEN]; - buttonX = wii->getController()->buttons[TurntableButtons::TURNTABLE_RIGHT_RED]; - buttonA = wii->getController()->buttons[TurntableButtons::TURNTABLE_RIGHT_BLUE]; - - buttonZR = wii->getController()->buttons[TurntableButtons::TURNTABLE_EUPHORIA]; - - leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightX = map(wii->getController()->analogState[TurntableAnalogs::TURNTABLE_RIGHT],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - rightY = map(wii->getController()->analogState[TurntableAnalogs::TURNTABLE_LEFT],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - - triggerLeft = wii->getController()->analogState[TurntableAnalogs::TURNTABLE_EFFECTS]; - triggerRight = wii->getController()->analogState[TurntableAnalogs::TURNTABLE_CROSSFADE]; - } - } else { - currentConfig = NULL; - } + + update(); nextTimer = getMillis() + uIntervalMS; } @@ -275,3 +93,123 @@ uint16_t WiiExtensionInput::bounds(uint16_t x, uint16_t out_min, uint16_t out_ma if (x < out_min) x = out_min; return x; } + +void WiiExtensionInput::update() { + if (wii->extensionType != WII_EXTENSION_NONE) { + currentConfig = &extensionConfigs[wii->extensionType]; + + //for (const auto& [extensionButton, value] : currentConfig->buttonMap) { + // WII_SET_MASK(buttonState, wii->getController()->buttons[extensionButton], value); + //} + + if (wii->extensionType == WII_EXTENSION_NUNCHUCK) { + buttonZ = wii->getController()->buttons[WiiButtons::WII_BUTTON_Z]; + buttonC = wii->getController()->buttons[WiiButtons::WII_BUTTON_C]; + + leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,1023,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],1023,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightX = GAMEPAD_JOYSTICK_MID; + rightY = GAMEPAD_JOYSTICK_MID; + + triggerLeft = 0; + triggerRight = 0; + } else if ((wii->extensionType == WII_EXTENSION_CLASSIC) || (wii->extensionType == WII_EXTENSION_CLASSIC_PRO)) { + buttonA = wii->getController()->buttons[WiiButtons::WII_BUTTON_A]; + buttonB = wii->getController()->buttons[WiiButtons::WII_BUTTON_B]; + buttonX = wii->getController()->buttons[WiiButtons::WII_BUTTON_X]; + buttonY = wii->getController()->buttons[WiiButtons::WII_BUTTON_Y]; + buttonL = wii->getController()->buttons[WiiButtons::WII_BUTTON_L]; + buttonZL = wii->getController()->buttons[WiiButtons::WII_BUTTON_ZL]; + buttonR = wii->getController()->buttons[WiiButtons::WII_BUTTON_R]; + buttonZR = wii->getController()->buttons[WiiButtons::WII_BUTTON_ZR]; + buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; + buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; + buttonHome = wii->getController()->buttons[WiiButtons::WII_BUTTON_HOME]; + dpadUp = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_UP]; + dpadDown = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN]; + dpadLeft = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_LEFT]; + dpadRight = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_RIGHT]; + + if (wii->extensionType == WII_EXTENSION_CLASSIC) { + triggerLeft = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT]; + triggerRight = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT]; + } + + leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + } else if (wii->extensionType == WII_EXTENSION_GUITAR) { + buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; + buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; + + dpadUp = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_UP]; + dpadDown = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN]; + + buttonB = wii->getController()->buttons[GuitarButtons::GUITAR_GREEN]; + buttonA = wii->getController()->buttons[GuitarButtons::GUITAR_RED]; + buttonX = wii->getController()->buttons[GuitarButtons::GUITAR_YELLOW]; + buttonY = wii->getController()->buttons[GuitarButtons::GUITAR_BLUE]; + buttonL = wii->getController()->buttons[GuitarButtons::GUITAR_ORANGE]; + + // whammy currently maps to Joy2X in addition to the raw whammy value + whammyBar = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_X]; + buttonR = wii->getController()->buttons[GuitarButtons::GUITAR_PEDAL]; + + leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightY = GAMEPAD_JOYSTICK_MID; + + triggerLeft = 0; + triggerRight = 0; + } else if (wii->extensionType == WII_EXTENSION_TAIKO) { + buttonL = wii->getController()->buttons[TaikoButtons::TATA_KAT_LEFT]; + buttonR = wii->getController()->buttons[TaikoButtons::TATA_KAT_RIGHT]; + + dpadLeft = wii->getController()->buttons[TaikoButtons::TATA_DON_LEFT]; + buttonA = wii->getController()->buttons[TaikoButtons::TATA_DON_RIGHT]; + } else if (wii->extensionType == WII_EXTENSION_DRUMS) { + buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; + buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; + + buttonB = wii->getController()->buttons[DrumButtons::DRUM_RED]; + buttonA = wii->getController()->buttons[DrumButtons::DRUM_GREEN]; + buttonX = wii->getController()->buttons[DrumButtons::DRUM_YELLOW]; + buttonY = wii->getController()->buttons[DrumButtons::DRUM_BLUE]; + buttonL = wii->getController()->buttons[DrumButtons::DRUM_ORANGE]; + buttonZR = wii->getController()->buttons[DrumButtons::DRUM_PEDAL]; + + leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightX = GAMEPAD_JOYSTICK_MID; + rightY = GAMEPAD_JOYSTICK_MID; + + triggerLeft = 0; + triggerRight = 0; + } else if (wii->extensionType == WII_EXTENSION_TURNTABLE) { + buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; + buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; + + dpadLeft = wii->getController()->directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_GREEN]; + dpadUp = wii->getController()->directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_RED]; + dpadRight = wii->getController()->directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_BLUE]; + + buttonY = wii->getController()->buttons[TurntableButtons::TURNTABLE_RIGHT_GREEN]; + buttonX = wii->getController()->buttons[TurntableButtons::TURNTABLE_RIGHT_RED]; + buttonA = wii->getController()->buttons[TurntableButtons::TURNTABLE_RIGHT_BLUE]; + + buttonZR = wii->getController()->buttons[TurntableButtons::TURNTABLE_EUPHORIA]; + + leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightX = map(wii->getController()->analogState[TurntableAnalogs::TURNTABLE_RIGHT],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + rightY = map(wii->getController()->analogState[TurntableAnalogs::TURNTABLE_LEFT],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + + triggerLeft = wii->getController()->analogState[TurntableAnalogs::TURNTABLE_EFFECTS]; + triggerRight = wii->getController()->analogState[TurntableAnalogs::TURNTABLE_CROSSFADE]; + } + } else { + currentConfig = NULL; + } +} \ No newline at end of file From 81c94ff44e9f46d819c1e2ec03f771246ee39c97 Mon Sep 17 00:00:00 2001 From: Mike Parks Date: Tue, 5 Sep 2023 17:53:14 -0700 Subject: [PATCH 09/15] Refactor of Wii addon to include button and analog mapping. Included separation of Wii addon-specific L10n strings to Locales/en/Addons/WiiAddon.jsx --- headers/addons/wiiext.h | 200 ++++++++- headers/gamepad/GamepadState.h | 4 + lib/WiiExtension/WiiExtension.h | 1 - .../extensions/ClassicExtension.cpp | 84 ++-- lib/WiiExtension/extensions/ExtensionBase.cpp | 11 +- lib/WiiExtension/extensions/ExtensionBase.h | 24 +- .../extensions/GuitarExtension.cpp | 38 +- .../extensions/TurntableExtension.cpp | 46 +- .../extensions/TurntableExtension.h | 15 +- proto/config.proto | 110 +++++ src/addons/wiiext.cpp | 409 ++++++++++++++++-- src/configs/webconfig.cpp | 166 +++++++ www/server/app.js | 72 +++ www/src/Addons/Wii.tsx | 361 ++++++++++++---- www/src/Locales/en/Addons/WiiAddon.jsx | 72 +++ www/src/Locales/en/AddonsConfig.jsx | 7 - www/src/Locales/en/Index.jsx | 2 + www/src/Services/WebApi.js | 101 +++++ 18 files changed, 1453 insertions(+), 270 deletions(-) create mode 100644 www/src/Locales/en/Addons/WiiAddon.jsx diff --git a/headers/addons/wiiext.h b/headers/addons/wiiext.h index 564385d11..fe6a694e8 100644 --- a/headers/addons/wiiext.h +++ b/headers/addons/wiiext.h @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include #include "BoardConfig.h" @@ -40,12 +42,44 @@ #define WII_SET_MASK(bits, check, val) ((check) ? ((bits) |= (val)) : ((bits) &= ~(val))) +typedef enum { + WII_ANALOG_TYPE_NONE, + WII_ANALOG_TYPE_LEFT_STICK_X, + WII_ANALOG_TYPE_LEFT_STICK_Y, + WII_ANALOG_TYPE_RIGHT_STICK_X, + WII_ANALOG_TYPE_RIGHT_STICK_Y, + WII_ANALOG_TYPE_DPAD_X, + WII_ANALOG_TYPE_DPAD_Y, + WII_ANALOG_TYPE_LEFT_TRIGGER, + WII_ANALOG_TYPE_RIGHT_TRIGGER, + WII_ANALOG_TYPE_LEFT_STICK_X_PLUS, + WII_ANALOG_TYPE_LEFT_STICK_X_MINUS, + WII_ANALOG_TYPE_LEFT_STICK_Y_PLUS, + WII_ANALOG_TYPE_LEFT_STICK_Y_MINUS, + WII_ANALOG_TYPE_RIGHT_STICK_X_PLUS, + WII_ANALOG_TYPE_RIGHT_STICK_X_MINUS, + WII_ANALOG_TYPE_RIGHT_STICK_Y_PLUS, + WII_ANALOG_TYPE_RIGHT_STICK_Y_MINUS, + WII_ANALOG_TYPE_COUNT +} WiiAnalogType; + +typedef struct { + uint16_t axisType; + uint16_t minRange; + uint16_t maxRange; +} WiiAnalogAxis; + typedef struct { // button ID = gamepad mask value - std::unordered_map buttonMap; - std::unordered_map dpadMap; + std::unordered_map buttonMap; + std::unordered_map analogMap; } WiiExtensionConfig; +typedef struct { + uint16_t analogInput; + uint16_t analogValue; +} WiiAnalogChange; + class WiiExtensionInput : public GPAddon { public: virtual bool available(); @@ -62,16 +96,26 @@ class WiiExtensionInput : public GPAddon { // defaults if no defined config std::unordered_map extensionConfigs = { { - WII_EXTENSION_NUNCHUCK, + WiiExtensionController::WII_EXTENSION_NUNCHUCK, { { {WiiButtons::WII_BUTTON_C,GAMEPAD_MASK_B1}, - {WiiButtons::WII_BUTTON_C,GAMEPAD_MASK_B2}, - },{/* No D-Pad */} + {WiiButtons::WII_BUTTON_Z,GAMEPAD_MASK_B2}, + }, + { + { + WiiAnalogs::WII_ANALOG_LEFT_X, + { WiiAnalogType::WII_ANALOG_TYPE_LEFT_STICK_X, 0, 0 } + }, + { + WiiAnalogs::WII_ANALOG_LEFT_Y, + { WiiAnalogType::WII_ANALOG_TYPE_LEFT_STICK_Y, 0, 0 } + }, + } } }, { - WII_EXTENSION_CLASSIC, + WiiExtensionController::WII_EXTENSION_CLASSIC, { { {WiiButtons::WII_BUTTON_B, GAMEPAD_MASK_B1}, @@ -85,30 +129,53 @@ class WiiExtensionInput : public GPAddon { {WiiButtons::WII_BUTTON_MINUS, GAMEPAD_MASK_S1}, {WiiButtons::WII_BUTTON_PLUS, GAMEPAD_MASK_S2}, {WiiButtons::WII_BUTTON_HOME, GAMEPAD_MASK_A1}, + {WiiButtons::WII_BUTTON_UP, GAMEPAD_MASK_DU}, + {WiiButtons::WII_BUTTON_DOWN, GAMEPAD_MASK_DD}, + {WiiButtons::WII_BUTTON_LEFT, GAMEPAD_MASK_DL}, + {WiiButtons::WII_BUTTON_RIGHT, GAMEPAD_MASK_DR}, }, { - {WiiDirectionalPad::WII_DIRECTION_UP, GAMEPAD_MASK_UP}, - {WiiDirectionalPad::WII_DIRECTION_DOWN, GAMEPAD_MASK_DOWN}, - {WiiDirectionalPad::WII_DIRECTION_LEFT, GAMEPAD_MASK_LEFT}, - {WiiDirectionalPad::WII_DIRECTION_RIGHT, GAMEPAD_MASK_RIGHT}, + { + WiiAnalogs::WII_ANALOG_LEFT_X, + { WiiAnalogType::WII_ANALOG_TYPE_LEFT_STICK_X, 0, 0 } + }, + { + WiiAnalogs::WII_ANALOG_LEFT_Y, + { WiiAnalogType::WII_ANALOG_TYPE_LEFT_STICK_Y, 0, 0 } + }, + { + WiiAnalogs::WII_ANALOG_RIGHT_X, + { WiiAnalogType::WII_ANALOG_TYPE_RIGHT_STICK_X, 0, 0 } + }, + { + WiiAnalogs::WII_ANALOG_RIGHT_Y, + { WiiAnalogType::WII_ANALOG_TYPE_RIGHT_STICK_Y, 0, 0 } + }, + { + WiiAnalogs::WII_ANALOG_LEFT_TRIGGER, + { WiiAnalogType::WII_ANALOG_TYPE_LEFT_TRIGGER, 0, 0 } + }, + { + WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER, + { WiiAnalogType::WII_ANALOG_TYPE_RIGHT_TRIGGER, 0, 0 } + }, } } }, { - WII_EXTENSION_TAIKO, + WiiExtensionController::WII_EXTENSION_TAIKO, { { {TaikoButtons::TATA_KAT_LEFT, GAMEPAD_MASK_L2}, {TaikoButtons::TATA_KAT_RIGHT, GAMEPAD_MASK_R2}, {TaikoButtons::TATA_DON_RIGHT, GAMEPAD_MASK_B1}, + {TaikoButtons::TATA_DON_LEFT, GAMEPAD_MASK_DL}, }, - { - {TaikoButtons::TATA_DON_LEFT, GAMEPAD_MASK_LEFT}, - } + {} } }, { - WII_EXTENSION_GUITAR, + WiiExtensionController::WII_EXTENSION_GUITAR, { { {GuitarButtons::GUITAR_RED, GAMEPAD_MASK_B2}, @@ -119,11 +186,27 @@ class WiiExtensionInput : public GPAddon { {GuitarButtons::GUITAR_PEDAL, GAMEPAD_MASK_R2}, {WiiButtons::WII_BUTTON_MINUS, GAMEPAD_MASK_S1}, {WiiButtons::WII_BUTTON_PLUS, GAMEPAD_MASK_S2}, - },{/* No D-Pad */} + {WiiButtons::WII_BUTTON_UP, GAMEPAD_MASK_DU}, + {WiiButtons::WII_BUTTON_DOWN, GAMEPAD_MASK_DD}, + }, + { + { + WiiAnalogs::WII_ANALOG_LEFT_X, + { WiiAnalogType::WII_ANALOG_TYPE_LEFT_STICK_X, 0, 0 } + }, + { + WiiAnalogs::WII_ANALOG_LEFT_Y, + { WiiAnalogType::WII_ANALOG_TYPE_LEFT_STICK_Y, 0, 0 } + }, + { + WiiAnalogs::WII_ANALOG_RIGHT_X, + { WiiAnalogType::WII_ANALOG_TYPE_RIGHT_STICK_X, 0, 0 } + }, + } } }, { - WII_EXTENSION_DRUMS, + WiiExtensionController::WII_EXTENSION_DRUMS, { { {DrumButtons::DRUM_RED, GAMEPAD_MASK_B2}, @@ -134,24 +217,58 @@ class WiiExtensionInput : public GPAddon { {DrumButtons::DRUM_PEDAL, GAMEPAD_MASK_R2}, {WiiButtons::WII_BUTTON_MINUS, GAMEPAD_MASK_S1}, {WiiButtons::WII_BUTTON_PLUS, GAMEPAD_MASK_S2}, - },{/* No D-Pad */} + }, + { + { + WiiAnalogs::WII_ANALOG_LEFT_X, + { WiiAnalogType::WII_ANALOG_TYPE_LEFT_STICK_X, 0, 0 } + }, + { + WiiAnalogs::WII_ANALOG_LEFT_Y, + { WiiAnalogType::WII_ANALOG_TYPE_LEFT_STICK_Y, 0, 0 } + }, + } } }, { - WII_EXTENSION_TURNTABLE, + WiiExtensionController::WII_EXTENSION_TURNTABLE, { { {TurntableButtons::TURNTABLE_RIGHT_GREEN, GAMEPAD_MASK_B3}, {TurntableButtons::TURNTABLE_RIGHT_RED, GAMEPAD_MASK_B4}, {TurntableButtons::TURNTABLE_RIGHT_BLUE, GAMEPAD_MASK_B2}, {TurntableButtons::TURNTABLE_EUPHORIA, GAMEPAD_MASK_R1}, + {TurntableButtons::TURNTABLE_LEFT_GREEN, GAMEPAD_MASK_DL}, + {TurntableButtons::TURNTABLE_LEFT_RED, GAMEPAD_MASK_DU}, + {TurntableButtons::TURNTABLE_LEFT_BLUE, GAMEPAD_MASK_DR}, {WiiButtons::WII_BUTTON_MINUS, GAMEPAD_MASK_S1}, {WiiButtons::WII_BUTTON_PLUS, GAMEPAD_MASK_S2}, }, { - {TurntableDirectionalPad::TURNTABLE_LEFT_GREEN, GAMEPAD_MASK_LEFT}, - {TurntableDirectionalPad::TURNTABLE_LEFT_RED, GAMEPAD_MASK_UP}, - {TurntableDirectionalPad::TURNTABLE_LEFT_BLUE, GAMEPAD_MASK_RIGHT}, + { + WiiAnalogs::WII_ANALOG_LEFT_X, + { WiiAnalogType::WII_ANALOG_TYPE_LEFT_STICK_X, 0, 0 } + }, + { + WiiAnalogs::WII_ANALOG_LEFT_Y, + { WiiAnalogType::WII_ANALOG_TYPE_LEFT_STICK_Y, 0, 0 } + }, + { + WiiAnalogs::WII_ANALOG_RIGHT_X, + { WiiAnalogType::WII_ANALOG_TYPE_RIGHT_STICK_X, 0, 0 } + }, + { + WiiAnalogs::WII_ANALOG_RIGHT_Y, + { WiiAnalogType::WII_ANALOG_TYPE_RIGHT_STICK_Y, 0, 0 } + }, + { + WiiAnalogs::WII_ANALOG_LEFT_TRIGGER, + { WiiAnalogType::WII_ANALOG_TYPE_RIGHT_STICK_X, 0, 0 } + }, + { + WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER, + { WiiAnalogType::WII_ANALOG_TYPE_RIGHT_STICK_X, 0, 0 } + }, } } }, @@ -181,17 +298,56 @@ class WiiExtensionInput : public GPAddon { uint16_t triggerLeft = 0; uint16_t triggerRight = 0; + uint16_t lastTriggerLeft = 0; + uint16_t lastTriggerRight = 0; uint16_t whammyBar = 0; uint16_t leftX = 0; + uint16_t lastLeftX = 0; + uint16_t leftY = 0; + uint16_t lastLeftY = 0; + uint16_t rightX = 0; + uint16_t lastRightX = 0; + uint16_t rightY = 0; + uint16_t lastRightY = 0; + + std::map> analogChanges = { + {WII_ANALOG_TYPE_LEFT_STICK_X,{}}, + {WII_ANALOG_TYPE_LEFT_STICK_Y,{}}, + {WII_ANALOG_TYPE_RIGHT_STICK_X,{}}, + {WII_ANALOG_TYPE_RIGHT_STICK_Y,{}}, + {WII_ANALOG_TYPE_DPAD_X,{}}, + {WII_ANALOG_TYPE_DPAD_Y,{}}, + {WII_ANALOG_TYPE_LEFT_TRIGGER,{}}, + {WII_ANALOG_TYPE_RIGHT_TRIGGER,{}}, + + {WII_ANALOG_TYPE_LEFT_STICK_X_PLUS,{}}, + {WII_ANALOG_TYPE_LEFT_STICK_X_MINUS,{}}, + {WII_ANALOG_TYPE_LEFT_STICK_Y_PLUS,{}}, + {WII_ANALOG_TYPE_LEFT_STICK_Y_MINUS,{}}, + {WII_ANALOG_TYPE_RIGHT_STICK_X_PLUS,{}}, + {WII_ANALOG_TYPE_RIGHT_STICK_X_MINUS,{}}, + {WII_ANALOG_TYPE_RIGHT_STICK_Y_PLUS,{}}, + {WII_ANALOG_TYPE_RIGHT_STICK_Y_MINUS,{}}, + }; uint16_t map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max); uint16_t bounds(uint16_t x, uint16_t out_min, uint16_t out_max); void update(); + void setControllerButton(uint16_t controllerID, uint16_t buttonID, uint32_t buttonMask); + void setControllerAnalog(uint16_t controllerID, uint16_t analogID, uint32_t axisType); + void setControllerStickMode(uint16_t controllerID, uint16_t analogID, uint32_t axisType); + void setButtonState(bool buttonState, uint16_t buttonMask); + void queueAnalogChange(uint16_t analogInput, uint16_t analogValue, uint16_t lastAnalogValue); + void updateAnalogState(); + void reloadConfig(); + + uint16_t getAverage(std::vector const& changes); + uint16_t getDelta(std::vector const& changes, uint16_t baseValue); }; #endif // _WIIExtensionAddon_H diff --git a/headers/gamepad/GamepadState.h b/headers/gamepad/GamepadState.h index 9361eca6a..f48ec03f1 100644 --- a/headers/gamepad/GamepadState.h +++ b/headers/gamepad/GamepadState.h @@ -74,6 +74,10 @@ #define GAMEPAD_JOYSTICK_MID 0x7FFF #define GAMEPAD_JOYSTICK_MAX 0xFFFF +#define GAMEPAD_TRIGGER_MIN 0 +#define GAMEPAD_TRIGGER_MID 0x7F +#define GAMEPAD_TRIGGER_MAX 0xFF + /** * @brief AUX defines --- gamepad state that doesn't translate to an output button/dpad/etc. */ diff --git a/lib/WiiExtension/WiiExtension.h b/lib/WiiExtension/WiiExtension.h index 7458d31a1..cecdfb2b6 100644 --- a/lib/WiiExtension/WiiExtension.h +++ b/lib/WiiExtension/WiiExtension.h @@ -121,7 +121,6 @@ class WiiExtension { uint8_t iSDA; uint8_t iSCL; - uint8_t bWire; i2c_inst_t *picoI2C; int32_t iSpeed; diff --git a/lib/WiiExtension/extensions/ClassicExtension.cpp b/lib/WiiExtension/extensions/ClassicExtension.cpp index 060fe15bd..c12a46507 100644 --- a/lib/WiiExtension/extensions/ClassicExtension.cpp +++ b/lib/WiiExtension/extensions/ClassicExtension.cpp @@ -15,10 +15,10 @@ void ClassicExtension::init(uint8_t dataType) { _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].origin = WII_ANALOG_PRECISION_0; _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].destination = WII_ANALOG_PRECISION_3; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].origin = WII_ANALOG_PRECISION_0; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].destination = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_0; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].origin = WII_ANALOG_PRECISION_0; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].origin = WII_ANALOG_PRECISION_0; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].destination = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination = WII_ANALOG_PRECISION_3; @@ -32,10 +32,10 @@ void ClassicExtension::init(uint8_t dataType) { _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].origin = WII_ANALOG_PRECISION_3; _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].destination = WII_ANALOG_PRECISION_3; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].origin = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].destination = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].destination = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination = WII_ANALOG_PRECISION_3; @@ -49,10 +49,10 @@ void ClassicExtension::init(uint8_t dataType) { _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].origin = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].destination = WII_ANALOG_PRECISION_3; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].origin = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].destination = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].destination = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination = WII_ANALOG_PRECISION_3; @@ -75,13 +75,13 @@ void ClassicExtension::init(uint8_t dataType) { _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].center = WII_CLASSIC_GATE_CENTER; _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].maximum = WII_CLASSIC_GATE_CENTER+WII_CLASSIC_GATE_SIZE; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].minimum = 1; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].center = WII_CLASSIC_TRIGGER_MAX/2; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].maximum = WII_CLASSIC_TRIGGER_MAX; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].minimum = 1; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].center = WII_CLASSIC_TRIGGER_MAX/2; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].maximum = WII_CLASSIC_TRIGGER_MAX; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].minimum = 1; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].center = WII_CLASSIC_TRIGGER_MAX/2; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].maximum = WII_CLASSIC_TRIGGER_MAX; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].minimum = 1; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].center = WII_CLASSIC_TRIGGER_MAX/2; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].maximum = WII_CLASSIC_TRIGGER_MAX; } bool ClassicExtension::calibrate(uint8_t *calibrationData) { @@ -104,13 +104,13 @@ bool ClassicExtension::calibrate(uint8_t *calibrationData) { _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].minimum = calibrationData[10]; _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].center = calibrationData[11]; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].maximum = calibrationData[12]; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].minimum = 1; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].center = calibrationData[12]/2; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].maximum = calibrationData[12]; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].minimum = 1; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].center = calibrationData[12]/2; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].maximum = calibrationData[13]; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].minimum = 1; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].center = calibrationData[12]/2; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].maximum = calibrationData[13]; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].minimum = 1; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].center = calibrationData[12]/2; return true; } else { } @@ -127,11 +127,11 @@ void ClassicExtension::process(uint8_t *inputData) { analogState[WiiAnalogs::WII_ANALOG_RIGHT_X] = ((inputData[0] & 0xC0) >> 3) | ((inputData[1] & 0xC0) >> 5) | ((inputData[2] & 0x80) >> 7); analogState[WiiAnalogs::WII_ANALOG_RIGHT_Y] = (inputData[2] & 0x1F); - analogState[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT] = (((inputData[2] & 0x60) >> 2) | ((inputData[3] & 0xE0) >> 5)); - analogState[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT] = ((inputData[3] & 0x1F) >> 0); + analogState[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER] = (((inputData[2] & 0x60) >> 2) | ((inputData[3] & 0xE0) >> 5)); + analogState[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER] = ((inputData[3] & 0x1F) >> 0); - directionalPad[WiiDirectionalPad::WII_DIRECTION_RIGHT] = !((inputData[4] & 0x80) >> 7); - directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN] = !((inputData[4] & 0x40) >> 6); + buttons[WiiButtons::WII_BUTTON_RIGHT] = !((inputData[4] & 0x80) >> 7); + buttons[WiiButtons::WII_BUTTON_DOWN] = !((inputData[4] & 0x40) >> 6); buttons[WiiButtons::WII_BUTTON_L] = !((inputData[4] & 0x20) >> 5); buttons[WiiButtons::WII_BUTTON_MINUS] = !((inputData[4] & 0x10) >> 4); buttons[WiiButtons::WII_BUTTON_HOME] = !((inputData[4] & 0x08) >> 3); @@ -144,19 +144,19 @@ void ClassicExtension::process(uint8_t *inputData) { buttons[WiiButtons::WII_BUTTON_A] = !((inputData[5] & 0x10) >> 4); buttons[WiiButtons::WII_BUTTON_X] = !((inputData[5] & 0x08) >> 3); buttons[WiiButtons::WII_BUTTON_ZR] = !((inputData[5] & 0x04) >> 2); - directionalPad[WiiDirectionalPad::WII_DIRECTION_LEFT] = !((inputData[5] & 0x02) >> 1); - directionalPad[WiiDirectionalPad::WII_DIRECTION_UP] = !((inputData[5] & 0x01) >> 0); + buttons[WiiButtons::WII_BUTTON_LEFT] = !((inputData[5] & 0x02) >> 1); + buttons[WiiButtons::WII_BUTTON_UP] = !((inputData[5] & 0x01) >> 0); } else if (getDataType() == WII_DATA_TYPE_2) { analogState[WiiAnalogs::WII_ANALOG_LEFT_X] = ((inputData[0] << 2) | ((inputData[4] & 0x03) >> 0)); analogState[WiiAnalogs::WII_ANALOG_LEFT_Y] = ((inputData[2] << 2) | ((inputData[4] & 0x30) >> 4)); analogState[WiiAnalogs::WII_ANALOG_RIGHT_X] = ((inputData[1] << 2) | ((inputData[4] & 0x0C) >> 2)); analogState[WiiAnalogs::WII_ANALOG_RIGHT_Y] = ((inputData[3] << 2) | ((inputData[4] & 0xC0) >> 6)); - analogState[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT] = (inputData[5] & 0xFF); - analogState[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT] = (inputData[6] & 0xFF); + analogState[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER] = (inputData[5] & 0xFF); + analogState[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER] = (inputData[6] & 0xFF); - directionalPad[WiiDirectionalPad::WII_DIRECTION_RIGHT] = !((inputData[7] & 0x80) >> 7); - directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN] = !((inputData[7] & 0x40) >> 6); + buttons[WiiButtons::WII_BUTTON_RIGHT] = !((inputData[7] & 0x80) >> 7); + buttons[WiiButtons::WII_BUTTON_DOWN] = !((inputData[7] & 0x40) >> 6); buttons[WiiButtons::WII_BUTTON_L] = !((inputData[7] & 0x20) >> 5); buttons[WiiButtons::WII_BUTTON_MINUS] = !((inputData[7] & 0x10) >> 4); buttons[WiiButtons::WII_BUTTON_HOME] = !((inputData[7] & 0x08) >> 3); @@ -169,19 +169,19 @@ void ClassicExtension::process(uint8_t *inputData) { buttons[WiiButtons::WII_BUTTON_A] = !((inputData[8] & 0x10) >> 4); buttons[WiiButtons::WII_BUTTON_X] = !((inputData[8] & 0x08) >> 3); buttons[WiiButtons::WII_BUTTON_ZR] = !((inputData[8] & 0x04) >> 2); - directionalPad[WiiDirectionalPad::WII_DIRECTION_LEFT] = !((inputData[8] & 0x02) >> 1); - directionalPad[WiiDirectionalPad::WII_DIRECTION_UP] = !((inputData[8] & 0x01) >> 0); + buttons[WiiButtons::WII_BUTTON_LEFT] = !((inputData[8] & 0x02) >> 1); + buttons[WiiButtons::WII_BUTTON_UP] = !((inputData[8] & 0x01) >> 0); } else if (getDataType() == WII_DATA_TYPE_3) { analogState[WiiAnalogs::WII_ANALOG_LEFT_X] = (inputData[0] & 0xFF); analogState[WiiAnalogs::WII_ANALOG_LEFT_Y] = (inputData[2] & 0xFF); analogState[WiiAnalogs::WII_ANALOG_RIGHT_X] = (inputData[1] & 0xFF); analogState[WiiAnalogs::WII_ANALOG_RIGHT_Y] = (inputData[3] & 0xFF); - analogState[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT] = (inputData[4] & 0xFF); - analogState[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT] = (inputData[5] & 0xFF); + analogState[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER] = (inputData[4] & 0xFF); + analogState[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER] = (inputData[5] & 0xFF); - directionalPad[WiiDirectionalPad::WII_DIRECTION_RIGHT] = !((inputData[6] & 0x80) >> 7); - directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN] = !((inputData[6] & 0x40) >> 6); + buttons[WiiButtons::WII_BUTTON_RIGHT] = !((inputData[6] & 0x80) >> 7); + buttons[WiiButtons::WII_BUTTON_DOWN] = !((inputData[6] & 0x40) >> 6); buttons[WiiButtons::WII_BUTTON_L] = !((inputData[6] & 0x20) >> 5); buttons[WiiButtons::WII_BUTTON_MINUS] = !((inputData[6] & 0x10) >> 4); buttons[WiiButtons::WII_BUTTON_HOME] = !((inputData[6] & 0x08) >> 3); @@ -194,8 +194,8 @@ void ClassicExtension::process(uint8_t *inputData) { buttons[WiiButtons::WII_BUTTON_A] = !((inputData[7] & 0x10) >> 4); buttons[WiiButtons::WII_BUTTON_X] = !((inputData[7] & 0x08) >> 3); buttons[WiiButtons::WII_BUTTON_ZR] = !((inputData[7] & 0x04) >> 2); - directionalPad[WiiDirectionalPad::WII_DIRECTION_LEFT] = !((inputData[7] & 0x02) >> 1); - directionalPad[WiiDirectionalPad::WII_DIRECTION_UP] = !((inputData[7] & 0x01) >> 0); + buttons[WiiButtons::WII_BUTTON_LEFT] = !((inputData[7] & 0x02) >> 1); + buttons[WiiButtons::WII_BUTTON_UP] = !((inputData[7] & 0x01) >> 0); } else { // unknown } diff --git a/lib/WiiExtension/extensions/ExtensionBase.cpp b/lib/WiiExtension/extensions/ExtensionBase.cpp index 1d3bcc4d0..a561a496b 100644 --- a/lib/WiiExtension/extensions/ExtensionBase.cpp +++ b/lib/WiiExtension/extensions/ExtensionBase.cpp @@ -12,7 +12,6 @@ void ExtensionBase::init(uint8_t dataType) { isFirstRead = true; _hasCalibrationData = false; - for (i = 0; i < WiiDirectionalPad::WII_MAX_DIRECTIONS; ++i) directionalPad[i] = 0; for (i = 0; i < WiiButtons::WII_MAX_BUTTONS; ++i) buttons[i] = 0; for (i = 0; i < WiiMotions::WII_MAX_MOTIONS; ++i) motionState[i] = 0; for (i = 0; i < WiiAnalogs::WII_MAX_ANALOGS; ++i) { @@ -66,9 +65,9 @@ void ExtensionBase::postProcess() { for (i = 0; i < WiiAnalogs::WII_MAX_ANALOGS; ++i) { // scale calibration values before using if (i != WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION) { - minVal = map(_analogCalibration[i].minimum-1, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin-1, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination-1); - maxVal = map(_analogCalibration[i].maximum-1, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin-1, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination-1); - cenVal = map(_analogCalibration[i].center-1, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin-1, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination-1); + minVal = map(_analogCalibration[i].minimum, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin-1, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination-1); + maxVal = map(_analogCalibration[i].maximum, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin-1, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination-1); + cenVal = map(_analogCalibration[i].center, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin-1, 0, _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination-1); if (isFirstRead) { // stash the first read as the initial orientation. will reset on hotswap. @@ -86,7 +85,7 @@ void ExtensionBase::postProcess() { } outVal = map(analogState[i], 0, (_analogPrecision[i].origin-1), 0,(_analogPrecision[i].destination-1)); - if ((i != WiiAnalogs::WII_ANALOG_TRIGGER_LEFT) && (i != WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT)) { + if ((i != WiiAnalogs::WII_ANALOG_LEFT_TRIGGER) && (i != WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER)) { outVal = bounds(outVal, minVal, maxVal); outVal = map(outVal+centerOffset, minVal+centerOffset, maxVal+centerOffset, 0, (_analogPrecision[i].destination-1)); outVal = bounds(outVal, minVal, maxVal); @@ -94,7 +93,7 @@ void ExtensionBase::postProcess() { outVal = bounds(outVal, 0, (_analogPrecision[i].destination-1)); } - //applyCalibration(analogState[i], minVal, maxVal, cenVal), + applyCalibration(outVal, minVal, maxVal, cenVal); #if WII_EXTENSION_DEBUG==true if (i == WiiAnalogs::WII_ANALOG_RIGHT_Y) { diff --git a/lib/WiiExtension/extensions/ExtensionBase.h b/lib/WiiExtension/extensions/ExtensionBase.h index 14078a292..48fe56c37 100644 --- a/lib/WiiExtension/extensions/ExtensionBase.h +++ b/lib/WiiExtension/extensions/ExtensionBase.h @@ -4,14 +4,6 @@ #include #include -typedef enum { - WII_DIRECTION_UP, - WII_DIRECTION_DOWN, - WII_DIRECTION_LEFT, - WII_DIRECTION_RIGHT, - WII_MAX_DIRECTIONS -} WiiDirectionalPad; - typedef enum { WII_BUTTON_A, WII_BUTTON_B, @@ -26,6 +18,10 @@ typedef enum { WII_BUTTON_MINUS, WII_BUTTON_HOME, WII_BUTTON_PLUS, + WII_BUTTON_UP, + WII_BUTTON_DOWN, + WII_BUTTON_LEFT, + WII_BUTTON_RIGHT, WII_MAX_BUTTONS } WiiButtons; @@ -34,12 +30,19 @@ typedef enum { WII_ANALOG_LEFT_Y, WII_ANALOG_RIGHT_X, WII_ANALOG_RIGHT_Y, - WII_ANALOG_TRIGGER_LEFT, - WII_ANALOG_TRIGGER_RIGHT, + WII_ANALOG_LEFT_TRIGGER, + WII_ANALOG_RIGHT_TRIGGER, WII_ANALOG_CALIBRATION_PRECISION, WII_MAX_ANALOGS } WiiAnalogs; +typedef enum { + WII_JOYSTICK_NONE, + WII_JOYSTICK_LEFT_ANALOG, + WII_JOYSTICK_RIGHT_ANALOG, + WII_JOYSTICK_DPAD +} WiiJoystickModes; + typedef enum { WII_MOTION_X, WII_MOTION_Y, @@ -73,7 +76,6 @@ class ExtensionBase { private: uint16_t applyCalibration(uint16_t pos, uint16_t min, uint16_t max, uint16_t cen); public: - bool directionalPad[WiiDirectionalPad::WII_MAX_DIRECTIONS]; bool buttons[WiiButtons::WII_MAX_BUTTONS]; uint16_t analogState[WiiAnalogs::WII_MAX_ANALOGS]; uint16_t motionState[WiiMotions::WII_MAX_MOTIONS]; diff --git a/lib/WiiExtension/extensions/GuitarExtension.cpp b/lib/WiiExtension/extensions/GuitarExtension.cpp index 6ce501dbd..2a90a9366 100644 --- a/lib/WiiExtension/extensions/GuitarExtension.cpp +++ b/lib/WiiExtension/extensions/GuitarExtension.cpp @@ -17,10 +17,10 @@ void GuitarExtension::init(uint8_t dataType) { _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].origin = WII_ANALOG_PRECISION_0; _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].destination = WII_ANALOG_PRECISION_3; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].origin = WII_ANALOG_PRECISION_0; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].destination = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_0; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].origin = WII_ANALOG_PRECISION_0; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].origin = WII_ANALOG_PRECISION_0; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].destination = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination = WII_ANALOG_PRECISION_3; @@ -34,10 +34,10 @@ void GuitarExtension::init(uint8_t dataType) { _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].origin = WII_ANALOG_PRECISION_3; _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].destination = WII_ANALOG_PRECISION_3; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].origin = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].destination = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].destination = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination = WII_ANALOG_PRECISION_3; @@ -51,10 +51,10 @@ void GuitarExtension::init(uint8_t dataType) { _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].origin = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].destination = WII_ANALOG_PRECISION_3; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].origin = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].destination = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].origin = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].destination = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination = WII_ANALOG_PRECISION_3; @@ -112,7 +112,7 @@ void GuitarExtension::process(uint8_t *inputData) { whammyBar = (inputData[3] & 0x1F); analogState[WiiAnalogs::WII_ANALOG_RIGHT_X] = (inputData[3] & 0x1F); - directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN] = !((inputData[4] & 0x40) >> 6); + buttons[WiiButtons::WII_BUTTON_DOWN] = !((inputData[4] & 0x40) >> 6); buttons[WiiButtons::WII_BUTTON_MINUS] = !((inputData[4] & 0x10) >> 4); buttons[WiiButtons::WII_BUTTON_PLUS] = !((inputData[4] & 0x04) >> 2); @@ -122,7 +122,7 @@ void GuitarExtension::process(uint8_t *inputData) { buttons[GuitarButtons::GUITAR_GREEN] = !((inputData[5] & 0x10) >> 4); buttons[GuitarButtons::GUITAR_YELLOW] = !((inputData[5] & 0x08) >> 3); buttons[GuitarButtons::GUITAR_PEDAL] = !((inputData[5] & 0x04) >> 2); - directionalPad[WiiDirectionalPad::WII_DIRECTION_UP] = !((inputData[5] & 0x01) >> 0); + buttons[WiiButtons::WII_BUTTON_UP] = !((inputData[5] & 0x01) >> 0); isTouched = (touchBar != WII_GUITAR_TOUCHPAD_NONE); @@ -135,7 +135,7 @@ void GuitarExtension::process(uint8_t *inputData) { buttons[GuitarButtons::GUITAR_YELLOW] = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_YELLOW,WII_GUITAR_TOUCHPAD_BLUE)); buttons[GuitarButtons::GUITAR_BLUE] = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_BLUE,WII_GUITAR_TOUCHPAD_ORANGE)); buttons[GuitarButtons::GUITAR_ORANGE] = (TOUCH_BETWEEN_RANGE(touchBar,WII_GUITAR_TOUCHPAD_ORANGE,WII_GUITAR_TOUCHPAD_MAX)); - directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN] = isTouched; + buttons[WiiButtons::WII_BUTTON_DOWN] = isTouched; } } else if (getDataType() == WII_DATA_TYPE_2) { analogState[WiiAnalogs::WII_ANALOG_LEFT_X] = ((inputData[0] << 2) | ((inputData[4] & 0x03) >> 0)); @@ -146,7 +146,7 @@ void GuitarExtension::process(uint8_t *inputData) { whammyBar = (inputData[6] & 0xFF); analogState[WiiAnalogs::WII_ANALOG_RIGHT_X] = (inputData[6] & 0xFF); - directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN] = !((inputData[7] & 0x40) >> 6); + buttons[WiiButtons::WII_BUTTON_DOWN] = !((inputData[7] & 0x40) >> 6); buttons[WiiButtons::WII_BUTTON_MINUS] = !((inputData[7] & 0x10) >> 4); buttons[WiiButtons::WII_BUTTON_PLUS] = !((inputData[7] & 0x04) >> 2); @@ -156,7 +156,7 @@ void GuitarExtension::process(uint8_t *inputData) { buttons[GuitarButtons::GUITAR_GREEN] = !((inputData[8] & 0x10) >> 4); buttons[GuitarButtons::GUITAR_YELLOW] = !((inputData[8] & 0x08) >> 3); buttons[GuitarButtons::GUITAR_PEDAL] = !((inputData[8] & 0x04) >> 2); - directionalPad[WiiDirectionalPad::WII_DIRECTION_UP] = !((inputData[8] & 0x01) >> 0); + buttons[WiiButtons::WII_BUTTON_UP] = !((inputData[8] & 0x01) >> 0); } else if (getDataType() == WII_DATA_TYPE_3) { analogState[WiiAnalogs::WII_ANALOG_LEFT_X] = (inputData[0] & 0xFF); analogState[WiiAnalogs::WII_ANALOG_LEFT_Y] = (inputData[2] & 0xFF); @@ -166,7 +166,7 @@ void GuitarExtension::process(uint8_t *inputData) { whammyBar = (inputData[5] & 0xFF); analogState[WiiAnalogs::WII_ANALOG_RIGHT_X] = (inputData[5] & 0xFF); - directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN] = !((inputData[6] & 0x40) >> 6); + buttons[WiiButtons::WII_BUTTON_DOWN] = !((inputData[6] & 0x40) >> 6); buttons[WiiButtons::WII_BUTTON_MINUS] = !((inputData[6] & 0x10) >> 4); buttons[WiiButtons::WII_BUTTON_PLUS] = !((inputData[6] & 0x04) >> 2); @@ -176,7 +176,7 @@ void GuitarExtension::process(uint8_t *inputData) { buttons[GuitarButtons::GUITAR_GREEN] = !((inputData[7] & 0x10) >> 4); buttons[GuitarButtons::GUITAR_YELLOW] = !((inputData[7] & 0x08) >> 3); buttons[GuitarButtons::GUITAR_PEDAL] = !((inputData[7] & 0x04) >> 2); - directionalPad[WiiDirectionalPad::WII_DIRECTION_UP] = !((inputData[7] & 0x01) >> 0); + buttons[WiiButtons::WII_BUTTON_UP] = !((inputData[7] & 0x01) >> 0); } } #if WII_EXTENSION_DEBUG==true diff --git a/lib/WiiExtension/extensions/TurntableExtension.cpp b/lib/WiiExtension/extensions/TurntableExtension.cpp index 137f458b5..79ecde516 100644 --- a/lib/WiiExtension/extensions/TurntableExtension.cpp +++ b/lib/WiiExtension/extensions/TurntableExtension.cpp @@ -14,10 +14,10 @@ void TurntableExtension::init(uint8_t dataType) { _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].origin = WII_ANALOG_PRECISION_1; _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_Y].destination = WII_ANALOG_PRECISION_3; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].origin = WII_ANALOG_PRECISION_0; // 32 - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].destination = WII_ANALOG_PRECISION_2; - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].origin = WII_ANALOG_PRECISION_0; // 32 - _analogPrecision[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].origin = WII_ANALOG_PRECISION_0; // 32 + _analogPrecision[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].destination = WII_ANALOG_PRECISION_2; + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].origin = WII_ANALOG_PRECISION_0; // 32 + _analogPrecision[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].destination = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].origin = WII_ANALOG_PRECISION_2; _analogPrecision[WiiAnalogs::WII_ANALOG_CALIBRATION_PRECISION].destination = WII_ANALOG_PRECISION_3; @@ -42,13 +42,13 @@ void TurntableExtension::init(uint8_t dataType) { _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].maximum = 256; _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_Y].useOffset = false; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].minimum = 1; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].center = WII_TURNTABLE_TRIGGER_MAX/2; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT].maximum = WII_TURNTABLE_TRIGGER_MAX; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].minimum = 1; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].center = WII_TURNTABLE_TRIGGER_MAX/2; + _analogCalibration[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER].maximum = WII_TURNTABLE_TRIGGER_MAX; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].minimum = 1; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].center = WII_TURNTABLE_TRIGGER_MAX/2; - _analogCalibration[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT].maximum = WII_TURNTABLE_TRIGGER_MAX; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].minimum = 1; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].center = WII_TURNTABLE_TRIGGER_MAX/2; + _analogCalibration[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER].maximum = WII_TURNTABLE_TRIGGER_MAX; } void TurntableExtension::process(uint8_t *inputData) { @@ -56,27 +56,27 @@ void TurntableExtension::process(uint8_t *inputData) { analogState[WiiAnalogs::WII_ANALOG_LEFT_X] = (inputData[0] & 0x3F); analogState[WiiAnalogs::WII_ANALOG_LEFT_Y] = (inputData[1] & 0x3F); - analogState[TurntableAnalogs::TURNTABLE_LEFT] = (((inputData[4] & 0x00) << 5) | ((inputData[3] & 0x1F) >> 0)); - analogState[TurntableAnalogs::TURNTABLE_RIGHT] = (((inputData[0] & 0xC0) >> 3) | ((inputData[1] & 0xC0) >> 5) | ((inputData[2] & 0x80) >> 7)); - analogState[TurntableAnalogs::TURNTABLE_EFFECTS] = (((inputData[2] & 0x60) >> 2) | ((inputData[3] & 0xE0) >> 5)); - analogState[TurntableAnalogs::TURNTABLE_CROSSFADE] = ((inputData[2] & 0x1E) >> 1); + analogState[TurntableAnalogs::TURNTABLE_LEFT] = (((inputData[4] & 0x00) << 5) | ((inputData[3] & 0x1F) >> 0)); + analogState[TurntableAnalogs::TURNTABLE_RIGHT] = (((inputData[0] & 0xC0) >> 3) | ((inputData[1] & 0xC0) >> 5) | ((inputData[2] & 0x80) >> 7)); + analogState[TurntableAnalogs::TURNTABLE_EFFECTS] = (((inputData[2] & 0x60) >> 2) | ((inputData[3] & 0xE0) >> 5)); + analogState[TurntableAnalogs::TURNTABLE_CROSSFADE] = ((inputData[2] & 0x1E) >> 1); // turntables report 0-5 for CW, 27-32 for CCW (as unsigned) - analogState[TurntableAnalogs::TURNTABLE_LEFT] = ((analogState[TurntableAnalogs::TURNTABLE_LEFT] < 15) ? WII_TURNTABLE_MAX_PRECISION-analogState[TurntableAnalogs::TURNTABLE_LEFT] : WII_TURNTABLE_MAX_PRECISION+(WII_TURNTABLE_MAX_PRECISION-analogState[TurntableAnalogs::TURNTABLE_LEFT])); - analogState[TurntableAnalogs::TURNTABLE_RIGHT] = ((analogState[TurntableAnalogs::TURNTABLE_RIGHT] < 15) ? WII_TURNTABLE_MAX_PRECISION-analogState[TurntableAnalogs::TURNTABLE_RIGHT] : WII_TURNTABLE_MAX_PRECISION+(WII_TURNTABLE_MAX_PRECISION-analogState[TurntableAnalogs::TURNTABLE_RIGHT])); + analogState[TurntableAnalogs::TURNTABLE_LEFT] = ((analogState[TurntableAnalogs::TURNTABLE_LEFT] < 15) ? WII_TURNTABLE_MAX_PRECISION-analogState[TurntableAnalogs::TURNTABLE_LEFT] : WII_TURNTABLE_MAX_PRECISION+(WII_TURNTABLE_MAX_PRECISION-analogState[TurntableAnalogs::TURNTABLE_LEFT])); + analogState[TurntableAnalogs::TURNTABLE_RIGHT] = ((analogState[TurntableAnalogs::TURNTABLE_RIGHT] < 15) ? WII_TURNTABLE_MAX_PRECISION-analogState[TurntableAnalogs::TURNTABLE_RIGHT] : WII_TURNTABLE_MAX_PRECISION+(WII_TURNTABLE_MAX_PRECISION-analogState[TurntableAnalogs::TURNTABLE_RIGHT])); buttons[WiiButtons::WII_BUTTON_MINUS] = !((inputData[4] & 0x10) >> 4); buttons[WiiButtons::WII_BUTTON_PLUS] = !((inputData[4] & 0x04) >> 2); - buttons[TurntableButtons::TURNTABLE_RIGHT_GREEN] = !((inputData[5] & 0x20) >> 5); - buttons[TurntableButtons::TURNTABLE_RIGHT_RED] = !((inputData[4] & 0x02) >> 1); - buttons[TurntableButtons::TURNTABLE_RIGHT_BLUE] = !((inputData[5] & 0x04) >> 2); + buttons[TurntableButtons::TURNTABLE_RIGHT_GREEN] = !((inputData[5] & 0x20) >> 5); + buttons[TurntableButtons::TURNTABLE_RIGHT_RED] = !((inputData[4] & 0x02) >> 1); + buttons[TurntableButtons::TURNTABLE_RIGHT_BLUE] = !((inputData[5] & 0x04) >> 2); - buttons[TurntableButtons::TURNTABLE_EUPHORIA] = !((inputData[5] & 0x10) >> 4); + buttons[TurntableButtons::TURNTABLE_EUPHORIA] = !((inputData[5] & 0x10) >> 4); - directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_GREEN] = !((inputData[5] & 0x08) >> 3); - directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_RED] = !((inputData[4] & 0x20) >> 5); - directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_BLUE] = !((inputData[5] & 0x80) >> 7); + buttons[TurntableButtons::TURNTABLE_LEFT_GREEN] = !((inputData[5] & 0x08) >> 3); + buttons[TurntableButtons::TURNTABLE_LEFT_RED] = !((inputData[4] & 0x20) >> 5); + buttons[TurntableButtons::TURNTABLE_LEFT_BLUE] = !((inputData[5] & 0x80) >> 7); #if WII_EXTENSION_DEBUG==true //printf("LR=%1d LG=%1d LB=%1d\n", buttons[TurntableButtons::TURNTABLE_LEFT_RED], buttons[TurntableButtons::TURNTABLE_LEFT_GREEN], buttons[TurntableButtons::TURNTABLE_LEFT_BLUE]); diff --git a/lib/WiiExtension/extensions/TurntableExtension.h b/lib/WiiExtension/extensions/TurntableExtension.h index b7677daf4..87d3fa883 100644 --- a/lib/WiiExtension/extensions/TurntableExtension.h +++ b/lib/WiiExtension/extensions/TurntableExtension.h @@ -7,20 +7,17 @@ enum TurntableButtons { TURNTABLE_RIGHT_GREEN = WiiButtons::WII_BUTTON_Y, TURNTABLE_RIGHT_RED = WiiButtons::WII_BUTTON_X, TURNTABLE_RIGHT_BLUE = WiiButtons::WII_BUTTON_A, - TURNTABLE_EUPHORIA = WiiButtons::WII_BUTTON_ZR -}; - -enum TurntableDirectionalPad { - TURNTABLE_LEFT_GREEN = WiiDirectionalPad::WII_DIRECTION_LEFT, - TURNTABLE_LEFT_RED = WiiDirectionalPad::WII_DIRECTION_UP, - TURNTABLE_LEFT_BLUE = WiiDirectionalPad::WII_DIRECTION_RIGHT, + TURNTABLE_EUPHORIA = WiiButtons::WII_BUTTON_ZR, + TURNTABLE_LEFT_GREEN = WiiButtons::WII_BUTTON_LEFT, + TURNTABLE_LEFT_RED = WiiButtons::WII_BUTTON_UP, + TURNTABLE_LEFT_BLUE = WiiButtons::WII_BUTTON_RIGHT }; enum TurntableAnalogs { TURNTABLE_LEFT = WiiAnalogs::WII_ANALOG_RIGHT_Y, TURNTABLE_RIGHT = WiiAnalogs::WII_ANALOG_RIGHT_X, - TURNTABLE_EFFECTS = WiiAnalogs::WII_ANALOG_TRIGGER_LEFT, - TURNTABLE_CROSSFADE = WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT + TURNTABLE_EFFECTS = WiiAnalogs::WII_ANALOG_LEFT_TRIGGER, + TURNTABLE_CROSSFADE = WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER }; #define WII_TURNTABLE_GATE_SIZE 97 diff --git a/proto/config.proto b/proto/config.proto index 4931d6f46..d38b87a21 100644 --- a/proto/config.proto +++ b/proto/config.proto @@ -433,11 +433,121 @@ message PSPassthroughOptions message WiiOptions { + message AnalogAxis + { + optional int32 axisType = 1; + optional int32 minRange = 2; + optional int32 maxRange = 3; + } + + message StickOptions + { + optional AnalogAxis x = 1; + optional AnalogAxis y = 2; + } + + message NunchukOptions + { + optional int32 buttonC = 1; + optional int32 buttonZ = 2; + optional StickOptions stick = 3; + } + + message ClassicOptions + { + optional int32 buttonA = 1; + optional int32 buttonB = 2; + optional int32 buttonX = 3; + optional int32 buttonY = 4; + optional int32 buttonL = 5; + optional int32 buttonZL = 6; + optional int32 buttonR = 7; + optional int32 buttonZR = 8; + optional int32 buttonMinus = 9; + optional int32 buttonPlus = 10; + optional int32 buttonHome = 11; + optional int32 buttonUp = 12; + optional int32 buttonDown = 13; + optional int32 buttonLeft = 14; + optional int32 buttonRight = 15; + optional StickOptions rightStick = 17; + optional StickOptions leftStick = 16; + optional AnalogAxis leftTrigger = 18; + optional AnalogAxis rightTrigger = 19; + } + + message TaikoOptions + { + optional int32 buttonKatLeft = 1; + optional int32 buttonKatRight = 2; + optional int32 buttonDonLeft = 3; + optional int32 buttonDonRight = 4; + } + + message GuitarOptions + { + optional int32 buttonRed = 1; + optional int32 buttonGreen = 2; + optional int32 buttonYellow = 3; + optional int32 buttonBlue = 4; + optional int32 buttonOrange = 5; + optional int32 buttonPedal = 6; + optional int32 buttonMinus = 7; + optional int32 buttonPlus = 8; + optional int32 strumUp = 9; + optional int32 strumDown = 10; + optional StickOptions stick = 11; + optional AnalogAxis whammyBar = 12; + } + + message DrumOptions + { + optional int32 buttonRed = 1; + optional int32 buttonGreen = 2; + optional int32 buttonYellow = 3; + optional int32 buttonBlue = 4; + optional int32 buttonOrange = 5; + optional int32 buttonPedal = 6; + optional int32 buttonMinus = 7; + optional int32 buttonPlus = 8; + optional StickOptions stick = 9; + } + + message TurntableOptions + { + optional int32 buttonLeftRed = 1; + optional int32 buttonLeftGreen = 2; + optional int32 buttonLeftBlue = 3; + optional int32 buttonRightRed = 4; + optional int32 buttonRightGreen = 5; + optional int32 buttonRightBlue = 6; + optional int32 buttonMinus = 7; + optional int32 buttonPlus = 8; + optional int32 buttonEuphoria = 9; + optional StickOptions stick = 10; + optional AnalogAxis leftTurntable = 11; + optional AnalogAxis rightTurntable = 12; + optional AnalogAxis effects = 13; + optional AnalogAxis fader = 14; + } + + message ControllerOptions + { + optional NunchukOptions nunchuk = 1; + optional ClassicOptions classic = 2; + optional TaikoOptions taiko = 3; + optional GuitarOptions guitar = 4; + optional DrumOptions drum = 5; + optional TurntableOptions turntable = 6; + } + optional bool enabled = 1; optional int32 i2cBlock = 2; optional int32 i2cSDAPin = 3; optional int32 i2cSCLPin = 4; optional int32 i2cSpeed = 5; + + optional ControllerOptions controllers = 6; } message SNESOptions diff --git a/src/addons/wiiext.cpp b/src/addons/wiiext.cpp index 109690ad3..c9dd8c374 100644 --- a/src/addons/wiiext.cpp +++ b/src/addons/wiiext.cpp @@ -32,6 +32,8 @@ void WiiExtensionInput::setup() { wii->begin(); wii->start(); + reloadConfig(); + // Run during setup to catch boot selection mode wii->poll(); @@ -39,8 +41,6 @@ void WiiExtensionInput::setup() { } void WiiExtensionInput::process() { - Gamepad * gamepad = Storage::getInstance().GetGamepad(); - if (nextTimer < getMillis()) { wii->poll(); @@ -50,37 +50,40 @@ void WiiExtensionInput::process() { } if (currentConfig != NULL) { - gamepad->state.lx = bounds(leftX,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - gamepad->state.ly = bounds(leftY,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - gamepad->state.rx = bounds(rightX,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - gamepad->state.ry = bounds(rightY,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - - if ((wii->extensionType == WII_EXTENSION_CLASSIC) || (wii->extensionType == WII_EXTENSION_TURNTABLE)) { - gamepad->hasAnalogTriggers = true; - gamepad->state.lt = triggerLeft; - gamepad->state.rt = triggerRight; - } else { - gamepad->hasAnalogTriggers = false; - } - - if (buttonC) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_C]; - if (buttonZ) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_Z]; - - if (buttonA) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_A]; - if (buttonB) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_B]; - if (buttonX) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_X]; - if (buttonY) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_Y]; - if (buttonL) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_L]; - if (buttonZL) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_ZL]; - if (buttonR) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_R]; - if (buttonZR) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_ZR]; - if (buttonSelect) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_MINUS]; - if (buttonStart) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_PLUS]; - if (buttonHome) gamepad->state.buttons |= currentConfig->buttonMap[WiiButtons::WII_BUTTON_HOME]; - if (dpadUp) gamepad->state.dpad |= currentConfig->dpadMap[WiiDirectionalPad::WII_DIRECTION_UP]; - if (dpadDown) gamepad->state.dpad |= currentConfig->dpadMap[WiiDirectionalPad::WII_DIRECTION_DOWN]; - if (dpadLeft) gamepad->state.dpad |= currentConfig->dpadMap[WiiDirectionalPad::WII_DIRECTION_LEFT]; - if (dpadRight) gamepad->state.dpad |= currentConfig->dpadMap[WiiDirectionalPad::WII_DIRECTION_RIGHT]; + queueAnalogChange(WiiAnalogs::WII_ANALOG_LEFT_X, leftX, lastLeftX); + queueAnalogChange(WiiAnalogs::WII_ANALOG_LEFT_Y, leftY, lastLeftY); + queueAnalogChange(WiiAnalogs::WII_ANALOG_RIGHT_X, rightX, lastRightX); + queueAnalogChange(WiiAnalogs::WII_ANALOG_RIGHT_Y, rightY, lastRightY); + queueAnalogChange(WiiAnalogs::WII_ANALOG_LEFT_TRIGGER, triggerLeft, lastTriggerLeft); + queueAnalogChange(WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER, triggerRight, lastTriggerRight); + updateAnalogState(); + + setButtonState(buttonC, WiiButtons::WII_BUTTON_C); + setButtonState(buttonZ, WiiButtons::WII_BUTTON_Z); + + setButtonState(buttonA, WiiButtons::WII_BUTTON_A); + setButtonState(buttonB, WiiButtons::WII_BUTTON_B); + setButtonState(buttonX, WiiButtons::WII_BUTTON_X); + setButtonState(buttonY, WiiButtons::WII_BUTTON_Y); + setButtonState(buttonL, WiiButtons::WII_BUTTON_L); + setButtonState(buttonZL, WiiButtons::WII_BUTTON_ZL); + setButtonState(buttonR, WiiButtons::WII_BUTTON_R); + setButtonState(buttonZR, WiiButtons::WII_BUTTON_ZR); + setButtonState(buttonSelect, WiiButtons::WII_BUTTON_MINUS); + setButtonState(buttonStart, WiiButtons::WII_BUTTON_PLUS); + setButtonState(buttonHome, WiiButtons::WII_BUTTON_HOME); + + setButtonState(dpadUp, WiiButtons::WII_BUTTON_UP); + setButtonState(dpadDown, WiiButtons::WII_BUTTON_DOWN); + setButtonState(dpadLeft, WiiButtons::WII_BUTTON_LEFT); + setButtonState(dpadRight, WiiButtons::WII_BUTTON_RIGHT); + + if (lastLeftX != leftX) lastLeftX = leftX; + if (lastLeftY != leftY) lastLeftY = leftY; + if (lastRightX != rightX) lastRightX = rightX; + if (lastRightY != rightY) lastRightY = rightY; + if (lastTriggerLeft != triggerLeft) lastTriggerLeft = triggerLeft; + if (lastTriggerRight != triggerRight) lastTriggerRight = triggerRight; } } @@ -106,8 +109,8 @@ void WiiExtensionInput::update() { buttonZ = wii->getController()->buttons[WiiButtons::WII_BUTTON_Z]; buttonC = wii->getController()->buttons[WiiButtons::WII_BUTTON_C]; - leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,1023,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); - leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],1023,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); + leftY = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_Y],WII_ANALOG_PRECISION_3,0,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); rightX = GAMEPAD_JOYSTICK_MID; rightY = GAMEPAD_JOYSTICK_MID; @@ -125,14 +128,14 @@ void WiiExtensionInput::update() { buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; buttonHome = wii->getController()->buttons[WiiButtons::WII_BUTTON_HOME]; - dpadUp = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_UP]; - dpadDown = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN]; - dpadLeft = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_LEFT]; - dpadRight = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_RIGHT]; + dpadUp = wii->getController()->buttons[WiiButtons::WII_BUTTON_UP]; + dpadDown = wii->getController()->buttons[WiiButtons::WII_BUTTON_DOWN]; + dpadLeft = wii->getController()->buttons[WiiButtons::WII_BUTTON_LEFT]; + dpadRight = wii->getController()->buttons[WiiButtons::WII_BUTTON_RIGHT]; if (wii->extensionType == WII_EXTENSION_CLASSIC) { - triggerLeft = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_TRIGGER_LEFT]; - triggerRight = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_TRIGGER_RIGHT]; + triggerLeft = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_TRIGGER]; + triggerRight = wii->getController()->analogState[WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER]; } leftX = map(wii->getController()->analogState[WiiAnalogs::WII_ANALOG_LEFT_X],0,WII_ANALOG_PRECISION_3,GAMEPAD_JOYSTICK_MIN,GAMEPAD_JOYSTICK_MAX); @@ -143,8 +146,8 @@ void WiiExtensionInput::update() { buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; - dpadUp = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_UP]; - dpadDown = wii->getController()->directionalPad[WiiDirectionalPad::WII_DIRECTION_DOWN]; + dpadUp = wii->getController()->buttons[WiiButtons::WII_BUTTON_UP]; + dpadDown = wii->getController()->buttons[WiiButtons::WII_BUTTON_DOWN]; buttonB = wii->getController()->buttons[GuitarButtons::GUITAR_GREEN]; buttonA = wii->getController()->buttons[GuitarButtons::GUITAR_RED]; @@ -191,9 +194,9 @@ void WiiExtensionInput::update() { buttonSelect = wii->getController()->buttons[WiiButtons::WII_BUTTON_MINUS]; buttonStart = wii->getController()->buttons[WiiButtons::WII_BUTTON_PLUS]; - dpadLeft = wii->getController()->directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_GREEN]; - dpadUp = wii->getController()->directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_RED]; - dpadRight = wii->getController()->directionalPad[TurntableDirectionalPad::TURNTABLE_LEFT_BLUE]; + dpadLeft = wii->getController()->buttons[TurntableButtons::TURNTABLE_LEFT_GREEN]; + dpadUp = wii->getController()->buttons[TurntableButtons::TURNTABLE_LEFT_RED]; + dpadRight = wii->getController()->buttons[TurntableButtons::TURNTABLE_LEFT_BLUE]; buttonY = wii->getController()->buttons[TurntableButtons::TURNTABLE_RIGHT_GREEN]; buttonX = wii->getController()->buttons[TurntableButtons::TURNTABLE_RIGHT_RED]; @@ -212,4 +215,318 @@ void WiiExtensionInput::update() { } else { currentConfig = NULL; } +} + +void WiiExtensionInput::setControllerButton(uint16_t controllerID, uint16_t buttonID, uint32_t buttonMask) { + extensionConfigs[controllerID].buttonMap[buttonID] = buttonMask; +} + +void WiiExtensionInput::setControllerAnalog(uint16_t controllerID, uint16_t analogID, uint32_t axisType) { + extensionConfigs[controllerID].analogMap[analogID].axisType = axisType; +} + +void WiiExtensionInput::reloadConfig() { + const WiiOptions& wiiOptions = Storage::getInstance().getAddonOptions().wiiOptions; + + // digital mapping + setControllerButton(WII_EXTENSION_NUNCHUCK, WiiButtons::WII_BUTTON_C, wiiOptions.controllers.nunchuk.buttonC); + setControllerButton(WII_EXTENSION_NUNCHUCK, WiiButtons::WII_BUTTON_Z, wiiOptions.controllers.nunchuk.buttonZ); + + setControllerButton(WII_EXTENSION_CLASSIC, WiiButtons::WII_BUTTON_A, wiiOptions.controllers.classic.buttonA); + setControllerButton(WII_EXTENSION_CLASSIC, WiiButtons::WII_BUTTON_B, wiiOptions.controllers.classic.buttonB); + setControllerButton(WII_EXTENSION_CLASSIC, WiiButtons::WII_BUTTON_X, wiiOptions.controllers.classic.buttonX); + setControllerButton(WII_EXTENSION_CLASSIC, WiiButtons::WII_BUTTON_Y, wiiOptions.controllers.classic.buttonY); + setControllerButton(WII_EXTENSION_CLASSIC, WiiButtons::WII_BUTTON_L, wiiOptions.controllers.classic.buttonL); + setControllerButton(WII_EXTENSION_CLASSIC, WiiButtons::WII_BUTTON_ZL, wiiOptions.controllers.classic.buttonZL); + setControllerButton(WII_EXTENSION_CLASSIC, WiiButtons::WII_BUTTON_R, wiiOptions.controllers.classic.buttonR); + setControllerButton(WII_EXTENSION_CLASSIC, WiiButtons::WII_BUTTON_ZR, wiiOptions.controllers.classic.buttonZR); + setControllerButton(WII_EXTENSION_CLASSIC, WiiButtons::WII_BUTTON_MINUS, wiiOptions.controllers.classic.buttonMinus); + setControllerButton(WII_EXTENSION_CLASSIC, WiiButtons::WII_BUTTON_PLUS, wiiOptions.controllers.classic.buttonPlus); + setControllerButton(WII_EXTENSION_CLASSIC, WiiButtons::WII_BUTTON_HOME, wiiOptions.controllers.classic.buttonHome); + setControllerButton(WII_EXTENSION_CLASSIC, WiiButtons::WII_BUTTON_UP, wiiOptions.controllers.classic.buttonUp); + setControllerButton(WII_EXTENSION_CLASSIC, WiiButtons::WII_BUTTON_DOWN, wiiOptions.controllers.classic.buttonDown); + setControllerButton(WII_EXTENSION_CLASSIC, WiiButtons::WII_BUTTON_LEFT, wiiOptions.controllers.classic.buttonLeft); + setControllerButton(WII_EXTENSION_CLASSIC, WiiButtons::WII_BUTTON_RIGHT, wiiOptions.controllers.classic.buttonRight); + + setControllerButton(WII_EXTENSION_TAIKO, TaikoButtons::TATA_KAT_LEFT, wiiOptions.controllers.taiko.buttonKatLeft); + setControllerButton(WII_EXTENSION_TAIKO, TaikoButtons::TATA_KAT_RIGHT, wiiOptions.controllers.taiko.buttonKatRight); + setControllerButton(WII_EXTENSION_TAIKO, TaikoButtons::TATA_DON_LEFT, wiiOptions.controllers.taiko.buttonDonLeft); + setControllerButton(WII_EXTENSION_TAIKO, TaikoButtons::TATA_DON_RIGHT, wiiOptions.controllers.taiko.buttonDonRight); + + setControllerButton(WII_EXTENSION_GUITAR, GuitarButtons::GUITAR_RED, wiiOptions.controllers.guitar.buttonRed); + setControllerButton(WII_EXTENSION_GUITAR, GuitarButtons::GUITAR_GREEN, wiiOptions.controllers.guitar.buttonGreen); + setControllerButton(WII_EXTENSION_GUITAR, GuitarButtons::GUITAR_YELLOW, wiiOptions.controllers.guitar.buttonYellow); + setControllerButton(WII_EXTENSION_GUITAR, GuitarButtons::GUITAR_BLUE, wiiOptions.controllers.guitar.buttonBlue); + setControllerButton(WII_EXTENSION_GUITAR, GuitarButtons::GUITAR_ORANGE, wiiOptions.controllers.guitar.buttonOrange); + setControllerButton(WII_EXTENSION_GUITAR, GuitarButtons::GUITAR_PEDAL, wiiOptions.controllers.guitar.buttonPedal); + setControllerButton(WII_EXTENSION_GUITAR, WiiButtons::WII_BUTTON_MINUS, wiiOptions.controllers.guitar.buttonMinus); + setControllerButton(WII_EXTENSION_GUITAR, WiiButtons::WII_BUTTON_PLUS, wiiOptions.controllers.guitar.buttonPlus); + setControllerButton(WII_EXTENSION_GUITAR, WiiButtons::WII_BUTTON_UP, wiiOptions.controllers.guitar.strumUp); + setControllerButton(WII_EXTENSION_GUITAR, WiiButtons::WII_BUTTON_DOWN, wiiOptions.controllers.guitar.strumDown); + + setControllerButton(WII_EXTENSION_DRUMS, DrumButtons::DRUM_RED, wiiOptions.controllers.drum.buttonRed); + setControllerButton(WII_EXTENSION_DRUMS, DrumButtons::DRUM_GREEN, wiiOptions.controllers.drum.buttonGreen); + setControllerButton(WII_EXTENSION_DRUMS, DrumButtons::DRUM_YELLOW, wiiOptions.controllers.drum.buttonYellow); + setControllerButton(WII_EXTENSION_DRUMS, DrumButtons::DRUM_BLUE, wiiOptions.controllers.drum.buttonBlue); + setControllerButton(WII_EXTENSION_DRUMS, DrumButtons::DRUM_ORANGE, wiiOptions.controllers.drum.buttonOrange); + setControllerButton(WII_EXTENSION_DRUMS, DrumButtons::DRUM_PEDAL, wiiOptions.controllers.drum.buttonPedal); + setControllerButton(WII_EXTENSION_DRUMS, WiiButtons::WII_BUTTON_MINUS, wiiOptions.controllers.drum.buttonMinus); + setControllerButton(WII_EXTENSION_DRUMS, WiiButtons::WII_BUTTON_PLUS, wiiOptions.controllers.drum.buttonPlus); + + setControllerButton(WII_EXTENSION_TURNTABLE, TurntableButtons::TURNTABLE_LEFT_RED, wiiOptions.controllers.turntable.buttonLeftRed); + setControllerButton(WII_EXTENSION_TURNTABLE, TurntableButtons::TURNTABLE_LEFT_GREEN, wiiOptions.controllers.turntable.buttonLeftGreen); + setControllerButton(WII_EXTENSION_TURNTABLE, TurntableButtons::TURNTABLE_LEFT_BLUE, wiiOptions.controllers.turntable.buttonLeftBlue); + setControllerButton(WII_EXTENSION_TURNTABLE, TurntableButtons::TURNTABLE_RIGHT_RED, wiiOptions.controllers.turntable.buttonRightRed); + setControllerButton(WII_EXTENSION_TURNTABLE, TurntableButtons::TURNTABLE_RIGHT_GREEN, wiiOptions.controllers.turntable.buttonRightGreen); + setControllerButton(WII_EXTENSION_TURNTABLE, TurntableButtons::TURNTABLE_RIGHT_BLUE, wiiOptions.controllers.turntable.buttonRightBlue); + setControllerButton(WII_EXTENSION_TURNTABLE, TurntableButtons::TURNTABLE_EUPHORIA, wiiOptions.controllers.turntable.buttonEuphoria); + setControllerButton(WII_EXTENSION_TURNTABLE, WiiButtons::WII_BUTTON_MINUS, wiiOptions.controllers.turntable.buttonMinus); + setControllerButton(WII_EXTENSION_TURNTABLE, WiiButtons::WII_BUTTON_PLUS, wiiOptions.controllers.turntable.buttonPlus); + + // analog mapping + setControllerAnalog(WII_EXTENSION_NUNCHUCK, WiiAnalogs::WII_ANALOG_LEFT_X, wiiOptions.controllers.nunchuk.stick.x.axisType); + setControllerAnalog(WII_EXTENSION_NUNCHUCK, WiiAnalogs::WII_ANALOG_LEFT_Y, wiiOptions.controllers.nunchuk.stick.y.axisType); + + setControllerAnalog(WII_EXTENSION_CLASSIC, WiiAnalogs::WII_ANALOG_LEFT_X, wiiOptions.controllers.classic.leftStick.x.axisType); + setControllerAnalog(WII_EXTENSION_CLASSIC, WiiAnalogs::WII_ANALOG_LEFT_Y, wiiOptions.controllers.classic.leftStick.y.axisType); + setControllerAnalog(WII_EXTENSION_CLASSIC, WiiAnalogs::WII_ANALOG_RIGHT_X, wiiOptions.controllers.classic.rightStick.x.axisType); + setControllerAnalog(WII_EXTENSION_CLASSIC, WiiAnalogs::WII_ANALOG_RIGHT_Y, wiiOptions.controllers.classic.rightStick.y.axisType); + setControllerAnalog(WII_EXTENSION_CLASSIC, WiiAnalogs::WII_ANALOG_LEFT_TRIGGER, wiiOptions.controllers.classic.leftTrigger.axisType); + setControllerAnalog(WII_EXTENSION_CLASSIC, WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER, wiiOptions.controllers.classic.rightTrigger.axisType); + + setControllerAnalog(WII_EXTENSION_GUITAR, WiiAnalogs::WII_ANALOG_LEFT_X, wiiOptions.controllers.guitar.stick.x.axisType); + setControllerAnalog(WII_EXTENSION_GUITAR, WiiAnalogs::WII_ANALOG_LEFT_Y, wiiOptions.controllers.guitar.stick.y.axisType); + setControllerAnalog(WII_EXTENSION_GUITAR, WiiAnalogs::WII_ANALOG_RIGHT_X, wiiOptions.controllers.guitar.whammyBar.axisType); + + setControllerAnalog(WII_EXTENSION_DRUMS, WiiAnalogs::WII_ANALOG_LEFT_X, wiiOptions.controllers.drum.stick.x.axisType); + setControllerAnalog(WII_EXTENSION_DRUMS, WiiAnalogs::WII_ANALOG_LEFT_Y, wiiOptions.controllers.drum.stick.y.axisType); + + setControllerAnalog(WII_EXTENSION_TURNTABLE, WiiAnalogs::WII_ANALOG_LEFT_X, wiiOptions.controllers.turntable.stick.x.axisType); + setControllerAnalog(WII_EXTENSION_TURNTABLE, WiiAnalogs::WII_ANALOG_LEFT_Y, wiiOptions.controllers.turntable.stick.y.axisType); + setControllerAnalog(WII_EXTENSION_TURNTABLE, WiiAnalogs::WII_ANALOG_RIGHT_X, wiiOptions.controllers.turntable.leftTurntable.axisType); + setControllerAnalog(WII_EXTENSION_TURNTABLE, WiiAnalogs::WII_ANALOG_RIGHT_Y, wiiOptions.controllers.turntable.rightTurntable.axisType); + setControllerAnalog(WII_EXTENSION_TURNTABLE, WiiAnalogs::WII_ANALOG_LEFT_TRIGGER, wiiOptions.controllers.turntable.effects.axisType); + setControllerAnalog(WII_EXTENSION_TURNTABLE, WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER, wiiOptions.controllers.turntable.fader.axisType); +} + +void WiiExtensionInput::setButtonState(bool buttonState, uint16_t buttonMask) { + Gamepad * gamepad = Storage::getInstance().GetGamepad(); + + if (buttonState) { + if (currentConfig->buttonMap[buttonMask] > GAMEPAD_MASK_A2) { + if (((currentConfig->buttonMap[buttonMask] >> 16) & GAMEPAD_MASK_UP) == GAMEPAD_MASK_UP) gamepad->state.dpad |= ((currentConfig->buttonMap[buttonMask] >> 16) & GAMEPAD_MASK_UP); + if (((currentConfig->buttonMap[buttonMask] >> 16) & GAMEPAD_MASK_DOWN) == GAMEPAD_MASK_DOWN) gamepad->state.dpad |= ((currentConfig->buttonMap[buttonMask] >> 16) & GAMEPAD_MASK_DOWN); + if (((currentConfig->buttonMap[buttonMask] >> 16) & GAMEPAD_MASK_LEFT) == GAMEPAD_MASK_LEFT) gamepad->state.dpad |= ((currentConfig->buttonMap[buttonMask] >> 16) & GAMEPAD_MASK_LEFT); + if (((currentConfig->buttonMap[buttonMask] >> 16) & GAMEPAD_MASK_RIGHT) == GAMEPAD_MASK_RIGHT) gamepad->state.dpad |= ((currentConfig->buttonMap[buttonMask] >> 16) & GAMEPAD_MASK_RIGHT); + } else { + gamepad->state.buttons |= currentConfig->buttonMap[buttonMask]; + } + } +} + +void WiiExtensionInput::queueAnalogChange(uint16_t analogInput, uint16_t analogValue, uint16_t lastAnalogValue) { + if (analogInput != lastAnalogValue) analogChanges[currentConfig->analogMap[analogInput].axisType].push_back({analogInput, analogValue}); +} + +void WiiExtensionInput::updateAnalogState() { + Gamepad * gamepad = Storage::getInstance().GetGamepad(); + gamepad->hasAnalogTriggers = true; + + uint16_t axisType; + uint16_t analogInput; + uint16_t analogValue; + + uint16_t axisToChange; + uint16_t adjustedValue; + + uint16_t minValue = GAMEPAD_JOYSTICK_MIN; + uint16_t midValue = GAMEPAD_JOYSTICK_MID; + uint16_t maxValue = GAMEPAD_JOYSTICK_MAX; + + std::map> axesOfChange = { + {WII_ANALOG_TYPE_LEFT_STICK_X,{}}, + {WII_ANALOG_TYPE_LEFT_STICK_Y,{}}, + {WII_ANALOG_TYPE_RIGHT_STICK_X,{}}, + {WII_ANALOG_TYPE_RIGHT_STICK_Y,{}}, + {WII_ANALOG_TYPE_LEFT_TRIGGER,{}}, + {WII_ANALOG_TYPE_RIGHT_TRIGGER,{}} + }; + + for (auto currChange = analogChanges.begin(); currChange != analogChanges.end(); ++currChange) { + if (!currChange->second.empty()) { + // this analog type has changes. get the last one, use it, and clear the list. + axisType = currChange->first; + analogInput = currChange->second.front().analogInput; + analogValue = getAverage(currChange->second); + currChange->second.clear(); + + axisToChange = WII_ANALOG_TYPE_NONE; + adjustedValue = 0; + + // define ranges + switch (analogInput) { + case WiiAnalogs::WII_ANALOG_LEFT_X: + case WiiAnalogs::WII_ANALOG_LEFT_Y: + case WiiAnalogs::WII_ANALOG_RIGHT_X: + case WiiAnalogs::WII_ANALOG_RIGHT_Y: + minValue = GAMEPAD_JOYSTICK_MIN; + midValue = GAMEPAD_JOYSTICK_MID; + maxValue = GAMEPAD_JOYSTICK_MAX; + break; + case WiiAnalogs::WII_ANALOG_LEFT_TRIGGER: + case WiiAnalogs::WII_ANALOG_RIGHT_TRIGGER: + minValue = GAMEPAD_TRIGGER_MIN; + midValue = GAMEPAD_TRIGGER_MID; + maxValue = GAMEPAD_TRIGGER_MAX; + break; + } + + switch (axisType) { + case WII_ANALOG_TYPE_LEFT_STICK_X: + axisToChange = WII_ANALOG_TYPE_LEFT_STICK_X; + adjustedValue = bounds(analogValue,minValue,maxValue); + break; + case WII_ANALOG_TYPE_LEFT_STICK_Y: + axisToChange = WII_ANALOG_TYPE_LEFT_STICK_Y; + adjustedValue = bounds(analogValue,minValue,maxValue); + break; + case WII_ANALOG_TYPE_RIGHT_STICK_X: + axisToChange = WII_ANALOG_TYPE_RIGHT_STICK_X; + adjustedValue = bounds(analogValue,minValue,maxValue); + break; + case WII_ANALOG_TYPE_RIGHT_STICK_Y: + axisToChange = WII_ANALOG_TYPE_RIGHT_STICK_Y; + adjustedValue = bounds(analogValue,minValue,maxValue); + break; + case WII_ANALOG_TYPE_DPAD_X: + if (analogValue < midValue/2) gamepad->state.dpad |= GAMEPAD_MASK_LEFT; + if (analogValue > midValue+(midValue/2)) gamepad->state.dpad |= GAMEPAD_MASK_RIGHT; + break; + case WII_ANALOG_TYPE_DPAD_Y: + if (analogValue < midValue/2) gamepad->state.dpad |= GAMEPAD_MASK_UP; + if (analogValue > midValue+(midValue/2)) gamepad->state.dpad |= GAMEPAD_MASK_DOWN; + break; + case WII_ANALOG_TYPE_LEFT_TRIGGER: + axisToChange = WII_ANALOG_TYPE_LEFT_TRIGGER; + adjustedValue = bounds(analogValue,minValue,maxValue); + break; + case WII_ANALOG_TYPE_RIGHT_TRIGGER: + axisToChange = WII_ANALOG_TYPE_RIGHT_TRIGGER; + adjustedValue = bounds(analogValue,minValue,maxValue); + break; + // advanced types + case WII_ANALOG_TYPE_LEFT_STICK_X_PLUS: + axisToChange = WII_ANALOG_TYPE_LEFT_STICK_X; + adjustedValue = map(analogValue,minValue,maxValue,GAMEPAD_JOYSTICK_MID,GAMEPAD_JOYSTICK_MAX); + minValue = GAMEPAD_JOYSTICK_MID; + maxValue = GAMEPAD_JOYSTICK_MAX; + break; + case WII_ANALOG_TYPE_LEFT_STICK_X_MINUS: + axisToChange = WII_ANALOG_TYPE_LEFT_STICK_X; + adjustedValue = map(analogValue,minValue,maxValue,GAMEPAD_JOYSTICK_MID,GAMEPAD_JOYSTICK_MIN); + minValue = GAMEPAD_JOYSTICK_MIN; + maxValue = GAMEPAD_JOYSTICK_MID; + break; + case WII_ANALOG_TYPE_LEFT_STICK_Y_PLUS: + axisToChange = WII_ANALOG_TYPE_LEFT_STICK_Y; + adjustedValue = map(analogValue,minValue,maxValue,GAMEPAD_JOYSTICK_MID,GAMEPAD_JOYSTICK_MAX); + minValue = GAMEPAD_JOYSTICK_MID; + maxValue = GAMEPAD_JOYSTICK_MAX; + break; + case WII_ANALOG_TYPE_LEFT_STICK_Y_MINUS: + axisToChange = WII_ANALOG_TYPE_LEFT_STICK_Y; + adjustedValue = map(analogValue,minValue,maxValue,GAMEPAD_JOYSTICK_MID,GAMEPAD_JOYSTICK_MIN); + minValue = GAMEPAD_JOYSTICK_MIN; + maxValue = GAMEPAD_JOYSTICK_MID; + break; + case WII_ANALOG_TYPE_RIGHT_STICK_X_PLUS: + axisToChange = WII_ANALOG_TYPE_RIGHT_STICK_X; + adjustedValue = map(analogValue,minValue,maxValue,GAMEPAD_JOYSTICK_MID,GAMEPAD_JOYSTICK_MAX); + minValue = GAMEPAD_JOYSTICK_MID; + maxValue = GAMEPAD_JOYSTICK_MAX; + break; + case WII_ANALOG_TYPE_RIGHT_STICK_X_MINUS: + axisToChange = WII_ANALOG_TYPE_RIGHT_STICK_X; + adjustedValue = map(analogValue,minValue,maxValue,GAMEPAD_JOYSTICK_MID,GAMEPAD_JOYSTICK_MIN); + minValue = GAMEPAD_JOYSTICK_MIN; + maxValue = GAMEPAD_JOYSTICK_MID; + break; + case WII_ANALOG_TYPE_RIGHT_STICK_Y_PLUS: + axisToChange = WII_ANALOG_TYPE_RIGHT_STICK_Y; + adjustedValue = map(analogValue,minValue,maxValue,GAMEPAD_JOYSTICK_MID,GAMEPAD_JOYSTICK_MAX); + minValue = GAMEPAD_JOYSTICK_MID; + maxValue = GAMEPAD_JOYSTICK_MAX; + break; + case WII_ANALOG_TYPE_RIGHT_STICK_Y_MINUS: + axisToChange = WII_ANALOG_TYPE_RIGHT_STICK_Y; + adjustedValue = map(analogValue,minValue,maxValue,GAMEPAD_JOYSTICK_MID,GAMEPAD_JOYSTICK_MIN); + minValue = GAMEPAD_JOYSTICK_MIN; + maxValue = GAMEPAD_JOYSTICK_MID; + break; + } + + if (axisToChange != WII_ANALOG_TYPE_NONE) axesOfChange[axisToChange].push_back(bounds(adjustedValue,minValue,maxValue)); + } + } + + for (auto currAxis = axesOfChange.begin(); currAxis != axesOfChange.end(); ++currAxis) { + if (!currAxis->second.empty()) { + axisType = currAxis->first; + + switch (axisType) { + case WII_ANALOG_TYPE_LEFT_STICK_X: + gamepad->state.lx = getDelta(currAxis->second, GAMEPAD_JOYSTICK_MID); + break; + case WII_ANALOG_TYPE_LEFT_STICK_Y: + gamepad->state.ly = getDelta(currAxis->second, GAMEPAD_JOYSTICK_MID); + break; + case WII_ANALOG_TYPE_RIGHT_STICK_X: + gamepad->state.rx = getDelta(currAxis->second, GAMEPAD_JOYSTICK_MID); + break; + case WII_ANALOG_TYPE_RIGHT_STICK_Y: + gamepad->state.ry = getDelta(currAxis->second, GAMEPAD_JOYSTICK_MID); + break; + case WII_ANALOG_TYPE_LEFT_TRIGGER: + gamepad->state.lt = getDelta(currAxis->second, GAMEPAD_TRIGGER_MID); + break; + case WII_ANALOG_TYPE_RIGHT_TRIGGER: + gamepad->state.rt = getDelta(currAxis->second, GAMEPAD_TRIGGER_MID); + break; + } + } + } +} + +uint16_t WiiExtensionInput::getAverage(std::vector const& changes) { + std::vector values; + + if (changes.empty()) { + return 0; + } + + for (auto currVal = changes.begin(); currVal != changes.end(); ++currVal) { + values.push_back(currVal->analogValue); + } + + return std::accumulate(values.begin(), values.end(), 0) / values.size(); +} + +uint16_t WiiExtensionInput::getDelta(std::vector const& changes, uint16_t baseValue) { + uint16_t value = baseValue; + + if (changes.empty()) { + return baseValue; + } + + for (auto currVal = changes.begin(); currVal != changes.end(); ++currVal) { + if (*currVal != baseValue) { + if (*currVal < baseValue) { + value -= (baseValue - *currVal); + } else if (*currVal > baseValue) { + value += (*currVal - baseValue); + } + } + } + + return value; } \ No newline at end of file diff --git a/src/configs/webconfig.cpp b/src/configs/webconfig.cpp index 28a8393a0..eedfd6c16 100644 --- a/src/configs/webconfig.cpp +++ b/src/configs/webconfig.cpp @@ -1211,6 +1211,170 @@ std::string setPS4Options() return "{\"success\":true}"; } +std::string setWiiControls() +{ + DynamicJsonDocument doc = get_post_data(); + WiiOptions& wiiOptions = Storage::getInstance().getAddonOptions().wiiOptions; + + readDoc(wiiOptions.controllers.nunchuk.buttonC, doc, "nunchuk.buttonC"); + readDoc(wiiOptions.controllers.nunchuk.buttonZ, doc, "nunchuk.buttonZ"); + readDoc(wiiOptions.controllers.nunchuk.stick.x.axisType, doc, "nunchuk.analogStick.x.axisType"); + readDoc(wiiOptions.controllers.nunchuk.stick.y.axisType, doc, "nunchuk.analogStick.y.axisType"); + + readDoc(wiiOptions.controllers.classic.buttonA, doc, "classic.buttonA"); + readDoc(wiiOptions.controllers.classic.buttonB, doc, "classic.buttonB"); + readDoc(wiiOptions.controllers.classic.buttonX, doc, "classic.buttonX"); + readDoc(wiiOptions.controllers.classic.buttonY, doc, "classic.buttonY"); + readDoc(wiiOptions.controllers.classic.buttonL, doc, "classic.buttonL"); + readDoc(wiiOptions.controllers.classic.buttonZL, doc, "classic.buttonZL"); + readDoc(wiiOptions.controllers.classic.buttonR, doc, "classic.buttonR"); + readDoc(wiiOptions.controllers.classic.buttonZR, doc, "classic.buttonZR"); + readDoc(wiiOptions.controllers.classic.buttonMinus, doc, "classic.buttonMinus"); + readDoc(wiiOptions.controllers.classic.buttonPlus, doc, "classic.buttonPlus"); + readDoc(wiiOptions.controllers.classic.buttonHome, doc, "classic.buttonHome"); + readDoc(wiiOptions.controllers.classic.buttonUp, doc, "classic.buttonUp"); + readDoc(wiiOptions.controllers.classic.buttonDown, doc, "classic.buttonDown"); + readDoc(wiiOptions.controllers.classic.buttonLeft, doc, "classic.buttonLeft"); + readDoc(wiiOptions.controllers.classic.buttonRight, doc, "classic.buttonRight"); + readDoc(wiiOptions.controllers.classic.leftStick.x.axisType, doc, "classic.analogLeftStick.x.axisType"); + readDoc(wiiOptions.controllers.classic.leftStick.y.axisType, doc, "classic.analogLeftStick.y.axisType"); + readDoc(wiiOptions.controllers.classic.rightStick.x.axisType, doc, "classic.analogRightStick.x.axisType"); + readDoc(wiiOptions.controllers.classic.rightStick.y.axisType, doc, "classic.analogRightStick.y.axisType"); + readDoc(wiiOptions.controllers.classic.leftTrigger.axisType, doc, "classic.analogLeftTrigger.axisType"); + readDoc(wiiOptions.controllers.classic.rightTrigger.axisType, doc, "classic.analogRightTrigger.axisType"); + + readDoc(wiiOptions.controllers.taiko.buttonKatLeft, doc, "taiko.buttonKatLeft"); + readDoc(wiiOptions.controllers.taiko.buttonKatRight, doc, "taiko.buttonKatRight"); + readDoc(wiiOptions.controllers.taiko.buttonDonLeft, doc, "taiko.buttonDonLeft"); + readDoc(wiiOptions.controllers.taiko.buttonDonRight, doc, "taiko.buttonDonRight"); + + readDoc(wiiOptions.controllers.guitar.buttonRed, doc, "guitar.buttonRed"); + readDoc(wiiOptions.controllers.guitar.buttonGreen, doc, "guitar.buttonGreen"); + readDoc(wiiOptions.controllers.guitar.buttonYellow, doc, "guitar.buttonYellow"); + readDoc(wiiOptions.controllers.guitar.buttonBlue, doc, "guitar.buttonBlue"); + readDoc(wiiOptions.controllers.guitar.buttonOrange, doc, "guitar.buttonOrange"); + readDoc(wiiOptions.controllers.guitar.buttonPedal, doc, "guitar.buttonPedal"); + readDoc(wiiOptions.controllers.guitar.buttonMinus, doc, "guitar.buttonMinus"); + readDoc(wiiOptions.controllers.guitar.buttonPlus, doc, "guitar.buttonPlus"); + readDoc(wiiOptions.controllers.guitar.strumUp, doc, "guitar.strumUp"); + readDoc(wiiOptions.controllers.guitar.strumDown, doc, "guitar.strumDown"); + readDoc(wiiOptions.controllers.guitar.stick.x.axisType, doc, "guitar.analogStick.x.axisType"); + readDoc(wiiOptions.controllers.guitar.stick.y.axisType, doc, "guitar.analogStick.y.axisType"); + readDoc(wiiOptions.controllers.guitar.whammyBar.axisType, doc, "guitar.analogWhammyBar.axisType"); + + readDoc(wiiOptions.controllers.drum.buttonRed, doc, "drum.buttonRed"); + readDoc(wiiOptions.controllers.drum.buttonGreen, doc, "drum.buttonGreen"); + readDoc(wiiOptions.controllers.drum.buttonYellow, doc, "drum.buttonYellow"); + readDoc(wiiOptions.controllers.drum.buttonBlue, doc, "drum.buttonBlue"); + readDoc(wiiOptions.controllers.drum.buttonOrange, doc, "drum.buttonOrange"); + readDoc(wiiOptions.controllers.drum.buttonPedal, doc, "drum.buttonPedal"); + readDoc(wiiOptions.controllers.drum.buttonMinus, doc, "drum.buttonMinus"); + readDoc(wiiOptions.controllers.drum.buttonPlus, doc, "drum.buttonPlus"); + readDoc(wiiOptions.controllers.drum.stick.x.axisType, doc, "drum.analogStick.x.axisType"); + readDoc(wiiOptions.controllers.drum.stick.y.axisType, doc, "drum.analogStick.y.axisType"); + + readDoc(wiiOptions.controllers.turntable.buttonLeftRed, doc, "turntable.buttonLeftRed"); + readDoc(wiiOptions.controllers.turntable.buttonLeftGreen, doc, "turntable.buttonLeftGreen"); + readDoc(wiiOptions.controllers.turntable.buttonLeftBlue, doc, "turntable.buttonLeftBlue"); + readDoc(wiiOptions.controllers.turntable.buttonRightRed, doc, "turntable.buttonRightRed"); + readDoc(wiiOptions.controllers.turntable.buttonRightGreen, doc, "turntable.buttonRightGreen"); + readDoc(wiiOptions.controllers.turntable.buttonRightBlue, doc, "turntable.buttonRightBlue"); + readDoc(wiiOptions.controllers.turntable.buttonMinus, doc, "turntable.buttonMinus"); + readDoc(wiiOptions.controllers.turntable.buttonPlus, doc, "turntable.buttonPlus"); + readDoc(wiiOptions.controllers.turntable.buttonEuphoria, doc, "turntable.buttonEuphoria"); + readDoc(wiiOptions.controllers.turntable.stick.x.axisType, doc, "turntable.analogStick.x.axisType"); + readDoc(wiiOptions.controllers.turntable.stick.y.axisType, doc, "turntable.analogStick.y.axisType"); + readDoc(wiiOptions.controllers.turntable.leftTurntable.axisType, doc, "turntable.analogLeftTurntable.axisType"); + readDoc(wiiOptions.controllers.turntable.rightTurntable.axisType, doc, "turntable.analogRightTurntable.axisType"); + readDoc(wiiOptions.controllers.turntable.effects.axisType, doc, "turntable.analogEffects.axisType"); + readDoc(wiiOptions.controllers.turntable.fader.axisType, doc, "turntable.analogFader.axisType"); + + Storage::getInstance().save(); + + return "{\"success\":true}"; +} + +std::string getWiiControls() +{ + DynamicJsonDocument doc(LWIP_HTTPD_POST_MAX_PAYLOAD_LEN); + WiiOptions& wiiOptions = Storage::getInstance().getAddonOptions().wiiOptions; + + writeDoc(doc, "nunchuk.buttonC", wiiOptions.controllers.nunchuk.buttonC); + writeDoc(doc, "nunchuk.buttonZ", wiiOptions.controllers.nunchuk.buttonZ); + writeDoc(doc, "nunchuk.analogStick.x.axisType", wiiOptions.controllers.nunchuk.stick.x.axisType); + writeDoc(doc, "nunchuk.analogStick.y.axisType", wiiOptions.controllers.nunchuk.stick.y.axisType); + + writeDoc(doc, "classic.buttonA", wiiOptions.controllers.classic.buttonA); + writeDoc(doc, "classic.buttonB", wiiOptions.controllers.classic.buttonB); + writeDoc(doc, "classic.buttonX", wiiOptions.controllers.classic.buttonX); + writeDoc(doc, "classic.buttonY", wiiOptions.controllers.classic.buttonY); + writeDoc(doc, "classic.buttonL", wiiOptions.controllers.classic.buttonL); + writeDoc(doc, "classic.buttonZL", wiiOptions.controllers.classic.buttonZL); + writeDoc(doc, "classic.buttonR", wiiOptions.controllers.classic.buttonR); + writeDoc(doc, "classic.buttonZR", wiiOptions.controllers.classic.buttonZR); + writeDoc(doc, "classic.buttonMinus", wiiOptions.controllers.classic.buttonMinus); + writeDoc(doc, "classic.buttonPlus", wiiOptions.controllers.classic.buttonPlus); + writeDoc(doc, "classic.buttonHome", wiiOptions.controllers.classic.buttonHome); + writeDoc(doc, "classic.buttonUp", wiiOptions.controllers.classic.buttonUp); + writeDoc(doc, "classic.buttonDown", wiiOptions.controllers.classic.buttonDown); + writeDoc(doc, "classic.buttonLeft", wiiOptions.controllers.classic.buttonLeft); + writeDoc(doc, "classic.buttonRight", wiiOptions.controllers.classic.buttonRight); + writeDoc(doc, "classic.analogLeftStick.x.axisType", wiiOptions.controllers.classic.leftStick.x.axisType); + writeDoc(doc, "classic.analogLeftStick.y.axisType", wiiOptions.controllers.classic.leftStick.y.axisType); + writeDoc(doc, "classic.analogRightStick.x.axisType", wiiOptions.controllers.classic.rightStick.x.axisType); + writeDoc(doc, "classic.analogRightStick.y.axisType", wiiOptions.controllers.classic.rightStick.y.axisType); + writeDoc(doc, "classic.analogLeftTrigger.axisType", wiiOptions.controllers.classic.leftTrigger.axisType); + writeDoc(doc, "classic.analogRightTrigger.axisType", wiiOptions.controllers.classic.rightTrigger.axisType); + + writeDoc(doc, "taiko.buttonKatLeft", wiiOptions.controllers.taiko.buttonKatLeft); + writeDoc(doc, "taiko.buttonKatRight", wiiOptions.controllers.taiko.buttonKatRight); + writeDoc(doc, "taiko.buttonDonLeft", wiiOptions.controllers.taiko.buttonDonLeft); + writeDoc(doc, "taiko.buttonDonRight", wiiOptions.controllers.taiko.buttonDonRight); + + writeDoc(doc, "guitar.buttonRed", wiiOptions.controllers.guitar.buttonRed); + writeDoc(doc, "guitar.buttonGreen", wiiOptions.controllers.guitar.buttonGreen); + writeDoc(doc, "guitar.buttonYellow", wiiOptions.controllers.guitar.buttonYellow); + writeDoc(doc, "guitar.buttonBlue", wiiOptions.controllers.guitar.buttonBlue); + writeDoc(doc, "guitar.buttonOrange", wiiOptions.controllers.guitar.buttonOrange); + writeDoc(doc, "guitar.buttonPedal", wiiOptions.controllers.guitar.buttonPedal); + writeDoc(doc, "guitar.buttonMinus", wiiOptions.controllers.guitar.buttonMinus); + writeDoc(doc, "guitar.buttonPlus", wiiOptions.controllers.guitar.buttonPlus); + writeDoc(doc, "guitar.strumUp", wiiOptions.controllers.guitar.strumUp); + writeDoc(doc, "guitar.strumDown", wiiOptions.controllers.guitar.strumDown); + writeDoc(doc, "guitar.analogStick.x.axisType", wiiOptions.controllers.guitar.stick.x.axisType); + writeDoc(doc, "guitar.analogStick.y.axisType", wiiOptions.controllers.guitar.stick.y.axisType); + writeDoc(doc, "guitar.analogWhammyBar.axisType", wiiOptions.controllers.guitar.whammyBar.axisType); + + writeDoc(doc, "drum.buttonRed", wiiOptions.controllers.drum.buttonRed); + writeDoc(doc, "drum.buttonGreen", wiiOptions.controllers.drum.buttonGreen); + writeDoc(doc, "drum.buttonYellow", wiiOptions.controllers.drum.buttonYellow); + writeDoc(doc, "drum.buttonBlue", wiiOptions.controllers.drum.buttonBlue); + writeDoc(doc, "drum.buttonOrange", wiiOptions.controllers.drum.buttonOrange); + writeDoc(doc, "drum.buttonPedal", wiiOptions.controllers.drum.buttonPedal); + writeDoc(doc, "drum.buttonMinus", wiiOptions.controllers.drum.buttonMinus); + writeDoc(doc, "drum.buttonPlus", wiiOptions.controllers.drum.buttonPlus); + writeDoc(doc, "drum.analogStick.x.axisType", wiiOptions.controllers.drum.stick.x.axisType); + writeDoc(doc, "drum.analogStick.y.axisType", wiiOptions.controllers.drum.stick.y.axisType); + + writeDoc(doc, "turntable.buttonLeftRed", wiiOptions.controllers.turntable.buttonLeftRed); + writeDoc(doc, "turntable.buttonLeftGreen", wiiOptions.controllers.turntable.buttonLeftGreen); + writeDoc(doc, "turntable.buttonLeftBlue", wiiOptions.controllers.turntable.buttonLeftBlue); + writeDoc(doc, "turntable.buttonRightRed", wiiOptions.controllers.turntable.buttonRightRed); + writeDoc(doc, "turntable.buttonRightGreen", wiiOptions.controllers.turntable.buttonRightGreen); + writeDoc(doc, "turntable.buttonRightBlue", wiiOptions.controllers.turntable.buttonRightBlue); + writeDoc(doc, "turntable.buttonMinus", wiiOptions.controllers.turntable.buttonMinus); + writeDoc(doc, "turntable.buttonPlus", wiiOptions.controllers.turntable.buttonPlus); + writeDoc(doc, "turntable.buttonEuphoria", wiiOptions.controllers.turntable.buttonEuphoria); + writeDoc(doc, "turntable.analogStick.x.axisType", wiiOptions.controllers.turntable.stick.x.axisType); + writeDoc(doc, "turntable.analogStick.x.axisType", wiiOptions.controllers.turntable.stick.y.axisType); + writeDoc(doc, "turntable.analogLeftTurntable.axisType", wiiOptions.controllers.turntable.leftTurntable.axisType); + writeDoc(doc, "turntable.analogRightTurntable.axisType", wiiOptions.controllers.turntable.rightTurntable.axisType); + writeDoc(doc, "turntable.analogEffects.axisType", wiiOptions.controllers.turntable.effects.axisType); + writeDoc(doc, "turntable.analogFader.axisType", wiiOptions.controllers.turntable.fader.axisType); + + return serialize_json(doc); +} + std::string getAddonOptions() { DynamicJsonDocument doc(LWIP_HTTPD_POST_MAX_PAYLOAD_LEN); @@ -1561,6 +1725,7 @@ static const std::pair handlerFuncs[] = { "/api/setKeyMappings", setKeyMappings }, { "/api/setAddonsOptions", setAddonOptions }, { "/api/setPS4Options", setPS4Options }, + { "/api/setWiiControls", setWiiControls }, { "/api/setSplashImage", setSplashImage }, { "/api/reboot", reboot }, { "/api/getDisplayOptions", getDisplayOptions }, @@ -1570,6 +1735,7 @@ static const std::pair handlerFuncs[] = { "/api/getProfileOptions", getProfileOptions }, { "/api/getKeyMappings", getKeyMappings }, { "/api/getAddonsOptions", getAddonOptions }, + { "/api/getWiiControls", getWiiControls }, { "/api/resetSettings", resetSettings }, { "/api/getSplashImage", getSplashImage }, { "/api/getFirmwareVersion", getFirmwareVersion }, diff --git a/www/server/app.js b/www/server/app.js index 79f3f3933..d7180153f 100644 --- a/www/server/app.js +++ b/www/server/app.js @@ -228,6 +228,78 @@ app.get('/api/getKeyMappings', (req, res) => res.send(mapValues(DEFAULT_KEYBOARD_MAPPING)), ); +app.get('/api/getWiiControls', (req, res) => + res.send({ + "nunchuk.analogStick.x.axisType": 1, + "nunchuk.analogStick.y.axisType": 2, + "nunchuk.buttonC": 1, + "nunchuk.buttonZ": 2, + "classic.analogLeftStick.x.axisType": 1, + "classic.analogLeftStick.y.axisType": 2, + "classic.analogRightStick.x.axisType": 3, + "classic.analogRightStick.y.axisType": 4, + "classic.analogLeftTrigger.axisType": 7, + "classic.analogRightTrigger.axisType": 8, + "classic.buttonA": 2, + "classic.buttonB": 1, + "classic.buttonX": 8, + "classic.buttonY": 4, + "classic.buttonL": 64, + "classic.buttonR": 128, + "classic.buttonZL": 16, + "classic.buttonZR": 32, + "classic.buttonMinus": 256, + "classic.buttonHome": 4096, + "classic.buttonPlus": 512, + "classic.buttonUp": 65536, + "classic.buttonDown": 131072, + "classic.buttonLeft": 262144, + "classic.buttonRight": 524288, + "guitar.analogStick.x.axisType": 1, + "guitar.analogStick.y.axisType": 2, + "guitar.analogWhammyBar.axisType": 14, + "guitar.buttonOrange": 64, + "guitar.buttonRed": 2, + "guitar.buttonBlue": 4, + "guitar.buttonGreen": 1, + "guitar.buttonYellow": 8, + "guitar.buttonPedal": 128, + "guitar.buttonMinus": 256, + "guitar.buttonPlus": 512, + "guitar.buttonStrumUp": 65536, + "guitar.buttonStrumDown": 131072, + "drum.analogStick.x.axisType": 1, + "drum.analogStick.y.axisType": 2, + "drum.buttonOrange": 64, + "drum.buttonRed": 2, + "drum.buttonBlue": 8, + "drum.buttonGreen": 1, + "drum.buttonYellow": 4, + "drum.buttonPedal": 128, + "drum.buttonMinus": 256, + "drum.buttonPlus": 512, + "turntable.analogStick.x.axisType": 1, + "turntable.analogStick.y.axisType": 2, + "turntable.analogLeftTurntable.axisType": 13, + "turntable.analogRightTurntable.axisType": 15, + "turntable.analogFader.axisType": 7, + "turntable.analogEffects.axisType": 8, + "turntable.buttonLeftGreen": 262144, + "turntable.buttonLeftRed": 65536, + "turntable.buttonLeftBlue": 524288, + "turntable.buttonRightGreen": 4, + "turntable.buttonRightRed": 8, + "turntable.buttonRightBlue": 2, + "turntable.buttonEuphoria": 32, + "turntable.buttonMinus": 256, + "turntable.buttonPlus": 512, + "taiko.buttonDonLeft": 262144, + "taiko.buttonKatLeft": 64, + "taiko.buttonDonRight": 1, + "taiko.buttonKatRight": 128, + }), +); + app.get('/api/getProfileOptions', (req, res) => { return res.send({ alternativePinMappings: [ diff --git a/www/src/Addons/Wii.tsx b/www/src/Addons/Wii.tsx index 32f25dc62..9a0b57f86 100644 --- a/www/src/Addons/Wii.tsx +++ b/www/src/Addons/Wii.tsx @@ -1,6 +1,7 @@ -import React from 'react'; +import { AppContext } from '../Contexts/AppContext'; +import React, { useContext, useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { FormCheck, Row } from 'react-bootstrap'; +import { Button, Form, Row, FormCheck, Tab, Tabs } from 'react-bootstrap'; import * as yup from 'yup'; import Section from '../Components/Section'; @@ -8,6 +9,9 @@ import Section from '../Components/Section'; import FormControl from '../Components/FormControl'; import FormSelect from '../Components/FormSelect'; import { I2C_BLOCKS } from '../Data/Addons'; +import { BUTTON_MASKS } from '../Data/Buttons'; + +import WebApi, { baseWiiControls } from '../Services/WebApi'; export const wiiScheme = { WiiExtensionAddonEnabled: yup @@ -43,91 +47,280 @@ export const wiiState = { wiiExtensionSpeed: 400000, }; +const WII_EXTENSION_CONTROLS = [ + { id: 'Nunchuk', value: 0, inputs: { analog: [ { id: 'Stick', axes: [ { id: 'X', axis: 0 }, { id: 'Y', axis: 1 } ] } ], digital: [ { id: 'C' }, { id: 'Z' } ] } }, + { id: 'Classic', value: 1, inputs: { analog: [ { id: 'LeftStick', axes: [ { id: 'X', axis: 0 }, { id: 'Y', axis: 1 } ] }, { id: 'RightStick', axes: [ { id: 'X', axis: 2 }, { id: 'Y', axis: 3 } ] }, { id: 'LeftTrigger', axes: [ { id: 'Trigger', axis: 4 } ] }, { id: 'RightTrigger', axes: [ { id : 'Trigger', axis: 5 } ] } ], digital: [ { id: 'A' }, { id: 'B' }, { id: 'X' }, { id: 'Y' }, { id: 'L' }, { id: 'R' }, { id: 'ZL' }, { id: 'ZR' }, { id: 'Minus' }, { id: 'Home' }, { id: 'Plus' }, { id: 'Up' }, { id: 'Down' }, { id: 'Left' }, { id: 'Right' } ] } }, + { id: 'Guitar', value: 3, inputs: { analog: [ { id: 'Stick', axes: [ { id: 'X', axis: 0 }, { id: 'Y', axis: 1 } ] }, { id: 'WhammyBar', axes: [ { id: 'Trigger', axis: 4 } ] } ], digital: [ { id: 'Orange' }, { id: 'Red' }, { id: 'Blue' }, { id: 'Green' }, { id: 'Yellow' }, { id: 'Pedal' }, { id: 'Minus' }, { id: 'Plus' }, { id: 'StrumUp' }, { id: 'StrumDown' } ] } }, + { id: 'Drum', value: 4, inputs: { analog: [ { id: 'Stick', axes: [ { id: 'X', axis: 0 }, { id: 'Y', axis: 1 } ] } ], digital: [ { id: 'Orange' }, { id: 'Red' }, { id: 'Blue' }, { id: 'Green' }, { id: 'Yellow' }, { id: 'Pedal' }, { id: 'Minus' }, { id: 'Plus' } ] } }, + { id: 'Turntable', value: 5, inputs: { analog: [ { id: 'Stick', axes: [ { id: 'X', axis: 0 }, { id: 'Y', axis: 1 } ] }, { id: 'LeftTurntable', axes: [ { id: 'Trigger', axis: 0 } ] }, { id: 'RightTurntable', axes: [ { id: 'Trigger', axis: 2 } ] }, { id: 'Fader', axes: [ { id: 'Trigger', axis: 4 } ] }, { id: 'Effects', axes: [ { id: 'Trigger', axis: 5 } ] } ], digital: [ { id: 'LeftGreen' }, { id: 'LeftRed' }, { id: 'LeftBlue' }, { id: 'RightGreen' }, { id: 'RightRed' }, { id: 'RightBlue' }, { id: 'Euphoria' }, { id: 'Minus' }, { id: 'Plus' } ] } }, + { id: 'Taiko', value: 6, inputs: { analog: [], digital: [ { id: 'DonLeft' }, { id: 'KatLeft' }, { id: 'DonRight' }, { id: 'KatRight' } ] } }, +]; + +const WII_JOYSTICK_MODES = [ + { label: 'None', value: 0, options: {'x': 0, 'y': 0} }, + { label: 'Left Analog', value: 1, options: {'x': 1, 'y': 2} }, + { label: 'Right Analog', value: 2, options: {'x': 3, 'y': 4} }, + { label: 'D-Pad', value: 3, options: {'x': 5, 'y': 6} }, +]; + const Wii = ({ values, errors, handleChange, handleCheckbox }) => { const { t } = useTranslation(); + const [wiiControls, setWiiControls] = useState(baseWiiControls); + const [selectedControls] = useState(baseWiiControls); + const { setLoading } = useContext(AppContext); + + useEffect(() => { + async function fetchData() { + let wc = await WebApi.getWiiControls(setLoading); + setWiiControls(wc); + } + + fetchData(); + }, [setWiiControls, selectedControls]); + + const ANALOG_SINGLE_AXIS_MODES = [ + { label: 'None', value: 0 }, + { label: 'Left Trigger', value: 7 }, + { label: 'Right Trigger', value: 8 }, + { label: 'Left Analog X+ Axis', value: 9 }, + { label: 'Left Analog X- Axis', value: 10 }, + { label: 'Left Analog Y+ Axis', value: 11 }, + { label: 'Left Analog Y- Axis', value: 12 }, + { label: 'Right Analog X+ Axis', value: 13 }, + { label: 'Right Analog X- Axis', value: 14 }, + { label: 'Right Analog Y+ Axis', value: 15 }, + { label: 'Right Analog Y- Axis', value: 16 }, + ]; + + const saveWiiOptions = async () => { + let wiiButtonsMap = Array.from(document.querySelectorAll('#WiiExtensionAddonOptions .wii-buttons')); + let wiiMap = wiiButtonsMap + .reduce( + (o, i) => Object.assign(o, { [i.getAttribute('controlid')+'.button'+i.getAttribute('buttonid')]: parseInt(i.value) }), {}); + + let wiiAnalogMap = Array.from(document.querySelectorAll('#WiiExtensionAddonOptions .wii-analogs')); + wiiMap = wiiAnalogMap + .reduce( + (o, i) => { + let modeID = i.value; + let joyMode = WII_JOYSTICK_MODES.filter((o,i) => o.value == modeID)[0]; + if (joyMode && joyMode.options) { + let r = o; + Object.keys(joyMode.options).forEach(key => { + Object.assign(r, { [i.getAttribute('controlid')+'.analog'+i.getAttribute('analogid')+'.'+key+'.axisType']: joyMode.options[key] }); + }); + o = r; + } else { + o = Object.assign(o, { [i.getAttribute('controlid')+'.analog'+i.getAttribute('analogid')+'.axisType']: parseInt(i.value) }); + } + return o; + }, wiiMap); + + try { + let success = await WebApi.setWiiControls(wiiMap); + + if (success) { + + } else { + + } + } catch (e) { + } + }; + + const setWiiControlEntry = (controlID,buttonID,e) => { + let controlEntry = {}; + controlEntry[`${controlID.toLowerCase()}.button${buttonID}`] = e.target.value; + setWiiControls(controls => ({ + ...wiiControls, + ...controlEntry + })); + }; + + const setWiiAnalogEntry = (controlID,analogID,e) => { + let analogEntry = {}; + let modeID = e.target.value; + let joyMode = WII_JOYSTICK_MODES.filter((o,i) => o.value == modeID)[0]; + if (joyMode && joyMode.options) { + let r = analogEntry; + Object.keys(joyMode.options).forEach(key => { + Object.assign(r, { [controlID.toLowerCase()+'.analog'+analogID+'.'+key+'.axisType']: joyMode.options[key] }); + }); + analogEntry = r; + } else { + analogEntry[`${controlID.toLowerCase()}.analog${analogID}.axisType`] = e.target.value; + } + + setWiiControls(controls => ({ + ...wiiControls, + ...analogEntry + })); + }; + + const getJoystickModeValue = (controlObj, analogObj) => { + let joystickMode = 0; + let foundOpt = false; + WII_JOYSTICK_MODES.forEach(mode => { + if (!foundOpt) { + Object.keys(mode.options).forEach(opt => { + if (!foundOpt) { + let modeEntry = mode.options[opt]; + let entryID = controlObj.id.toLowerCase()+'.analog'+analogObj.id+'.'+opt+'.axisType'; + let entry = wiiControls[entryID]; + if (wiiControls[entryID] == modeEntry) { + foundOpt = true; + joystickMode = mode.value; + } + } + }); + } + }); + return joystickMode; + }; + return ( -
- - + + Note: If the Display is enabled at the same time, this Addon will be disabled.', + 'sda-pin-label': 'I2C SDA Pin', + 'scl-pin-label': 'I2C SCL Pin', + 'block-label': 'I2C Block', + 'speed-label': 'I2C Speed', + 'section-digital': 'Digital', + 'section-analog': 'Analog', + 'option-simple': 'Simple', + 'option-advanced': 'Advanced', + 'controller-nunchuk': 'Nunchuk', + 'controller-classic': 'Classic', + 'controller-taiko': 'Taiko', + 'controller-guitar': 'Guitar', + 'controller-drum': 'Drums', + 'controller-turntable': 'Turntable', + 'controller-button-a': 'A', + 'controller-button-b': 'B', + 'controller-button-c': 'C', + 'controller-button-x': 'X', + 'controller-button-y': 'Y', + 'controller-button-z': 'Z', + 'controller-button-l': 'L', + 'controller-button-r': 'R', + 'controller-button-zl': 'ZL', + 'controller-button-zr': 'ZR', + 'controller-button-minus': 'Minus', + 'controller-button-home': 'Home', + 'controller-button-plus': 'Plus', + 'controller-button-up': 'Up', + 'controller-button-down': 'Down', + 'controller-button-left': 'Left', + 'controller-button-right': 'Right', + 'controller-button-euphoria': 'Euphoria', + 'controller-button-leftgreen': 'Left Green', + 'controller-button-leftred': 'Left Red', + 'controller-button-leftblue': 'Left Blue', + 'controller-button-rightgreen': 'Right Green', + 'controller-button-rightred': 'Right Red', + 'controller-button-rightblue': 'Right Blue', + 'controller-button-orange': 'Orange', + 'controller-button-red': 'Red', + 'controller-button-blue': 'Blue', + 'controller-button-green': 'Green', + 'controller-button-yellow': 'Yellow', + 'controller-button-pedal': 'Pedal', + 'controller-button-strumup': 'Strum Up', + 'controller-button-strumdown': 'Strum Down', + 'controller-button-donleft': 'Don Left', + 'controller-button-katleft': 'Kat Left', + 'controller-button-donright': 'Don Right', + 'controller-button-katright': 'Kat Right', + 'controller-analog-stick': 'Stick', + 'controller-analog-leftstick': 'Left Stick', + 'controller-analog-rightstick': 'Right Stick', + 'controller-analog-lefttrigger': 'L', + 'controller-analog-righttrigger': 'R', + 'controller-analog-leftturntable': 'Turntable Left', + 'controller-analog-rightturntable': 'Turntable Right', + 'controller-analog-fader': 'Fader', + 'controller-analog-effects': 'Effects Dial', + 'controller-analog-whammybar': 'Whammy Bar', + 'controller-analog-axis-x': 'X +/- Axis', + 'controller-analog-axis-y': 'Y +/- Axis', + 'controller-analog-axis-trigger': 'Trigger', + 'analog-axis-mode-trigger': '<0>The full range of values will be sent to the interface.', + 'analog-axis-mode-button': '<0>Acts as a digital switch, setting a "pressed" state after a defined threshold.', + 'analog-axis-mode-normal-direction': '<0>The full range of values will be translated to an axis of a joystick, where the lowest value is assumed the negative edge, and maximum is the positive edge.', + 'analog-axis-mode-invert-direction': '<0>The full range of values will be translated to an axis of a joystick, where the lowest value is assumed the positive edge, and maximum is the negative edge.', + 'button-save': 'Save Controllers', +}; diff --git a/www/src/Locales/en/AddonsConfig.jsx b/www/src/Locales/en/AddonsConfig.jsx index 558d84a06..fda777416 100644 --- a/www/src/Locales/en/AddonsConfig.jsx +++ b/www/src/Locales/en/AddonsConfig.jsx @@ -115,13 +115,6 @@ export default { 'ps4-mode-private-key-label': 'Private Key (PEM)', 'ps4-mode-serial-number-label': 'Serial Number (16 Bytes in Hex Ascii)', 'ps4-mode-signature-label': 'Signature (256 Bytes in Binary)', - 'wii-extension-header-text': 'Wii Extension', - 'wii-extension-sub-header-text': - '<0>Note: If the Display is enabled at the same time, this Addon will be disabled. <1>Currently Supported Controllers <0>Classic/Classic Pro: Both Analogs and D-Pad Supported. B = B1, A = B2, Y = B3, X = B4, L = L1, ZL = L2, R = R1, ZR = R2, Minus = S1, Plus = S2, Home = A1 <0>Nunchuck: Analog Stick Supported. C = B1, Z = B2 <0>Guitar Hero Guitar: Analog Stick Supported. Green = B1, Red = B2, Blue = B3, Yellow = B4, Orange = L1, Strum Up = Up, Strum Down = Down, Minus = S1, Plus = S2', - 'wii-extension-sda-pin-label': 'I2C SDA Pin', - 'wii-extension-scl-pin-label': 'I2C SCL Pin', - 'wii-extension-block-label': 'I2C Block', - 'wii-extension-speed-label': 'I2C Speed', 'snes-extension-header-text': 'SNES Extension Configuration', 'snes-extension-sub-header-text': '<0>Note: If the Display is enabled at the same time, this Addon will be disabled. <1>Currently Supported Controllers <0>SNES pad: D-Pad Supported. B = B1, A = B2, Y = B3, X = B4, L = L1, R = R1, Select = S1, Start = S2 <0>SNES mouse: Analog Stick Supported. Left Click = B1, Right Click = B2 <0>NES: D-Pad Supported. B = B1, A = B2, Select = S1, Start = S2', diff --git a/www/src/Locales/en/Index.jsx b/www/src/Locales/en/Index.jsx index 4729059aa..cc7194333 100644 --- a/www/src/Locales/en/Index.jsx +++ b/www/src/Locales/en/Index.jsx @@ -13,6 +13,7 @@ import BackupPage from './BackupPage'; import DisplayConfig from './DisplayConfig'; import AddonsConfig from './AddonsConfig'; import CaptureButton from './CaptureButton'; +import WiiAddon from './Addons/WiiAddon'; export default { Common, @@ -30,4 +31,5 @@ export default { DisplayConfig, AddonsConfig, CaptureButton, + WiiAddon, }; diff --git a/www/src/Services/WebApi.js b/www/src/Services/WebApi.js index c4ab94c32..ee85c5af3 100644 --- a/www/src/Services/WebApi.js +++ b/www/src/Services/WebApi.js @@ -73,6 +73,75 @@ export const baseProfileOptions = { ], }; +export const baseWiiControls = { + "nunchuk.analogStick.axisType": 1, + "nunchuk.buttonC": 1, + "nunchuk.buttonZ": 2, + "classic.analogLeftStick.x.axisType": 1, + "classic.analogLeftStick.y.axisType": 2, + "classic.analogRightStick.x.axisType": 3, + "classic.analogRightStick.y.axisType": 4, + "classic.analogLeftTrigger.axisType": 7, + "classic.analogRightTrigger.axisType": 8, + "classic.buttonA": 2, + "classic.buttonB": 1, + "classic.buttonX": 8, + "classic.buttonY": 4, + "classic.buttonL": 64, + "classic.buttonR": 128, + "classic.buttonZL": 16, + "classic.buttonZR": 32, + "classic.buttonMinus": 256, + "classic.buttonHome": 4096, + "classic.buttonPlus": 512, + "classic.buttonUp": 65536, + "classic.buttonDown": 131072, + "classic.buttonLeft": 262144, + "classic.buttonRight": 524288, + "guitar.analogStick.x.axisType": 1, + "guitar.analogStick.y.axisType": 2, + "guitar.analogWhammyBar.axisType": 14, + "guitar.buttonOrange": 64, + "guitar.buttonRed": 2, + "guitar.buttonBlue": 4, + "guitar.buttonGreen": 1, + "guitar.buttonYellow": 8, + "guitar.buttonPedal": 128, + "guitar.buttonMinus": 256, + "guitar.buttonPlus": 512, + "guitar.buttonStrumUp": 65536, + "guitar.buttonStrumDown": 131072, + "drum.analogStick.x.axisType": 1, + "drum.analogStick.y.axisType": 2, + "drum.buttonOrange": 64, + "drum.buttonRed": 2, + "drum.buttonBlue": 8, + "drum.buttonGreen": 1, + "drum.buttonYellow": 4, + "drum.buttonPedal": 128, + "drum.buttonMinus": 256, + "drum.buttonPlus": 512, + "turntable.analogStick.x.axisType": 1, + "turntable.analogStick.y.axisType": 2, + "turntable.analogLeftTurntable.axisType": 13, + "turntable.analogRightTurntable.axisType": 15, + "turntable.analogFader.axisType": 7, + "turntable.analogEffects.axisType": 8, + "turntable.buttonLeftGreen": 262144, + "turntable.buttonLeftRed": 65536, + "turntable.buttonLeftBlue": 524288, + "turntable.buttonRightGreen": 4, + "turntable.buttonRightRed": 8, + "turntable.buttonRightBlue": 2, + "turntable.buttonEuphoria": 32, + "turntable.buttonMinus": 256, + "turntable.buttonPlus": 512, + "taiko.buttonDonLeft": 262144, + "taiko.buttonKatLeft": 64, + "taiko.buttonDonRight": 1, + "taiko.buttonKatRight": 128, +}; + async function resetSettings() { return axios .get(`${baseUrl}/api/resetSettings`) @@ -434,6 +503,36 @@ async function setPS4Options(options) { }); } +async function getWiiControls(setLoading) { + setLoading(true); + + try { + const response = await axios.get(`${baseUrl}/api/getWiiControls`); + setLoading(false); + + let mappings = { ...baseWiiControls, ...response.data }; + return mappings; + } catch (error) { + setLoading(false); + console.error(error); + } +} + +async function setWiiControls(mappings) { + console.dir(mappings); + + return axios + .post(`${baseUrl}/api/setWiiControls`, sanitizeRequest(mappings)) + .then((response) => { + console.log(response.data); + return true; + }) + .catch((err) => { + console.error(err); + return false; + }); +} + async function getFirmwareVersion(setLoading) { setLoading(true); @@ -534,6 +633,8 @@ const WebApi = { getAddonsOptions, setAddonsOptions, setPS4Options, + getWiiControls, + setWiiControls, getSplashImage, setSplashImage, getFirmwareVersion, From 7d76a07d364872bcf74209e622f7fce5d01c5e55 Mon Sep 17 00:00:00 2001 From: Mike Parks Date: Tue, 5 Sep 2023 18:56:19 -0700 Subject: [PATCH 10/15] Fixed issue causing Classic Pro-based controllers from functioning. Updated documentation to remove Wii controller mapping details and add new image. --- .../images/gpc-add-ons-wii-extensions.png | Bin 52464 -> 87675 bytes docs/web-configurator-add-ons.md | 22 +++--------------- lib/WiiExtension/WiiExtension.cpp | 6 ++--- 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/docs/assets/images/gpc-add-ons-wii-extensions.png b/docs/assets/images/gpc-add-ons-wii-extensions.png index 5d504578b6e7309994a4d33ead5d7ea0c99f4d79..88313c8cfa2d746f6cd1502caf71f26d4c824f81 100644 GIT binary patch literal 87675 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�FABp*~5N_EzXl{61Pje_I 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%0DV zV@1eN1UuWzv->bM=|g;ki}=bf_j+`xN~5Eta<2L$8BUVtxr*I%C9v{)edNr1nObP=rGO`NrZBPFs9#t=m$vE1ix)_QIAHjJN zB2~O!LHCC?EnN}gcIaTzq)GOVw=7by6dc@rB{>I1eu->o`_hkEO3z0$W`s1vWXc*Z zy-mGDlb3Epxjw{jHI4Vx*dLxI74!hk&`*78sEzh+LkD-R!TFnctfL}!mTN)qynG>< z=L7pu>47>2XB9CrcGi5=w}HZv3XanOsW>(6=Vx`ZYf)nbM^M%fO{s{%Z(N$zO;fFi z$;pwi=t8-~7r053 zqZn7Nke(D36;(*JK>k;Vqeou;3ogMWAOCD-(dLj~@Mfo|DOrPH#>H-8Sb5&=s;6tjT`23?O)t~ow5pELi5N#h1ez3 zw+Eloh5vX)@Axhsl?t;2L`FO?>ikQT_YQ5CL4W1i|1}UC;-zbnQt7)WRZ=KSGf z;ilb0QMNf=w)zdiziB&uHIdNOAqHp#^b?4lR{fQ6O}KGs!rd>FkGZ}on7tL!t>3Zk z(?7Z9Ezrh)j>QgOnJm8H@H#r{Sk|3r9}6K6_Q| z)Di>4udwCYg%#8SJ8bOBF%(ac^4kDOV5*E3hI=W=vreZ+(~U_g5()PKd?C>hU=;~2 zua?_+^Kq*pcV-)s?-{-UYy)N&FLX%uy;M2v`D#rUb&hCY1o^aO0+J?XU3#ysGcN5? zwkFXy?*aj~d7p<%PLjyY=H7c{H>2Xh)XU%xToo#!v>G?=-F`$9;)*F%>-iK$30(bt z#Oii>%|>mzzWsrzfS>hu|FnRNUP!DQi)@34iYtUiGU4})!yR+;0yfE(GxGj3YObGq ze?Jjh%wrrg>>JwM>xdQj-7?&zKX=eaGPq4HQNdu<6$^Z4UTmTCfWyc-+8f`(FkUCe zB=mPjyNlqbNx6ec1F$RIq&wO<0F4)Mm5h-z4+JD@P1~!!ovq#vr2|;#EF6_FRB~gJ z-3LcVREbRjmjGkEPQZeJdDimf{$zT~;y3F*-JP<-gla%2@RvRC%jubWbE)_4w9pi6 z?55$>xlfXvNc6pd5Lyd?xYc@Mfroen<)Qtz2=L)G)4ybmiXstK_-lN5y2_#1|*Rq4AjLGkQ zQM>0=J;MD$cocNXxdsoOSa`fvg{ExGxU3JuH=$kk0DfLv;ry3PT~f0YV=0;VHm+L1 z)q9#?x^LgG19Xoqi<2;SxNFoeH}!DHMyY^FwozhT@vI^vxJPXQf&5{HSu{_vz=r#@ z>sp72zm`g+8Sus35fbW%r*Trq(vwdlk2V2dqp*lIfv-ZyAxjUM`%ixqr3MLB(tt=N z;Flw30l2&ElIHMpDeo_Vjd)G0BSo35q0*)TQE6K7loC`iNlmM9Xx9KSq6aiK#ktuOi5d#Ci{a=12clHVE`hIg$pP&11Jl^ z-Nd||w#;-Jf*~qzb(F}f!FYYE`D<$}?a)&rul6qV;Fj@XC)YbS8&B<=$xK9@y)4g~ z@fN(U^KxZ%j}KB^WT#jBWAXvW`vHh?$7cS$kDX4%1EcFJWqh?8aIf%ewa$Iri1=pH zo1TT7RVE@DM(YudL*8Z|y1gSZ0Gbgv^Etn*4Zf0L=VoC)a3Ibi?C zWMGsZjmVZskL|_dGK`M+^oe`vw@cdhxu0zUhZZR*3eW^$5O3_N?{--umdfm9=Q<_r zYN9Ns*SVSiPdM>4>ZL1;cnnO4Qe)T6H;h(jG5alaq#lMuXtb;VWbU_T%_M9`I__B8Bp3kWjrq`mlx{Z+6v;=RU>UdC z*lUt8W1G&-$;Lr|1e3ziCN9;jN)c9JP7@Hkr%l5&&2 z(N+ou_232kj2N?C4n{s=Cs934Emw<$f(&iH91)Sf##QRlj+G3k#M;1m69w0iWguYU!3XCt45NwyNG!lWJLd_f>V8EVapH zCbjWZd`dA#{O=cN>bl@Rbk$zJ$+}Q&OR@|rr9I7%` zD=7lo5`_)q5G(xT&-{I6xQOTkjZ<%C*y;v5S|f6rNR>ZJMyHS+$C@uQRA2+}=zszl z`^qCMsR*i6%2uLZo9>Y>u}!E9KSku(+DIi)0?MQX=_h8ciV>(&td<}1Gm<%_QElF1 zX>0<=v?#mg+qV_S>FoqpOULoOxe|ba-zFdN2=`F04V?@S3mLAWqm4R;7S4X<>Rw|s z-Hjm2r340b*oMu`Q@5N;-i1kgGxtNX*%r)bxlrr}(MmTGPeSBA(C4cuLL5k5f>fDT z`tg9LdVMBeyhGCytwLI3epv-}vtFpOLUz{P^V*f!;j)-JPxy7?4$MX9DM<745Yg7e zt(7%ybrFf?MNb2Wlt5Q{f(<|1nTW-9H)XD5O$%=0CQ+5Y(PP#Nu)k2nbBD&lr7!Br z(sU=q^DbARBkI&C4c;uLA=&QxHq4iIo4n)5m|HDIj~cpue`P}KUmVB#rcoJ`qfSBH z+j)ISUEqD)(JSRyFwsa=hPJKz2Jp7GA0@jXd{RV&acfLCGZ`s>Fd9k}qI>%m;^;y+ z0(a?6(B9ILbG*8o+FsfztXrW6wkxzALhr!>LChXC@R&^Mw>;>hr2q&0iTu@p-w179 z1hij%uIxKOGPszQ5B$J-9K_R&Ihb=o1!Z=78dGKtx3)}ui~Fwu0m{~RE07PFYw}vW z=y*yvn8puY+Ro1eau|f5;|3#n6RHQNK5_-@ZIwuTsyd$sfQf{augVb82pvr372^(d zPI5Os%~^XCQYzi)qpK?-Au9+wH)Pm9IxqKBXGsx0?S(;*d*Zot!yI?-_^Qf82{PmC z@ynNKGW!bnVmcBCdMiC)QpQt9U z_!|QdIKZ^|pz$y6D!cK2a94NRo&lz2ES2+BXo&p5>B;AyFlA?*7FcJ)`Fv$gd1`4vxcYr^D_P>N?Y(Xxs>@NJ>tOH)tjGYDi|4GmfZH9nm z1>q9F=Xi9X85-@LmIXjMXd{m)@c8cm>9cg}s*1FU0+P)%TuH^_sJ{wQw0Pv;Ovm(l z0suEz`4E%xx31$$5u&Nb*UVx}6zEy|fm&BCQDFTEJml>|F-4_XKs_4mC{`<?LsIu5N%+sO&qOM98VWp(+!*|N@2xNv6q`riAhUJ3ngEhJ*EYEKk-C$2VcTgc{Qg!wpShYi&8%Yb?2%{Z%b| z)bRXo;Mn}E7BLOHM2#^HxT&F{#%IzuE6GojH`ELt;(dP)I;OL34=T72{Xr3PiRZ`ax@ zTNOGqCL&P06Qh%mK*q(Kz)1FxqlxxMjuQ9H>}cn zHP_@&RdHn$;lzGxbCB3tO>R=sVoAVqAb->8n3(^?qq5q=CcQ<`nWFed4^!u>MpNu{ znel^3?syFqTI=C29v%jqvTq48WsWV@@fiz7`lSz|28L9=`WP4sN%{qzYpSqgjlyNw zxmhF1zBN-9xAa83J=bsOBsQ~E^drmCj2-sRu{_^OdN=^gxsYfLj=qq@NQw&S@ahLA z_34YS<0s~1;By%O;W>~b275Jeyt#ML^p)bpBU08KT$B{bH@8Ip`m+GA)D7-LCP<;m zs~D<3#>{j7jx|RBcXIaB#dP$GOu7oKs#Z!R5E)#dnvORyfpp8srN zdf3+`1;GFJA)Zn_F6Lkoq8AYOLO>QrJGlvS{4GBK0j{%;l+;vGG4Dn!ej{leT3MCx z=!wR5bo3}LGwB=deP|uot)}#EWXT;`l~QC+h38k(i9(%9Ak|%!ey~2VC!CHIa5jE4 z`hvzlQv{ndgZH1N$*$2tN61nbeh=!w9NiA_Ya z9~AmYRw;{e<7^tqm-Dap(JMKWRi6H*Uch})%s0K=kPw4`qzcqwY$1zqhrs-s+f zU*Ye)zEN8HYm%z&8XNwyOQLih)T{+oOrE)&1DjKxQyZV+rEpzVmT{Ss_nd-keg6z& zaiwFE+r9mF@O3(6?DcAQP@pbY0A-bSQ??gfBev7HUBi95!M>a3N%x9*wsF1tEpZN4 zl^H!U`g~(F->8d>&c*{&ThqERLMk=8?_wd(lG0!=Os|^GF0k-(zS$JvO1cGdNRT{; zroJy&gM0Lvjgvt+J(Ko}7}}bou@8VMYw+QH)Hmp+w7aLxT|Zg${Z86h_h?}%8HrX; zPmV7Z=x93Ktwgt(kOZ2qVD*XMn(ItN8}5jiSXHDyQ9A4v;~yh2#|vIM$h;3z`&h~& zpxszgyUj;0{j0A!;%E^5k?`xv!|=Yf zX&6hBFv+3kBmF07!wY)zu2;eabXpIVe%Ye0ILoyN@Mx9cyBD4^3%pU7@U3DHXrKSG zo_KPX&uy)9oWIqgH<_3vg+U#zSl#7&bA4IG@P-2~;aiWqJUPGDrntSk%vgEu$kKjk z|KqlSbVC$hoK^M4FQ*pY_-uEX{C`qtCWm%8Aw7`RM_<({`XR<`5w~hLo)nDK%lnW; zLKfu&!?xBEkq(xESSIztmF~SdbthbcZz7Vi{c*=(-1?ZF`>WQUUrL%k3v^W_9($TM z>OJ$NWUTa9I%rVPoc7A{u+RIR*O|^wt3Q_h;MX$oD9cFSV+~4 z4He2{-;vNg$P{c2pRPe}G(Iz>6hAop{&d!?=mL3Eo78G*Fe{K&++3H$)>{x}!5^4Zh9&hS5lQykt zW%MuSC`jc#5j^T!lw`P__ulcz7_6>H3xkv!h`Nd{p5xxiq?WCs=ek&zXKa0ZvUMJ_ zehoza-emwbNmr)(fw7m$YH_C76aCygMnjRLSFT*V4l%FYIh!}v8J4jHnobOyyR+Xb z&og`*ZgxHkS|BKzbLlYJo;i#%idlX`zMaVb{R_S95H@eTqtdRR{rQA{x4=INa0F`X zzGCLj0O6_y@91w7$^|L~{oP6R&G4Aw^(kHu$e47>b@yQWMGUMZ?*}mv9oq$ZJ31`# zS7>y-$1JuXw6n3xj^BrYjf;1^XQWv9a^jS*+dc9hF5~TU%{j0T4pS!7CPTd=^~z;5 zO%l|#Mco+1Jr7? zC9M~A4>9~?=$J&m{EJ8OMG^hL%;iDteW*lZ@YClFr!(&XEUYr}6m=JE&!zU|C8olQ zI**Gn^I7oV$vr3UkCc^+o-Dg_mn<2Xh6$Uzv|sSwoq(>^2ExRBH0zVU`bQ|delZ0t z__g#^w*C>ns1#lFSLyaf!ssFWRm|82h};w{fvry@($>7Hjm* zj#n=e>PIEjdmql2cwbZgRH>R@W4leMU6PBZKP7paAGh%941pB~XVJ$cS~?$qGn`s$ za+H0XyJM%mS60avnOyBU7YaLG9Wq=45y(jc7UOSX=e|6kB1(JV%^2yH0NA+OA;p@V1)Mk6>`RMZ4l z#BAAA$x&Rq%0UGyZQZ{UjOa$#E83*ScH&FD0<$-W8`ecKP<>%%&z{!oPat{lh>!m0 z?8Hc_Dp;vwYMhw$^HGB9KX>a&96EWM#j+EfclcJjxL7i?t%Xm!10UU8(^|>or1S0^ z-#Ls+byZPwcddWo4LY&4tFeH1C->BXKIZnRTV0JR_7(-EL5|aN&D14JnMl2p7SU|5RX!bq5SvkTnR8-LXV`1 z6US`E+Q&5npWha&^-fxUY%f4B{w+yxwt?r7s`8dfLPtyb#XreBU(T8m>($`VlI1dG zI1i$NI(q7hClssSEO`rK>%-t_bgSzIc6;W?jSuDyU;bKDLVRK=G;4Gr ztw?TME*~9N6o%@_DO|U6K->`d*>G=ham?H&bm>Ah64D3ZH;^Y67-2&Y_-0`6ICW7@ zdDYc?4-mJ&vtTLNyL3_b=t(%_YZ)=)x*^~-+V8OP=Qgwoc+b+NMhO+ySU`^JXaR&&c z0N80JI3xsOIM$IUeGVE;B~Xe&%M#&{vDCfL*L2IA9MxB!&JiLNwO`3vo?lyc{K8jeOG>W+1 z#(T}n$wWu3O}oB9+cq#g@$C(YJbLQmMQhk8xJ!V7qbw>>lSuRE!~?PKXk%zaN-}K;M9cm1c)sN!Qav=7sqIQ z{1I@1GH{S56K19g?Q9MY^|Ia=RKxw~rCHrzBd1=$7_VOo%2FD_{uGQ<9Kjx99Bh4M z5+Eij43=@nbG`V>j^9`5+XN(YOgiUCQ9d^#xY`~fGdnOlt8mnz_FQ} zn7$PHD-%Ue$Re&qE#+<6jVebnr%Ihvti2QaySn628GVqt?V}%I(rLJ(TDdv2XZOTg*G601%nhZ8ykY0f?)h=+dc|wEvR~i2^n{3>RBU5>bTxs{@9T|b zwawfv5?@*@(AZh;!iZg%8COHodLhTCYpRnPw`%Q-`-E(&%(8F#`|uP;pqxZ=dTib| z&#zoXH!G7i&wurOvakFb_~aE3%GjI7&h8)8ACI%vUhuBX)etdi3m>p0sgm&5yVq^Z z+t-;?<8&=1s!@chFiyn{vjdln&JWF2n=(-Hh9dP(mVWIb)NH0}Z>RQgA3fimJ3%&d zrqDdez0puEVLW2)zRRn!!|pVRcZvaCoX-6k*FM?iw>Q3>znx%89?R`& zujD>UO*B&+r?E*Jfj2!`IC|c@Uc*gNX>Pw>ZeN?1Y}R>$Z#0XUT4s$X@xFY%J6Gj! za~3I>o4nJoS-qb^Z_gIqO8AE~i$+Q+9!iiDe6LlOFSG;7?JtV3kFRwb^}r@{X&+P;ieihm!z8QFZ{|(9F=9itXznyPsR$cd(qt*U+f_Q^Xzt`-*wvG2xLsN z=E>;~`0NB4N~`>D;=|&KvZ#j=EXCgBoccmc)c)>=@S>fq-Rqo4J9iRjT?#)AYjisH z{rwHNznh32t&_Ndmg+yVojT-oMY?k$e_H-nqUGbcLLWhobJRfd z@v&^MW_%X8apZJ|0Y z{8xnX%~t*eACY##E}lzhH6M|9dVZ$Ro9gelWpeUbo?E|tZ-9Yw+4Y~D8--5}Q>(!`Mu>cbWv4 z$o=(hjIrKke^%Brm^hT3;yS(^%hp!Tkk8>OSz7gCwWjCjT@roq6TZa8yomN?yT7|+ zT92S8vS!oqQJ}KV=aekzB!Yh8Dz>bQv}H}y1D3&OEPX2kUVeXSAl-T2W6%%bAwF5k zdt*D-?0wKMB`srOs)kY+@;BKJ$NjTrzBtc3%BB=Rs=wBBbKA1}?e%)|82BfPwLIyk zhvf4x`*z>usC)b~DErrhl(_Fv_AOh$JboSkcUsD(E5U!ujNw zgHbLc|Aoq1d%Vpz8PY5X(|xn+bEy%)ISA0u21_?R%bDupSCi%a)n)?syi#-pbx7VC zQbdhfj2%=QjkT)~wc?_AjYTwEn{Njhck~7ngUkPVbkGhh*N~2=H#pPrnD!MEu`osK z^yg#(HOCvMpMI1)P|SZh znD6h&6(ased+!<4RP?AjawLnri*0Lcyd`@ip7GxSoU_k4J6U`0wV!9L^>kA6Mi{E-uQ{9+wrqR1aymR(TVo1-ZZq08){@iKCi`MhO zA}G~V-HI5)JUw0man|V9?9VUA%|#`+h^MI)8x4;EZ|%`rH6p66c4=9auV92esO|oR zJPVN`b-RD_s%M>*J&5?{D4H5{0PNDi)K^~4f2`?{!ktH?tfh)*US@^SBo zLN*b)oV|Tr@d5?|&1kRAsF8vdN+xGTdR_hE0_7Wy3&jIpRah%F3N))1QY!pGrFveE@QI zwEIRLf9AN7qjm#Czt3veb-`&6=T5tv8znwDH(&`bdlPu5H54t9TWKkRcaWg;>|tKY z^jWf6FnTVS>mHAE&_H*^yPzv9FF0+D4?D7AIYShH8b+cw{HYkz`6VFHdHtR?sP6A| z=9}TYC!owTnbW^DPy*hA>5=@BGU2ONAqy!$YQ{mRgs!s`$ zLcL3HpIc3*z>ZTND!cniRPW;Di0oQC(N_|OxmKy%qs=822@{rte%NBAFf*?3PJ_kz z@UGCAa={AC^IT_5&f)Suoi>IR1;<<^*Cv8}D=PCfZ#9D5S2y-tAj8yTj^p{_R!A$l zC&|Z!-LDZMBJcaP<2S1&1Nn3`6N*BxW_dhf&#Fp}*MM&BUYFwtmxkgJ&@0Wd2mPsG+hXAE_j16P{9u)q?G-L|HP zrDS~^hwF_ia$Tgo7-T^KR@ZZHnJ2X^xZmVmTJKcIBX1bA9$wr-IMzM!aR?N-dWBq_ zm1f`9Rt~AlYvV1_O#5x_q(gU|Cwd$$3EP>)K$5$%e3oK+hJ8DO)7US4kifMjOA&G8 z3Ag{!q#Gbau$3P0=oPqjTsBXhm}Qo7M2&HGO}3Y~JJ?CnuIPXNk~-%vRqT9V;nJOg z+Zt3IPG`aKWl#My6eBTP`@na*!TbGr<=3|aL42;;hqU#2W|ieNcHz|AEYYsD;V`tM z*J)={qy_tnx+<{vVHZoE`E78!&G~gJYbhy3;Ob_|H(7dQ|lZBxyC?D!F6Gt zZ;V>|6(R6+?lZpAm(`^B6`-0S5&Ih$P%0SFyLo0mip~9ZjEaR2*hVnYZXNF-L z6Ilt6vex(8ZawE7AzEhD-fX+x1F`uU#bJ3V0z&=xC~4I39vfG`B}Kgl!J$6&{)Dyp ztPh*{@{ZF<9g*vM1(-b~8a`DWTZgtcz8oa{{Be3j9XN+GtdsU3q zq6^s|@qkyJ<7ko&d2_p4GAxNzou4M-YmLwLT5?XetAWKCrs!A19N-84GrFig*W||e zqM=4bVnjRSTM57A+4vab+tp^&KEe_1GFcs|prT)^HcR6oBg(ysT3N9u(1G~JBVs7e zHbfm~GQFl>*xbGud-Yf6OQ5H8sVnr7g7Ct+F|2HJp?i?W7{zsY7%hYgY-%L9n1a~b zDqIf|G&fHR1OsHe4hm!r_an-)p6$#9=JFZTz3m>`PKg^epQ*N_k619Fe9_*Q=HO7r? z*KYIAA2FbP;xbG;_IZk_+V8(KtLLp`5_UR5Dzs~y+xA<1-f%gU7;!bF{^US;iU#SOOb-VO!f5Sb?g{H zEOP4w&ihwZO^cP|N4YGlb5oAGT4;R7^YLpY?g)7odL!uT8vnF0IZh!#F}tM#1|j`+ zde%mI=qcI8RK3EQR6BE#poXm?NN>68HeNLunxgUyCVmK3dfbgDhAl5c&*G4i_W;t>pvO)`~eQb zy`9zyQb8}Lar;Tp&88*&*=cW~t`(1~eXVofU#I1k-cZw_|6Sv9+BY2O48UV{{Zb<3 zv`TX^BNUqF?7K5#Vo*-6;w>k@*~6>FUeja(Om0}+_gZ!`L|Htpo1yGSwgg|#dugbb zXhdNMRhMg@6%g9}>KOPx@a87EQu9G0@^!j?5 znVIxGZmuZv@N~bsQp{0*%ck34?7B+lnvc!L7#n3~W3MXJ5OOBI$z=<{Zkhy#Nf;>h z6e9Sk>kjzU2^P_@gJB-2DpGgND1BG*;YDx)rO)VLh6DfKu78M9F3X_Gyb%+r&;Yw) z*eB;nrF@@1s7>&FO?%zeYxx~lg9c6%-cMROR!TF0MRje0b)5I84)1;3KVsRKUW(m1 zu!b=2o;+#`QI3p0pW8DQ(Vs{2(hho{4Ck$Q2fU_U{)}Kg6 zOa+MZAG!2@Ot`_Dq}BnOJUcDDj6B>q&@7p2&-nO6`~`1?HDMfUsQV~P`gHuGhbgVr z&TvV|TNns;#2~y~LZTznf7M1kUPFu|w4-ynO;GusbPT)4?;7!yy>AL3-VCSXqxON= zgy4|=RIx<}?&xNYIDwo@wJT;AQaz<+an$6r%pF~A*!jnpul#x~}DF={NUuq{R?95>B~Gn2Pp z=PvIy0yt&))uZ9~G-+>oY0!ayfmY_9o0PdvoA+I(SEoBYaB>qT9Z^}=ywOt;>89g= zFK3mU+{DO>p&G7L@YQA?$oa~LMwoWJ=uvkw!7N|U7Mxz^--6nnhPj^l zsQ2~5Aa36h@eGC^3#`d>3&Ld^M?H>@UF@{CX6h#&PeW5+%(9I>o2clN#8kB&5wN?> zW#PV3_`4JEvzG%3dV*Ey@b_)uXkcYN^*nmnk&Cx-i4FEpsbIuWK$}OMdcWr-K;pW1 z$NL)}A|X@HyuXx2{pBflqj}0@J7HBAC#hq&Q1=>9jKz7YJ5g1abYakYD5-RS!@OH^ z`Pik;-Bk>HK2kh-mB;NSoqsX+sxX0{rX%65`-JVc-wn8?j6|foI3k`UJ~{3#Lt^uB zf!uOpnLrvDn=wD^!}r}ED&OyZ8fa9-?9obSYW0qZM6Ty=R;mBl8|TC%WM87u$DoI<0*(_mcGu-h%; z%RC}N{@C*HqE%DR2;LgMi9y1mR3f*!fqQ&;(^|)BozYsL1iP@jI6A)6+CFQwd@< z!&;CxA=hS$)Q{#j3_1u{=?jZuzBW67o0b?|m*v?NL>@doLeD3x^tvu*>1CQ8X=M>k zFe7Qx@Sq(F=~}mi?+G^BcgAhqotaFFawGfcUZY13Zna$&_`-iPFMo+L87Q-yqv*in>@#oLqyiI}(Vtr>(jj}$AwZH#iT>JQ&G_5~o1hucfAx`Kzc`Ym#q0aXgK~C{ zrBRuWof^@KQ|C=7cFL6g2~|}bH&TR>CAiaS9WYSm;S?h~yX#EJ&qt{01s{@JdRi>O zOir{K-;DPr`4c)+JnDLXY($`;cpLV3$emw1MnFdCPvji8@TKUrf3aipSuF*I6X>&8q@R)_8VnT`4pjB8hb=#NtK`Be(Ai&km+9uz@4|Gx_KI*cgB51p{BtW zKbr<28b`TBz*E@H6lh-KYs21m9Zfm2RF^a-&%)KV)zTX_Gagdwx<_HC_|&y;##g=> z9l@Z8A=e!w%&z}qaEI%Q2{ERF=~7N+@7yQti$i27TywtUNb@b7^6FAoud+!2bYGEOg7NzERn#gEtG@ zTjGC99d>z%n>VQhzr)`q_JH-ThID}F>mD%iYPG9G2&Zh)IY#8o z1!NPdr^cA8MaCV+ zmpyFkpIGIGd0|&uz!AYxvuNXJ0fAiqUJVPqCj|ZW`+9zOT5M8Oc|L$xTuTW4>dh~y zgIhH5n!z+)_9Yp4q+yfv#y!J}UT@WToOvmUb;dlpEh`=ao=jgsxbYY^M!++6UFPfp zX%2{agC%|xsH}aXEOlbbUwc(pfCpCHVkwO`h0l2%H7Dr-Npx)vkVgUd!8N2|&-}k9 zG$s#jb|n8mB@sx82_0Cvj%0#3H3}oXa_BVWR7fou`x|v8en&mqiv6|@5qng7@c)Q* ze-YMR#1k>Gi{pPfv*3SG5d0IN$_M#TXXM+J4m%iUinBij$D^lfs`Jl1F`?59anX#qQ;G$Ae_^iYj|n+&jy+Qf{Y4ao>_g$ji#g z@(2$ik^K@Eo=6k5Gp+}SYB+vJiOJTDQd4!EZLm7+L&zL6^TS@xGHWlf&evDcYg&J; zF0qL}f%fuSKnhL9CY+V=L5V$eez>PlnK184F|eM1Cc z;djt)R^(rCZ==H8b=AD?Dmx^8_LPG3Iw{Wfa^VjW5|KzoWR+b;uPdhaf**LV?3kQh z1c91X*@8Y~SWwX=wJPfbe!g-Z;WQ4mE7K>ox_B_Fn5K5^OtI!PX6?uyDf<52oona0 zX{X!af9=}{MtqiW%@^KbP*jvINnqq}XpujCPG@b7w zf6ZngZ{+GS-gh@2!%krf=;`X_%`a-m?AxauHRZ!;*I=@uP_lC3JYD(hIVuS%^5=Mn zWxcOTt?B;B*uRGidCC>+V~aDlU`7T&lcR?90$L0FjwRZ~64wk9LIH3m@e+7qs8d`< z2OHL~KGoXD#Xvc&2BS$hIO8jUnu{QTF&PNBUvoEFR@~-!-i<8Jn|-0Sr<&FkjFy1KQVs_R2iP+-a1tP;Ao2TtX6LLRS(+q(4;uf~;(e!_M`XVw>r zjY+wx3JklR?vQ^r1s$kGb37Y%v$(1b1d+IDoFE#AN|)#jIFDb&OBH~A5v4-jyNQ}W zJ67Cp1`P|Sx+`Uex+bdZOPVuB3k6&l&+ew#*rvISHsWrq$eXPbVL20J`|c-jLBioM z1_=T;FA9xq{V9IWpme5zFZVPq;f$Y+ao!MLDFAkmYpH)Fzt=4ShvJ<(ucHHZn={)i zT_<9lsC(ZwH*@+7lfUX>j(Q|oxH{VM<#=O)Q(m&MVZEJP+koweNaS3aXEpCjkDC=# z3Onfo>6)%>MiHvdYF3fSGT^}(zl?e@@WM2@;a~`I&?x=G$VtSkT`$fi!g6N*)Gk~j zjJ%X~3%{JRYWYKku#LTq)YWpI%Y-8d^P@%0V&(abIOQ)@SGMb$uMO@ulQqvTbdPkU|F-MrB^3nFXP=>({UyKhD= z*&In*J@@o|{z0dhX9}~F5OEsHPrhBS9GiAjmBW!k#txd!*8=nHu>?lV8WkYMk(_KaH1Fc&0 z4OFh`-mV;_qKUW8;N~rqt4ixIHn-{IvzPVU+soX)57C_LT*s`QFB~SFZCNUCsdx3M zhpNY=ajz!mnHf8H5JWZjL%rf{q`12I$tSH!c=Y9af`*D3;$kgi@{4GD*aCD_K z-go_QF-N?=Y1WrM68f%Y*4Ru>=mAMqGCYO(wKtGi00ki8RLzeyA8Rr$oY1tsqoJGhl1<2-2vlWxHA)MJp>QV#xL~Cj0iCq+?v7 z6Ebi=*_K|Bb_73l9a0FMz+$^A0AEeZDo^FK8{nV=VAPQ|lPoqm#}ZUV)L!KPJKS^h ztV<)e`~7{gzZZMVuyUYd(G3%@+}&G(Ta2o4=O3MD?Oyrg3PcFjBjZA70$`rgxD}ak zq3M0>X1~VH{gR3)`r_IuV}U0Y7S%TATtc%ywdo1FL-BmxCB|nfvEwW}MWuACI9>+9 zJ=bchmQWW6ji9fy8Qj-AE%qY2?^0l}pq+N8f7+A0lZieyn&!9(my`4UzAZ{up%R}A zJs+(^kEtynfG%c=m6M;F`%lr?%`nXk3CDd8N4Iks_}3eTS~^nw^YcFp9a^}~_g3uM zU#u9^T~&0iyMw>1*!-fgNl?#Lqt514-CF6x73&_IGNvyYpvCm`7YKK=2p&n%hYts| zSI>>nw0E7!ABS8e#qkYa(|hykd!z7HvMM}kYm!%*{c)3mN_4DUIHH-w1 zB<3_UV67CPbe}z{L*enn>9v&EgYiy~iSpHJpFVxLdY$<;S~jqqghG1*J^c#%q2-;l zr+wmG7PD2Rm)9<)DpL&4Ty=X*K*5-gA*Wv>U1my>TgP$%e( zx2CIV+`9dp!$^7WS%~m_V*5$omOGnh%50@DHIKS zq$>B~Y`wWyg^zJ=Q{AR>>HhDj9IvlKwovSRgYmH4`F55jmm_;bv2uxy1VhBmRN-fE ziqulH-B|kftMH?NHCd%~I`hf5Yy-L5GEO5>Caor9`u8!5d zODWdBEtxE6Wn$m2e@hg0*oZI{qE+3=tRW`ysNu zL#NH3GFrDfKld&0xGNYkmjW8kHYFUkZgyi>_8fC8S0dDl-0v+E z!WiDXj2f7T{BpN{thb|lAM*K6>#;zLp7Y$#%27Yg9Jt8l=OA-S#>&nw-y0}#@DUka z5e49J!>W-jBbi5txPtb~*_)R@+-f*x%Az6R~%UA068L5(i%M%$ZIA?e9Oa zWR^4rIb&i}EGAR!JZ|HArzi}`*(ZyrSH?^dm*r|Z3>xJcvnO=TH+OwYiVPM_mhAYa z%7~#3pX>J3_!G+2j0mQuTq%`1`v`ynZi1ovn6Vf}h1$lSV2zL#Cobtql!GEQAoS&g zjRW&B;0yLTvvk9oWx+r$tW*~UbkJAS2!`=>=)ONZ1 z3L25s-oOJ032_-&X^S@jH?BR6eN(Z#Wm;!%UYy0vWi@(chX%jv8=nqsi7Wc+7NmG2 z0d*faR^r`PJ{q9=>Q|$=XkBG>&A3ub?tB!1RS$>n? zdeyex(iM)pm9^vk?)y{laF;=T!2-=p&XU^Fmjy##KMmNtWp2F@)Oxhg9a65|9ucSFRM$zt=l-0c)AXRSlqSwo?Iw~$1ymfp= z0*rE%e77sX?X(fTxKlOB;i~#LDXac7m-v1$)oKHZt!k?c|WkV{xPgV=VoL}Mp^ zMamQzgU`}{>tJTfc69J`gx^V$QF^siYg78R$&0Jk?Nhb#W;rCnS$NM#*nB{VUZRb< zH#9aF69*>KjK%^2I-V;8$5nxHW^KnqlWi~KgWB(=1fJ5~Mm0!a#L$~*K8bzU3}K$P zYHALinOeu~OUBFPCVAYr&thmHN%IE3)dkNw)9cBHCcg>~-y~IN(P)DU)pQaJr{RfX zH%!tjSh%I?fqMyWL0(=ieh7>4lWUbGZeXaoZr@XYq4=uHulD~4R^I?^`c?ZlY zlZ7@PRQ3C+e1UE2t3Cd7Ub&j_22zHJKaYl;dYQ33K<@t6{VFj>{^lPP?_OD|)lS0< zasea))I2OtPh!EdGDTMThP(2zn7m$Rn~o@4AXVJ*7^6%j8Zv6SUsRhs`jNyHpn;!>Yd9GC9=&@mh({-s`m3jqr-vPgR{C?Hp$>`_)&G<>KKt{Oq* zk`UAbMS`)|j(&M8hRXuHS$9QowVTW3rTt)6(M*s#PrgASs&{H)DoRWB&cHb1L}_kA z->-WhO}0Ac(Rc#|J7__L31oB2^;`v^di z;J2u3CaH@CABc7}n=|{Qy^uYSe}N3OV=DetV3UYwcRya$H+O;*!K7yMm}?-ZP1=^v zGtZdUdv?m92kE#Lgf_T-=V@SpCT*hkQury}A9*Cl8W$?B4Y}ANp8$M_b3p<-5M&-P zyT0$MvRdz9tz>`pr6@)%MelKv)zLKq4z=DX>^~WQuyV*F(Hn@5r)W6yUn({Wv-~tU zu5b$~b+Wt)d~%>>e)-$1RSHt3z`WwA`TAUh=_YaO$gTR!Ax-Ifn({R2(`WApv;^<= zhOk^QbJ`eTYC;P^qQ^N~_uj)HZgQZyL@(}^7xPr9Oh=Ab`*F3+R4I+WQ_fIYy51@e zaHbenTWeBhnl+qJR4yg=>`hppOIELU$i8d+=1b;~SxOq_ajri0>MhvDJM*s@KKReO zk)-}WO{qw0hdb>DdRIyYOwjk*z8YpteVK4+sME6PG+x==q^6t}H)ES{+VZlI-cIrn z#+mH)uv4W87PNERs3NUHs@}YeAZs0^ z@=X&8^~A_%&&fHT71_}M`XR!i+AIN@Xba)dBich>V(pGgmk0(#r;}IM;l?f1ZHf2x zL~YTB5iN$G%>2qsi||dc0@I2cv5NV;3Jw*9Hwr#-MsWLk;yalrdv^RogavJOOFt@2 zU%MSKiQ>Sid_(TY8@^f?fp3k0e8*qMzA6Wv@740ELhi}AV$w;l;)4%T+*=m&I%X$0 zV9KR)_T*Y?177WwrWy4JfJybdRKGzIF~I74t_#W`sQGGW6hpAt5D2d0P$89e9-~yY zp-CC08Z2M^pcq^y9v1Pq_~iIaV3qj|&6=ZJr!QXL#(}(HU3@OJUq>E+LLwb$c_+F& zo`^e>haO7mV3ak_fZMizSLIMFO^`tvjDLNL-t~$E2ANii8oOvS{dKdXk(Z+#sdutu1QFj6DBlj>5o_>x;^7;ht8X0;pr(dgAc7iG zd~@bp53V}(HMj9md%?(f+0y(G;9u4Eq;%s;@?_Hj!ELZ>8tsX=8 zduxP80Y*7_pz5pg>vGr0=%sPBPIVn4>3^o8F;-;-ULDJy^4C6*GnurvRejx@QRP`_ zX8S_6New6x`t$m+kQN9I|M>=^9Y?k9=ZLN?ae*s!?y0Cz>3i9KKMT zgR2~FO|xlZhg&m^X=y75S(fy>^K=;DIX9iGfaYR0s3OJfOjeUBm#l!wSQX6LU_p+t zUj1rFFOo^n3%d>;1=s!&EbB=Y5#;CZksu=)$Glp(^GcykONe>jo~jO;s&yv|@FQTlb%7+N zvZhX)xoFHjLON|sqMZAb)XNgP^zqEy^vwrVPaBtM{a1zsgZ{{$`O!{H)2RB>m4s9g zl{W{>MiI;kdt(l8uRIh~$!LaO?@BUZ+HLmnQ9=e}#!ei7p3R=B2= zcHULL&1E+Uv*A3e?AHcymbw;Jw*Wfu`5bIF%sMUKQ@K);!EH9XK#wz$Az@B<$lTo% zvmvAq;GZP-=qa0O%nOLYkXlJ<^;%u)xOzgBKbk4Ef$NZ)&sc!#c&8g0OOiQFmh!%v z2_D5TB+%u0&r=%lvnZ#&WSAk$gg_Zaz~YnBq;kY?KSnOI0UAL2P>HD8LUna3uZ8p~ zJ{oZCGLYwY_C4aJ@bvXV^zj@}jX^MMbfbzMTCQOHUCbZc{mn(4R$wyD%I#{U27_Tm|wfY0zPmV&eeF} z#zy#WVGGSO)d#SUZggBKt^^;$@7HL6N587JGFpSRh8M*mD%~#%ht`EGd*$) z4Y;*<4wN9shB-m^8gRc^)iyvV-^8%ED%68 zLKo~F>b3AT)0uCkYQGy~Os%DJGw+KM55YZsro!7YDMQB&QuvD8KwsbNJIMDG-YtI? za2~1V@dpdItq;?mDwekzo??j_^Cd=nJM!@H@$mEV2+Ah;9L`fXC4g!JRtFt}bxLmU z@nLPxn|W35Z;8cI=XBx2v`{Q_2Q7&QmSAg;W{9L!OADa2>5pWEj!}Sh|4ulijaJM0 z+q$nbs#CS0Tj4A)miV}+7n=nF3eg6OcXo{exdl4=rK>$K#dpFQK)~2BJz`We@>eEr z-cV2kgHwcVA7(Rh#E~NkT3fCm<*PIJ%_<35f`7LoLzvo(jM#IMt$r|86rW+1==0`u z$I9S{IpW0~mqC+C6w7RxSj9`fJn1w@`5W|3xVx&uxk=c*18pKzYES=6joThqhKqvZ zAOnI$?|Il*EiRL-4!*-@UqAnPUDYV9qJ6;~=m_bem!FB13h^m2#qDH@oeF$;wN!%d zr}+=UU*5i_>b5vS)NBQINN7W2VurMW+Yjv%=TjQqm*+K1>Atcs+K8JW^HJImqgT*- zTJd#Y_fiX;wwaevq7kD|NmZ?l4kU`4Li@%6M4(4*P1MJghG9Vn+rX?#H7k~|^?QFw z_f0W6U}1TFe!*7jk@N$z(TYyj9NTW_XEAb!>Cmkp zc1Mjs{NCH<=w|$v;eOe8b1wq}EOnKQH=6F7iA%LqzOlajS4CN`pvQ(?%Vcr{^JCQQ0aa zT4gY?ZYpCFRZ%+>V`^x$uosw;qXi+mO-@|vL?q5Wa|3=AK9;C>=1TO-Q`hq-Uhhxv zsq?uPZiz7)Q#}vfpM3GDjW+{|QV4{tmf0c)i^RfA;adM;Rbn(U{Q76cE$V0FZl+TF zWgX2r>4E$1a|(YM$P={k|A2bboQUOZpuQk4F6|- z9zXc+&gWhl{_1pH#+>;Jw_9|a<)gY5G3T|P0|R%F{1^8y$|f>L3Rz`#@Z#RZEGF_i z0L{d>S4ge|R9swT7M;9Rz_oC1p~80N*XX9RcL;!HmrsgOD~f+cYArxc84gza{vkSONfPNUy3g{?xA%7e>x&ZC*eA;&J) zkI};jr{r{Et_krsa2~dsdxEK=O{wN$m~=h{T_;DaW|U5^tDap~)25GTKOwHKX)@Q6 zdK+pkdQ$u*vGgAm-1s&^Zqgd_Kj6XsFQ0tr{|uL2?aZ#a? z!0*nrrB=6bOBv5n-S`HqW_B4@&D`I*4|q!(#xC6HaDSsT1~j=e=H!+6!l5%PR2AN# z2dJ2466E0GFfNr!_0H4F8hpu&2?Nw0MA=zN#(A~E9b<>QwYI&7kFr2|7xuSn`O6@DxnVVD6+z-h7dE%>O@hr?% zM<=?WS8%F49`b^W<;7o1uNF-eTgl1Ep*9$+&^MCtYQB68X`=Z%wh=fOPEG>N+F6^C z9a&596P&aFuJwKu7h3lxqn{CMmCzH|oU8Jf@@f=&yY-1&_!sSotGoNbTpIihx>@NX zrvkDl?4fE*Hyhd3^g;Wk3evab$7RrIcCM$P#kyRb&(4UjH0z8jJdK-w@5#pPP_-_; zt|^$-t(!NGT>{+bfiaur7gK>5JvA`lEKB*_yWf*0umFJ;xLWL>{rS63@|?3^ZbqGv zYlrt1d%kdTKhm+g;mmh>eS-ityx#lcSjO#w-`W@IL6f(Rp2OgZHu>O7qs_2Uz zD96TqTRO$;=;%0Xf1lVwMA2qQ`VGO0c>cw$2-0c)8ra*|gKH!`e*0ShKC=bXd@x>; z1i+$IK#1)(wuUa|&=($M60W>6%-Zk5P~%a|Z$YRcx-1R!y7q@fV#z z9i4`C%aL5Kq0(j&fie1(HDq98PHt{HeEz@gM45=N9xt>(?))jBz6x|`M~U$n@mn_R zr;yd{7p65BXjR19p=T<0QVjTviiqmeqf;!$E5`lUeXudj(Y0oxKK%AHz_93b&En=L z(p5%xVNc+k0~X3A(|GE?qGkh$M+VgKmGnsvG{$DO8O8#jMdj~ClU*haqm}_9wStmw z(#TiG%&&V+aXv_mvzg>kb>K-0Z@=w-=auhk`;OyI@jO$=pzACGxh}ohVi5K>(C7Vn zH7Rs%7f{?8HygR=#a0Xiwk5E&A$!5@x`ek`FQVw4Y)my{;NVUssUSz-zCQfqgi$Mk8*DnEDyPEOoX4Br?(9 z6~!DQf3JqixogXRGF8&=3trI3B#gPgkS6buDLBL1zV zhu4v3{s$iejGPo#O_vm+t$9j%G6zK!gNRjUS;gBK@2;gZTxwQSjQ-W!mD0;s^+z|B zZ1La3B}nH|UkW%1@$iOj`w`%gJkwACL8N1lz~*K@Z!H4x_H-^dq@3Jl=X4V6sg1vARetBP(JeBtE011oK@=+K3mzUV88mj-DRAWSXCbH++jtD@p3%|E z^$Ecm^HV|4iy7h7qipJ=j&gUcs8^16+#ngoG-CTxrd-Ggi7KT!n2Yo=19ta}SQ|dl z6nmV>!WRHJH*;}q$0ahG9R10=jZTuT2d=_gVy1mqM2^}LGnmq`P16cAy!W_)*;`&E zvwPmlvw-u>-lP_PZ~NI?m(yaU_F12CmW(xH{l)2i(z|Wkv6ivncN|5NMbvZL9UInf z&+qvbIyx%rgg?fLEm*x@{~OPi7+&|XdT-YJx<_bO*sWW)7%WIXXPD8VO!;r>{vO++A1_!vSyRefV(qEoiefr>yFJ8l8&xlDhaX}oC)IJ6 z)}nxS_C0$$WzJTcS9L?yi>$EyZh~)btEuigM+tP76!M0RmumA|zqkh8cjg0LfJD13 zx}D0WcmCJxDZs*~iM2aMzuafNo*aIM&cn)oVqNZb?R=y|K`_8D^f0wfvR@lASQ!}= zElXRC6VmZE&^NI#F%-0%p!_HsqierozUQGK$u z_M^Ib;){`)v;OWne=JAdfVfEQWPgLA@E;`AJt1e8eUPPXFa&`__#1sW>?=P4zqEvYYqBa3}fP&^}{*_jDk4jxUQ+<%K#C#fZRFe&k~QL8=zW)4(BA(H);1PS%eh8gv+4X zx*5XG?`caee(pEJ2`)!RdDs2HsyaG*k+Y}IdM^vYflo7-179d0gTnr^xO$4JiSP#K zt>(x9n=~Ar1Gv(e10a)PtDna+&Za%C-(hO+3v}pi4$2HVvr=jJ@!bhKiTF_dX2_{e zlQAOS;CMBrpE6L5zpGqPvU(XIk?w~}vdaJ$?;Z72^w-1vea1R*?Xy6}puq=s9}GNp zyJkFz|Ef>QWAJQE-9=lBKwuHXTNS)b5vB4u5C!k7Z8fKo=s?sud#hz&*^yE+7 zo}_1Wkk{FmLu?ls=#!^rb16SkKSz_(R81e92YX;OYvLSNg-HppB*Iq$-s#b+BPv4nsnq z-aPq(xr$kBqjo%dE92n0-~v{F3&KkpoIx-?JIus1=WqL;VgD2cBf;=>JLJQ~KMK(` z3$ynZ4qXxn#s{{0J{sxo%Ma~Nb$aa|GfH`%p|usjqNmlK4LWLx-3YIazPt=@Rek+4 zW9;*9E0LWN9c^t`8VS1}s? zl&i(z36uK9=~bsdL!IR3&6S5j1hZ0qOs907in~l0d2s;lBNwfm)R5iW%w|324t+}1 zn(vZjog8>Js{{<&d+7&NmwFP_trOjNp4EXrwrS9lQjZuLS#1Y=ZFX{C5APar5{arm<+kNtf_ zh7(Chi{Ha1n>pii0f&2eYF%BG($Lv;T+6nE&t`JrxaAtHt;LOhedS>2Zvl*lP}a$-7^y6$a{iilz*&wR_g6OMM*=o@QkjvfLd9j-4h=Rq|*n$QQ>0IM7mJ zjDr#!t+|Kvr!zpkefhKDPx8^W==JqGI=$EDusF=9G^k}bP_*_ZbHyBJUHYICz|1%* zKk;*PEwe>N-ZCz5=^UfEBcxy5k9dPYhJ}Vd0JeB3T{}7;mNfu3)X*wYDRXEl5FFdW z74R!C0(Qxg89xo8kQcYXvEXG-%h2NIZ$ux+c15%Mtr?sHY6CT%pZE#l5D%vM3T9nv z11kZvVKep7+8zbYdp)sL2vkA8oGs9Kj=o1>`YC%(u9ov8 z(mq^EW8237gR)i9*UcLl8QD4aH-CbHT_@l=E|)d*ikes*q-TXxQBa*JO;>8Gu8S>4 z5q#JB1`#ce~LcMemHH~F(fKt}CJNHofXqk9(@uUU#cL>N&Zig|=Q z4E_VYWdGgD&+osNOeK0YcGk>=`K8rneYt;B(PU}-9p8x2LPb9`xa!_2vFo zG`RhiYWhE&=Uyx0Y(6??q>F+xs7`)!J-&Bc1=A2pohl$qeh(*;A369`#AbfR@*U zk^kzK;3J4_u8WXUtc_1k$3ALqmN1uC?1dwRczKQl{#JXPXUwr#0W?Yg4!=HHc>Aj> z8-p#D1A3!}^Ts`d>fKLnWW`-P!kUp;?%mh>v7E8sC00vq-Z+-R zqA|1tp&Hnaj~UX1PmfBnTr}CO%Nbs0Wns=c!|EhEcc>1JI%s4X}7(((P6#>Ev) n=!Bu~b9rd{dW<^$`CUB4QLetXWys=R`%_icdS3kOP0)V>P_YY> literal 52464 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 z zI;|T^rYCPS5XO}4=)H|fJCK#Kt5R&cvmX^VC0n12TU;FdVveF=Yu)6MK`TQ!1IXr(qfseX8AsxP{QNpvh^68<%zq>Cr>C6U?Vc|h2RAf-$%D&zJ}cRUtN^W z7=VEk@bkXttJ`u?)_1iZGquEty+Hpg;_Hhy ztEK&H`3I(t?%gaNZ^=JE|NfO(MxF#d2mUv0g(pU$iHaX6JA$IL7G`3sxm4|Wt&{a^ z@f1^)HHS8>VggZJeDO&j_%IC-Wg3nt@y?BLq1mRd{WzzJyYWrwYpP7){3Y=KTMT1C zfZmU;K-)Wp4^(jE)ZrEv>7v|zpp^iQ^$*in1GfW+FbyVUz8fObx{zLWu#&v6!T-7oDfF-?%WcdS3uxrdkr((#0#F{}05YwI8 zu;OIJnPIP!WMM17_hnfANKQ|ClaQjR&Cj5a@Vx3$myykeTagotXMlx4MKM)Old8fv z2QK4&EeVj{(f<&4dKs>jE&-@k5?V55>q;VcMcJ;z+$q?prQ;65;Fohc)CtE_R>o3` zH+ELx9dNL7VL$^YS!q_7QDPwjlb2&g6~Pif68t zR2)eUxn@=78re!1KU%L34`VXt-r59EdWe>*QRV2Y0A@UH`CQrysD1()O@nIs3E>Tk z2U{2_E{w>Vw+)p&93Uwh^#zx@$)WR(Ro{_lDPu8pxgR#_4BwPKFXtY6ykOc*`MNig zQbOzGf$LDA0+z2gltZ^wbV9Fv?LV^>Vo%v=NE)$i7&MR}{M>N^uV z8KZw`$7e)yoYWl8U>=fStW|qsfeC`~@;$<$<*Oo%at8N^!W%3X{*L|-&9=||K5o?7 z?uS3lsuI&SQKMIW@BgNbHl3?AIS5vJmC^StIh~FNAI0cU%9w3+InUi{_dzZ8s z!_2hx^CqN$0`fx|JzVGkB6r@Q$l~Ye@Ig#alL9ZRT3ae$Oa!imvaf}eKoIs4S#HZB zpa|(9{K(QS>v?7DgiQeI=A)kNv0|!v^b7=?Et-_zs2?c4^mjg`zMutuhjJ;-pq~j4 zJr}){R_n!u)#kZpTGj5^`-`WGemMN~Ea$I&zk$Vbmi47cNqzd^HN5tF+2hMw)qc#q zpkw)5sS1LU{_(i6{AMEmqKvmD`fLtQjm#}IU}mhtfmP4xb=Mz9{+y#mKH1)FLdR-) z_toi2WE(}V?hh;y$LYvJmn6G|1bi-T5CdcMS z-0BStuD*(mEguV(^hT_m6I#O1w+L7S$Z~id`yy5e^1LX!e|e*m!Vs=8zbvyB`xgbaN6;0NqfEm=1ymr?#i{PDf69Y}T|JAomV5?}(AcXIhhX&dn#)&3sBeYkWb? z&cCyJEt!DTmpq8iJG!dyK$m#CCup3H)z;GBy_~GZ9kB%~x`Z~i5tpJd! zHJ$c{g9L#w!uRz}FvJ#2Z!Q)|dw>c%D}TYZbB2AP_0QoY0sPo(LM3Xk{1kGd?-+pu zqW9T9O9!e6hot%w;IBrG9!Bjtc}Ir-GAFdVnE>oGRA}DQ0O!7@zpl%L>|ypN zTi@qwI{#_&+D>@>4PUk1P5d$3;dzcqX2LpYfYke)l z(d!H2>T{12Gi`Yt0<)(2_XDdTT z^7q6$GGZEhVj7r7~&FNrx>Q?+~Y2$Z?*W+L>uc7VQkA;Hj9S5lhNy)uEm@Q zhQ`k(R(|zYQSF|(HMSZ1R(3A)=NG42^~^fzp)t8knT>C;>kR2BuSM5&HF1);te^s_&Cdj3 zmp+sTdJ&tTi7Zuq*aBWcAv}GB8&u_F3oXJ;we66a*MmeVX|zBZ^r6xqdt|AKuY8~G z!r)E53k}i_h2?wFeBI&o)dI`C&$B4cJ$2n?RqH)gFV>SfI|HS3wI1ll@O!3kbaaLp zMxhFcgR=1Idz#{R8fiW3Uw5Qr9#$ziz;n0r0{TwOHV1^yv$F0Y#;ShehRG2AJbByF zM=npj7>E3k>%fPVup$$Bz-6gK-Ll8DGPD4_?W6dHlb&JgG_K&CbGXq8)8;o-38%K8 zAaHwheL39vQF{+Yp3$)fVP5;)iG-ND-+dpCTU+6)7}zuN@H&E8l{;M-E;&RgA>g3Y zNxyiyB;4uN%tj)j>wkUod>f}hjR>$T+S7<3c_c;6N0b!k71FJpbKz2#V<1^UA{83C zPvXvrt`F+>W@Rmxr^f0@RngMq?8E4pz9Q5T0l*_W#GqCJA z@`KRc2&2fbN-0OdtJw6kQ*4UXNAD2Q|C3jgO;5VWgkv}?4 z)s$DC56=X%xhy1}(*7Jt+CFaXp+$G133t*oi4x_)j~n*ss}jXWr0b)Yr#kPZuY2}B zyFXedkZ{^_IC0rB=@ELak+A#h&km0ywb7&NDOll~l(4b*0l}cijxbB*f!;T=7J8D8 z9#-lmcex+tK%3-Finp;M^}qC@+bM~*b7p!*5|Wi`6vH8 zFBm75m8r$Lrb!kVcs_pCOdKHwmlz-W4`T48i>r~A%JoPf;O)_kPy?VA{2rrzT4%|4 zC%q#opL28B*QzZ%{d$3B+T)AbHA7n|&EBP-s-7;q=-g#msPgC&rC+LcBmX=HS@FZY zt^<}k{o(FT14*Aj$7ez!Qb9NI@Gs#<7Uz1C#w!0sJ0qa7BlheaylTr5T4a? zJ&FjpbDQ;a*FmJ>+fQ+2(y~G%0}jrL_XM4$$D4artm1f!L+9cH-d7viN{m^T*NzY3 zPdZ&To>&)>rN-Fih({y40-&kuL{|nfuCwu2n;izSsO&_u3yi$&{W-+uVmPsvAhZ=v z>tA7%K)TFd`ebb1ft>vSbz~Bby-vW@qTkR#clc^Q7FL8?)ur4Vn$JeJgC2#tY*F*6 zLF{$sW36c{^hf3g_@%=wS&3%^CL<(hnKFsz7zS88{l^st%)N{`tO>4>TH;)s4&7VM zNrUbxG=elE+q%7mJO*~DE)&gdk4tn@k}ReKj}kZQFKZ0yrSu4LBkP5u;}vE(V?h%p zc6{szaj1vyj^r%_vS@sAbW0j^k>y^zr~gcG@@UtMFegOl(FZxzw{Dw>LfmB+&zh9; zQK7!sX*HJX*KxcPxYsoXlF|!rzBziodnjvVq?2(B^(ruYvNO9RzPYTnf7+n*Ze&&2 zudGp1w+bA-vYdfO1>0VT=M$35?px2bKNv}U44C{wBB2)$AH$*A&xUTe(Kw*hEJLx-|Y zvPe%62ULGe1OIe3&Dex_+iFzdaZ!5a#?V7FFi{D6^sRt2>WPy=RA>{QWT2if!TiHW z)!M1aOjsc6=~N`Dx`mlhO?7xY>LtgJWjl8Ec!_b^u$W8B&`DT1O(^MS`cO2SUZU8V zl`=M=J9oG&gc4rDM{8<}AMZ8@{YL@FD%t4tZg!`%D=|GkG<1x zrOYHtCP;172z*c?c`zPH*Av_ANV+vKk2^7BqdSI8Ajs{m>2b4(AJ4)CIs@#Mz!fP_ zgz(=t^#4Eku}NG|&dbHezipuP%@w?f>F--@$)(D}7MiN}nVGaN)^?faDO)mEhn1W2 z%*)+RVPn(Zzp&i~i95ds+pCUispg3#I~bDPH!`(`+UZ<2GQ)Q|!gnwo_T3f>6)vCr z?%Xb79o!+={k+9x`J?)dp3U-cuUt6;R4rk-VM3-1dH%hMR^z{`r-o~4^A^^%i9HWb zf-jU*?qB>ya6yZ$>7MqY+{eKy^_}w!i&-Ut!Cqfx#;<#fEYgfEro-@}kr$umO)jQi z9hV>9@)I=Ui946?Dh_n+>6L?u@aR!{c&AXRAbgcR2s4`xA;=e32+V@FaHUJScF^M4h|a!TMyKJ70lZotkRB zDzx+?tz#ScUiq3B{-I-T`nxsk$TvZEg13_gWq;-^il7mb>foN+n#+J{j25x0MlE!n zQIJu?R$;i49+hr)5g=s7!z)kCVCPIj3O(p6YS2+thAldeb}1K~HP|vetxa~05JQ|} z&^w!!r$-!0PIM!CiOK~P3DDl)YL=CqJ8u>QM~6f zJUs@~I-N;cNypz87JPpRjVCC^k{lhs+CQd=_L0BG#H^+3wzayd?y+QUjQ!)3YhRzy zZD)SP+gPHN?5i}gb6-Y_`+VaN`}ialuG3x>_!W8(elNovCF8SPS`{75sh?`PyS9wM zUe+!xpC*?JG42doOb&f5EV3~kLUcCL`YXFTKJ9cGQ!R#%AedF_$G#TY^e!m-2osS)W2tYNA-}O^w6!B)$%h5}vs5*F`c@&bM>U zO}7+#aVGi8jMWJQ_TQ+Ai@}YC?i!LgPLkBCUa5}Hi}I2Gvj4j~wsQ`f40*e8Db}~- zu+AEuQfB6I4|(+3_Aa-qN?Y1vl_5CQI!*P+QN!{4d$2+TeZG;N;e^-t?7TvDvr9#0;KH!+xt$B{2WD>F#8l}?e)UJcna=r(*r;Y_gRVQ))(IzZ{|#;g z>2e*yn{B!8G3c$ky3~b_!rIba4}~sl-h3M(S742;L_KE2fH33?IUy>Uf4SI=>IMZo z*NQ+jq|ShWpzEv_wP0uvEwehoerND ziK4PS(b@H@xpB(awaMZq2@YG*9$Wl2f{%I<3Z0}F?(`%=T@o5h5~h3?57x&@tPa5z zQ8DRqaD>0Qa=5VNgoIuU@aymo-;?ni0aX%ZSj)YJG_~CEl-&##6SnB(t}yz!F+^dg zXmuFZJu!AohB?p4GyE1K!dtY7r#{;}SFXp_0~DFI09L1@RVC<`O?6r38G1wxJ3_A3 z?La4_+Lck~E5CUxMkA*csT`nDn4p2bFzl{nw>pf*DP+cOoZntR#xK*5wiSBr3OCKA zGg`S1Zz&O=ukc!xauP%FSUKp1M8k*MXcn{@7P)*E_m(IcGBvr9abSAOtmI&pDrj2BbmF@=%wMV+xD3fg9i zstd;}%*|J_3X2NbPTGv07U9IB|I;@Yqww$O%imLRcieXUv{g$R&Wf{8JT$Yx-(9V0NNQ|u-}%QWR+lA{c9Ldnras1TJIzk z#S@1nR&%WzpInYgH1vi|#@h9##N5-c!z0weZ2vyb#pzs^OVL+*_nuzZpDD%C+T<(J z!wQVHV7f^V^0eJ?38$Km1dVG2G=KxUaA*SmMy}ccW|utVNI8-`smW~@HyWv{dcP1k zaMap$mD`cSpp(>cUhcIvasI_nC_{(vH2azSY}=;-Fa*DgrIdwlXZxNhcFYAR zX6t(D`~BHnbKV+?lPxPphX4*cp}MjG6!eV6k_o?O!>rb&LqmZ(BGhhiI7%6Qc;>Qs z9~@k+J2^icEe-^`OCnH%El5zNgk&&ayjGZP#s`1MCh#Vjg{pTLtenn~O^qHM6Z1Ww zsUsjEI;gn^ib7=q%581(%w{%dccWgys>4w diff --git a/docs/web-configurator-add-ons.md b/docs/web-configurator-add-ons.md index 3a71bc4e2..f86d65902 100644 --- a/docs/web-configurator-add-ons.md +++ b/docs/web-configurator-add-ons.md @@ -163,28 +163,12 @@ Enabling this add-on will allow you to use GP2040-CE on a PS4 with an 8 minute t * `I2C Block` - The block of I2C to use (i2c0 or i2c1). * `I2C Speed` - Sets the speed of I2C communication. Common values are `100000` for standard, or `400000` for fast. -Supported Extension Controllers and their mapping is as follows: - -| GP2040-CE | Nunchuck | Classic | Guitar Hero Guitar | -|-----------|----------|--------------|--------------------| -| B1 | C | B | Green | -| B2 | Z | A | Red | -| B3 | | Y | Blue | -| B4 | | X | Yellow | -| L1 | | L | | -| L2 | | ZL | | -| R1 | | R | | -| R2 | | ZR | | -| S1 | | Select | | -| S2 | | Start | | -| A1 | | Home | | -| D-Pad | | D-Pad | Strum Up/Down | -| Analog | Left | Left & Right | Left | - -Classic Controller support includes Classic, Classic Pro, and NES/SNES Mini Controllers. +Classic Controller support includes Classic, Classic Pro, and NES/SNES Mini Controllers. Original Classic Controller L & R triggers are analog sensitive, where Pro triggers are not. +Due to an accessory hardware issue, Drum & DJ turntable controllers may require hot-swapping from a Nunchuk or Classic controller before being usable. + ## SNES Input ![GP2040-CE Configurator - SNES Input](assets/images/gpc-add-ons-snespad-input.png) diff --git a/lib/WiiExtension/WiiExtension.cpp b/lib/WiiExtension/WiiExtension.cpp index 6c10586ca..6d17ca105 100644 --- a/lib/WiiExtension/WiiExtension.cpp +++ b/lib/WiiExtension/WiiExtension.cpp @@ -48,9 +48,9 @@ void WiiExtension::start(){ extensionController = new NunchuckExtension(); } else if (idRead[5] == 0x01) { extensionType = WII_EXTENSION_CLASSIC; - if (idRead[0] == 0x01) { - extensionType = WII_EXTENSION_CLASSIC_PRO; - } + //if (idRead[0] == 0x01) { + // extensionType = WII_EXTENSION_CLASSIC_PRO; + //} extensionController = new ClassicExtension(); } else if (idRead[5] == 0x03) { extensionType = WII_EXTENSION_GUITAR; From 5ede15334571b63e13a7bc655a1871dce5e3a4a1 Mon Sep 17 00:00:00 2001 From: Mike Parks Date: Thu, 7 Sep 2023 20:12:35 -0700 Subject: [PATCH 11/15] Fixes unintentional tabs and moved unneeded debug variables behind WII_EXTENSION_DEBUG --- headers/addons/wiiext.h | 10 +++++----- lib/WiiExtension/README.md | 1 - lib/WiiExtension/WiiExtension.cpp | 8 +++++--- lib/WiiExtension/WiiExtension.h | 17 ++++++++++------- src/addons/wiiext.cpp | 6 +++--- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/headers/addons/wiiext.h b/headers/addons/wiiext.h index fe6a694e8..3d7377367 100644 --- a/headers/addons/wiiext.h +++ b/headers/addons/wiiext.h @@ -82,11 +82,11 @@ typedef struct { class WiiExtensionInput : public GPAddon { public: - virtual bool available(); - virtual void setup(); // WiiExtension Setup - virtual void process(); // WiiExtension Process - virtual void preprocess() {} - virtual std::string name() { return WiiExtensionName; } + virtual bool available(); + virtual void setup(); // WiiExtension Setup + virtual void process(); // WiiExtension Process + virtual void preprocess() {} + virtual std::string name() { return WiiExtensionName; } private: WiiExtension * wii; uint32_t uIntervalMS; diff --git a/lib/WiiExtension/README.md b/lib/WiiExtension/README.md index 1321909f7..f375f90eb 100644 --- a/lib/WiiExtension/README.md +++ b/lib/WiiExtension/README.md @@ -1,5 +1,4 @@ # WiiExtension -# Ported to BitBangI2C # # Written by: # Mike Parks diff --git a/lib/WiiExtension/WiiExtension.cpp b/lib/WiiExtension/WiiExtension.cpp index 6d17ca105..6238cd306 100644 --- a/lib/WiiExtension/WiiExtension.cpp +++ b/lib/WiiExtension/WiiExtension.cpp @@ -211,9 +211,11 @@ void WiiExtension::poll() { extensionController->process(regRead); extensionController->postProcess(); +#if WII_EXTENSION_DEBUG==true for (int i = 0; i < result; ++i) { _lastRead[i] = regRead[i]; } +#endif if (extensionType == WII_EXTENSION_TURNTABLE) { regWrite[0] = 0xFB; @@ -254,7 +256,7 @@ int WiiExtension::doI2CRead(uint8_t *pData, int iLen) { } uint8_t WiiExtension::doI2CTest() { - int result; + int result; uint8_t rxdata; result = doI2CRead(&rxdata, 1); #if WII_EXTENSION_DEBUG==true @@ -264,8 +266,8 @@ uint8_t WiiExtension::doI2CTest() { } void WiiExtension::doI2CInit() { - if ((iSDA + 2 * i2c_hw_index(picoI2C))%4 != 0) return; - if ((iSCL + 3 + 2 * i2c_hw_index(picoI2C))%4 != 0) return; + if ((iSDA + 2 * i2c_hw_index(picoI2C))%4 != 0) return; + if ((iSCL + 3 + 2 * i2c_hw_index(picoI2C))%4 != 0) return; i2c_init(picoI2C, iSpeed); gpio_set_function(iSDA, GPIO_FUNC_I2C); diff --git a/lib/WiiExtension/WiiExtension.h b/lib/WiiExtension/WiiExtension.h index cecdfb2b6..b3ee39113 100644 --- a/lib/WiiExtension/WiiExtension.h +++ b/lib/WiiExtension/WiiExtension.h @@ -59,6 +59,7 @@ typedef enum { #endif #ifndef WII_EXTENSION_ENCRYPTION +// if a device acts funny with "unencrypted" mode, enable this. used in very rare cases. #define WII_EXTENSION_ENCRYPTION false #endif @@ -99,7 +100,7 @@ static volatile bool WiiExtension_alarmFired; class WiiExtension { protected: - uint8_t address; + uint8_t address; public: int8_t extensionType = WII_EXTENSION_NONE; int8_t dataType = WII_DATA_TYPE_0; @@ -107,24 +108,26 @@ class WiiExtension { bool isReady = false; // Constructor - WiiExtension(int sda, int scl, i2c_inst_t *i2cCtl, int32_t speed, uint8_t addr); + WiiExtension(int sda, int scl, i2c_inst_t *i2cCtl, int32_t speed, uint8_t addr); // Methods void begin(); - void reset(); - void start(); - void poll(); + void reset(); + void start(); + void poll(); ExtensionBase* getController() { return extensionController; }; private: - ExtensionBase *extensionController = NULL; + ExtensionBase *extensionController = NULL; uint8_t iSDA; uint8_t iSCL; i2c_inst_t *picoI2C; - int32_t iSpeed; + int32_t iSpeed; +#if WII_EXTENSION_DEBUG==true uint8_t _lastRead[16] = {0xFF}; +#endif int doI2CWrite(uint8_t *pData, int iLen); int doI2CRead(uint8_t *pData, int iLen); diff --git a/src/addons/wiiext.cpp b/src/addons/wiiext.cpp index c9dd8c374..235e2ba53 100644 --- a/src/addons/wiiext.cpp +++ b/src/addons/wiiext.cpp @@ -498,17 +498,17 @@ void WiiExtensionInput::updateAnalogState() { } uint16_t WiiExtensionInput::getAverage(std::vector const& changes) { - std::vector values; + uint16_t values = 0; if (changes.empty()) { return 0; } for (auto currVal = changes.begin(); currVal != changes.end(); ++currVal) { - values.push_back(currVal->analogValue); + values += currVal->analogValue; } - return std::accumulate(values.begin(), values.end(), 0) / values.size(); + return values / changes.size(); } uint16_t WiiExtensionInput::getDelta(std::vector const& changes, uint16_t baseValue) { From 0566ac9116359c3e854769559d56a8a1c25bf6cd Mon Sep 17 00:00:00 2001 From: Mike Parks Date: Fri, 8 Sep 2023 23:14:54 -0700 Subject: [PATCH 12/15] Minor view refactors on input selectors --- www/src/Addons/Wii.tsx | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/www/src/Addons/Wii.tsx b/www/src/Addons/Wii.tsx index 9a0b57f86..c50b60392 100644 --- a/www/src/Addons/Wii.tsx +++ b/www/src/Addons/Wii.tsx @@ -103,7 +103,7 @@ const Wii = ({ values, errors, handleChange, handleCheckbox }) => { .reduce( (o, i) => { let modeID = i.value; - let joyMode = WII_JOYSTICK_MODES.filter((o,i) => o.value == modeID)[0]; + let joyMode = getJoystickMode(modeID); if (joyMode && joyMode.options) { let r = o; Object.keys(joyMode.options).forEach(key => { @@ -128,19 +128,12 @@ const Wii = ({ values, errors, handleChange, handleCheckbox }) => { } }; - const setWiiControlEntry = (controlID,buttonID,e) => { - let controlEntry = {}; - controlEntry[`${controlID.toLowerCase()}.button${buttonID}`] = e.target.value; - setWiiControls(controls => ({ - ...wiiControls, - ...controlEntry - })); - }; + const getJoystickMode = (searchValue: number) => WII_JOYSTICK_MODES.find(({ value }) => parseInt(value) === parseInt(searchValue)); const setWiiAnalogEntry = (controlID,analogID,e) => { let analogEntry = {}; let modeID = e.target.value; - let joyMode = WII_JOYSTICK_MODES.filter((o,i) => o.value == modeID)[0]; + let joyMode = getJoystickMode(modeID); if (joyMode && joyMode.options) { let r = analogEntry; Object.keys(joyMode.options).forEach(key => { @@ -267,7 +260,7 @@ const Wii = ({ values, errors, handleChange, handleCheckbox }) => { {controlObj.inputs.digital?.map((buttonObj,buttonID) => (
- setWiiControls((controls) => ({...controls,[`${controlObj.id.toLowerCase()}.button${buttonObj.id}`]: e.target.value,}))}> {BUTTON_MASKS.map((o, i) => (