From 2bcbf1e4587d172d1b710f4d35eae3275bb81beb Mon Sep 17 00:00:00 2001 From: Frederik Walk Date: Sat, 12 Oct 2024 18:38:42 +0200 Subject: [PATCH] Add XInput Analog mode This mode will map the readouts of the drum to the left (for player 1) or right (for player 2) analogstick axis. --- README.md | 3 +- include/peripherals/Drum.h | 4 +- include/usb/usb_driver.h | 2 + include/utils/InputState.h | 6 +- include/utils/Menu.h | 2 + src/peripherals/Display.cpp | 4 ++ src/peripherals/Drum.cpp | 81 +++++++++++++++++++++----- src/usb/usb_driver.c | 2 + src/utils/InputState.cpp | 112 ++++++++++++++++++++++++++---------- src/utils/Menu.cpp | 40 ++++++++----- 10 files changed, 191 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index ee5a433..b33fa59 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,8 @@ The firmware is pretty much tailored to this specific use case, if you are looki - Dualshock 3 - Switch Pro Controller - XInput - - Keyboard + - XInput Analog (Compatible with [TaikoArcadeLoader](https://github.com/esuo1198/TaikoArcadeLoader) analog input) + - Keyboard (Mapping: 'DFJK' / 'CBN,') - MIDI - Debug mode (will output current state via USB serial) - Additional buttons via external i2c GPIO expander diff --git a/include/peripherals/Drum.h b/include/peripherals/Drum.h index 8ff0b28..acfee9a 100644 --- a/include/peripherals/Drum.h +++ b/include/peripherals/Drum.h @@ -51,7 +51,6 @@ class Drum { private: enum class Id { - NONE, DON_LEFT, KA_LEFT, DON_RIGHT, @@ -74,6 +73,7 @@ class Drum { class AdcInterface { public: + // This is expected to return a 12bit value. virtual uint16_t read(uint8_t channel) = 0; }; @@ -98,6 +98,8 @@ class Drum { private: void updateRollCounter(Utils::InputState &input_state); + void updateDigitalInputState(Utils::InputState &input_state, const std::map &raw_values); + void updateAnalogInputState(Utils::InputState &input_state, const std::map &raw_values); std::map sampleInputs(); public: diff --git a/include/usb/usb_driver.h b/include/usb/usb_driver.h index 3934996..f76e451 100644 --- a/include/usb/usb_driver.h +++ b/include/usb/usb_driver.h @@ -23,6 +23,8 @@ typedef enum { USB_MODE_KEYBOARD_P1, USB_MODE_KEYBOARD_P2, USB_MODE_XBOX360, + USB_MODE_XBOX360_ANALOG_P1, + USB_MODE_XBOX360_ANALOG_P2, USB_MODE_MIDI, USB_MODE_DEBUG, } usb_mode_t; diff --git a/include/utils/InputState.h b/include/utils/InputState.h index f2da7b1..df7f37b 100644 --- a/include/utils/InputState.h +++ b/include/utils/InputState.h @@ -16,7 +16,7 @@ struct InputState { struct Drum { struct Pad { bool triggered; - uint16_t raw; + uint16_t analog; }; Pad don_left, ka_left, don_right, ka_right; @@ -62,7 +62,9 @@ struct InputState { usb_report_t getPS3InputReport(); usb_report_t getPS4InputReport(); usb_report_t getKeyboardReport(Player player); - usb_report_t getXinputReport(); + usb_report_t getXinputBaseReport(); + usb_report_t getXinputDigitalReport(); + usb_report_t getXinputAnalogReport(Player player); usb_report_t getMidiReport(); usb_report_t getDebugReport(); diff --git a/include/utils/Menu.h b/include/utils/Menu.h index f573377..386e192 100644 --- a/include/utils/Menu.h +++ b/include/utils/Menu.h @@ -66,6 +66,8 @@ class Menu { ChangeUsbModeKeyboardP1, ChangeUsbModeKeyboardP2, ChangeUsbModeXbox360, + ChangeUsbModeXbox360AnalogP1, + ChangeUsbModeXbox360AnalogP2, ChangeUsbModeMidi, ChangeUsbModeDebug, diff --git a/src/peripherals/Display.cpp b/src/peripherals/Display.cpp index 1fe6b09..9488f61 100644 --- a/src/peripherals/Display.cpp +++ b/src/peripherals/Display.cpp @@ -45,6 +45,10 @@ static std::string modeToString(usb_mode_t mode) { return "Keyboard P2"; case USB_MODE_XBOX360: return "Xbox 360"; + case USB_MODE_XBOX360_ANALOG_P1: + return "Analog P1"; + case USB_MODE_XBOX360_ANALOG_P2: + return "Analog P2"; case USB_MODE_MIDI: return "MIDI"; case USB_MODE_DEBUG: diff --git a/src/peripherals/Drum.cpp b/src/peripherals/Drum.cpp index 860394b..d7c55cd 100644 --- a/src/peripherals/Drum.cpp +++ b/src/peripherals/Drum.cpp @@ -4,14 +4,17 @@ #include "pico/time.h" #include +#include namespace Doncon::Peripherals { Drum::InternalAdc::InternalAdc(const Drum::Config::AdcInputs &adc_inputs) { - adc_gpio_init(adc_inputs.don_left + 26); - adc_gpio_init(adc_inputs.don_right + 26); - adc_gpio_init(adc_inputs.ka_left + 26); - adc_gpio_init(adc_inputs.ka_right + 26); + static const uint adc_base_pin = 26; + + adc_gpio_init(adc_base_pin + adc_inputs.don_left); + adc_gpio_init(adc_base_pin + adc_inputs.don_right); + adc_gpio_init(adc_base_pin + adc_inputs.ka_left); + adc_gpio_init(adc_base_pin + adc_inputs.ka_right); adc_init(); } @@ -80,6 +83,7 @@ Drum::Drum(const Config &config) : m_config(config) { std::map Drum::sampleInputs() { std::map values; + // Oversample ADC inputs to get rid of ADC noise for (uint8_t sample_number = 0; sample_number < m_config.sample_count; ++sample_number) { for (const auto &pad : m_pads) { values[pad.first] += m_adc->read(pad.second.getChannel()); @@ -138,10 +142,7 @@ void Drum::updateRollCounter(Utils::InputState &input_state) { input_state.drum.previous_roll = previous_roll; } -void Drum::updateInputState(Utils::InputState &input_state) { - // Oversample ADC inputs to get rid of ADC noise - const auto raw_values = sampleInputs(); - +void Drum::updateDigitalInputState(Utils::InputState &input_state, const std::map &raw_values) { auto get_threshold = [&](const Id target) { switch (target) { case Id::DON_LEFT: @@ -152,8 +153,6 @@ void Drum::updateInputState(Utils::InputState &input_state) { return m_config.trigger_thresholds.ka_left; case Id::KA_RIGHT: return m_config.trigger_thresholds.ka_right; - case Id::NONE: - return (uint16_t)0; } return (uint16_t)0; }; @@ -180,11 +179,6 @@ void Drum::updateInputState(Utils::InputState &input_state) { set_max(Id::DON_LEFT, Id::KA_LEFT); set_max(Id::DON_RIGHT, Id::KA_RIGHT); - input_state.drum.don_left.raw = raw_values.at(Id::DON_LEFT); - input_state.drum.ka_left.raw = raw_values.at(Id::KA_LEFT); - input_state.drum.don_right.raw = raw_values.at(Id::DON_RIGHT); - input_state.drum.ka_right.raw = raw_values.at(Id::KA_RIGHT); - input_state.drum.don_left.triggered = m_pads.at(Id::DON_LEFT).getState(); input_state.drum.ka_left.triggered = m_pads.at(Id::KA_LEFT).getState(); input_state.drum.don_right.triggered = m_pads.at(Id::DON_RIGHT).getState(); @@ -193,6 +187,63 @@ void Drum::updateInputState(Utils::InputState &input_state) { updateRollCounter(input_state); } +void Drum::updateAnalogInputState(Utils::InputState &input_state, const std::map &raw_values) { + struct buffer_entry { + uint16_t value; + uint32_t timestamp; + }; + + static std::map> buffer; + + uint32_t now = to_ms_since_boot(get_absolute_time()); + + std::for_each(raw_values.cbegin(), raw_values.cend(), [&](const auto &entry) { + const auto &id = entry.first; + const auto &raw = entry.second; + auto &buf = buffer[id]; + + // Clear outdated values, i.e. anything older than debounce_delay to allow for convenient configuration. + while (!buf.empty() && (buf.front().timestamp + m_config.debounce_delay_ms) <= now) { + buf.pop_front(); + } + + // Insert current value. + buf.push_back({raw, now}); + + // Set maximum value for each pads buffer window. + const auto get_max = [](const auto &input) { + return std::max_element(input.cbegin(), input.cend(), + [](const auto &a, const auto &b) { return a.value < b.value; }) + ->value; + }; + + // Map 12bit raw value to 16bit + const auto raw_to_uint16 = [](uint16_t raw) { return ((raw << 4) & 0xFFF0) | ((raw >> 8) & 0x000F); }; + + switch (id) { + case Id::DON_LEFT: + input_state.drum.don_left.analog = raw_to_uint16(get_max(buf)); + break; + case Id::DON_RIGHT: + input_state.drum.don_right.analog = raw_to_uint16(get_max(buf)); + break; + case Id::KA_LEFT: + input_state.drum.ka_left.analog = raw_to_uint16(get_max(buf)); + break; + case Id::KA_RIGHT: + input_state.drum.ka_right.analog = raw_to_uint16(get_max(buf)); + break; + } + }); +} + +void Drum::updateInputState(Utils::InputState &input_state) { + const auto raw_values = sampleInputs(); + + updateDigitalInputState(input_state, raw_values); + updateAnalogInputState(input_state, raw_values); +} + void Drum::setDebounceDelay(const uint16_t delay) { m_config.debounce_delay_ms = delay; } void Drum::setThresholds(const Config::Thresholds &thresholds) { m_config.trigger_thresholds = thresholds; } diff --git a/src/usb/usb_driver.c b/src/usb/usb_driver.c index f61c094..d3ae3d8 100644 --- a/src/usb/usb_driver.c +++ b/src/usb/usb_driver.c @@ -86,6 +86,8 @@ void usb_driver_init(usb_mode_t mode) { usbd_send_report = send_hid_keyboard_report; usbd_receive_report = NULL; break; + case USB_MODE_XBOX360_ANALOG_P1: + case USB_MODE_XBOX360_ANALOG_P2: case USB_MODE_XBOX360: usbd_desc_device = &xinput_desc_device; usbd_desc_cfg = xinput_desc_cfg; diff --git a/src/utils/InputState.cpp b/src/utils/InputState.cpp index 3b84fbd..0b129a5 100644 --- a/src/utils/InputState.cpp +++ b/src/utils/InputState.cpp @@ -28,7 +28,11 @@ usb_report_t InputState::getReport(usb_mode_t mode) { case USB_MODE_KEYBOARD_P2: return getKeyboardReport(Player::Two); case USB_MODE_XBOX360: - return getXinputReport(); + return getXinputDigitalReport(); + case USB_MODE_XBOX360_ANALOG_P1: + return getXinputAnalogReport(Player::One); + case USB_MODE_XBOX360_ANALOG_P2: + return getXinputAnalogReport(Player::Two); case USB_MODE_MIDI: return getMidiReport(); case USB_MODE_DEBUG: @@ -234,25 +238,25 @@ usb_report_t InputState::getKeyboardReport(InputState::Player player) { return {(uint8_t *)&m_keyboard_report, sizeof(hid_nkro_keyboard_report_t)}; } -usb_report_t InputState::getXinputReport() { - m_xinput_report.buttons1 = 0 // - | (controller.dpad.up ? (1 << 0) : 0) // Dpad Up - | ((controller.dpad.down || drum.don_left.triggered) ? (1 << 1) : 0) // Dpad Down - | ((controller.dpad.left || drum.ka_left.triggered) ? (1 << 2) : 0) // Dpad Left - | (controller.dpad.right ? (1 << 3) : 0) // Dpad Right - | (controller.buttons.start ? (1 << 4) : 0) // Start - | (controller.buttons.select ? (1 << 5) : 0) // Select - | (false ? (1 << 6) : 0) // L3 - | (false ? (1 << 7) : 0); // R3 +usb_report_t InputState::getXinputBaseReport() { + m_xinput_report.buttons1 = 0 // + | (controller.dpad.up ? (1 << 0) : 0) // Dpad Up + | (controller.dpad.down ? (1 << 1) : 0) // Dpad Down + | (controller.dpad.left ? (1 << 2) : 0) // Dpad Left + | (controller.dpad.right ? (1 << 3) : 0) // Dpad Right + | (controller.buttons.start ? (1 << 4) : 0) // Start + | (controller.buttons.select ? (1 << 5) : 0) // Select + | (false ? (1 << 6) : 0) // L3 + | (false ? (1 << 7) : 0); // R3 - m_xinput_report.buttons2 = 0 // - | (controller.buttons.l ? (1 << 0) : 0) // L1 - | (controller.buttons.r ? (1 << 1) : 0) // R1 - | (controller.buttons.home ? (1 << 2) : 0) // Guide - | ((controller.buttons.south || drum.don_right.triggered) ? (1 << 4) : 0) // A - | ((controller.buttons.east || drum.ka_right.triggered) ? (1 << 5) : 0) // B - | (controller.buttons.west ? (1 << 6) : 0) // X - | (controller.buttons.north ? (1 << 7) : 0); // Y + m_xinput_report.buttons2 = 0 // + | (controller.buttons.l ? (1 << 0) : 0) // L1 + | (controller.buttons.r ? (1 << 1) : 0) // R1 + | (controller.buttons.home ? (1 << 2) : 0) // Guide + | (controller.buttons.south ? (1 << 4) : 0) // A + | (controller.buttons.east ? (1 << 5) : 0) // B + | (controller.buttons.west ? (1 << 6) : 0) // X + | (controller.buttons.north ? (1 << 7) : 0); // Y m_xinput_report.lt = 0; m_xinput_report.rt = 0; @@ -265,6 +269,52 @@ usb_report_t InputState::getXinputReport() { return {(uint8_t *)&m_xinput_report, sizeof(xinput_report_t)}; } +usb_report_t InputState::getXinputDigitalReport() { + getXinputBaseReport(); + + m_xinput_report.buttons1 |= (drum.don_left.triggered ? (1 << 1) : 0) // Dpad Down + | (drum.ka_left.triggered ? (1 << 2) : 0); // Dpad Left + + m_xinput_report.buttons2 |= (drum.don_right.triggered ? (1 << 4) : 0) // A + | (drum.ka_right.triggered ? (1 << 5) : 0); // B + + return {(uint8_t *)&m_xinput_report, sizeof(xinput_report_t)}; +} + +usb_report_t InputState::getXinputAnalogReport(InputState::Player player) { + getXinputBaseReport(); + + int16_t x = 0; + int16_t y = 0; + + auto map_to_axis = [](uint16_t raw) -> uint16_t { return raw >> 1; }; + + if (drum.ka_left.analog > drum.don_left.analog) { + x = -map_to_axis(drum.ka_left.analog); + } else { + x = map_to_axis(drum.don_left.analog); + } + + if (drum.ka_right.analog > drum.don_right.analog) { + y = map_to_axis(drum.ka_right.analog); + } else { + y = -map_to_axis(drum.don_right.analog); + } + + switch (player) { + case Player::One: + m_xinput_report.lx = x; + m_xinput_report.ly = y; + break; + case Player::Two: + m_xinput_report.rx = x; + m_xinput_report.ry = y; + break; + } + + return {(uint8_t *)&m_xinput_report, sizeof(xinput_report_t)}; +} + usb_report_t InputState::getMidiReport() { struct state { bool last_triggered; @@ -287,8 +337,8 @@ usb_report_t InputState::getMidiReport() { target.on = false; } - if (new_state.triggered && new_state.raw > target.velocity) { - target.velocity = new_state.raw; + if (new_state.triggered && new_state.analog > target.velocity) { + target.velocity = new_state.analog; } target.last_triggered = new_state.triggered; @@ -305,7 +355,7 @@ usb_report_t InputState::getMidiReport() { m_midi_report.status.side_stick = side_stick.on; auto convert_range = [](uint16_t in) { - uint16_t out = in / 16; + uint16_t out = in / 256; return uint8_t(out > 127 ? 127 : out); }; @@ -320,17 +370,17 @@ usb_report_t InputState::getMidiReport() { usb_report_t InputState::getDebugReport() { std::stringstream out; - auto bar = [](uint16_t val) { return std::string(val / 512, '#'); }; + auto bar = [](uint16_t val) { return std::string(val / 8191, '#'); }; if (drum.don_left.triggered || drum.ka_left.triggered || drum.don_right.triggered || drum.ka_right.triggered) { - out << "(" << (drum.ka_left.triggered ? "*" : " ") << "( " // - << std::setw(4) << drum.ka_left.raw << "[" << std::setw(8) << bar(drum.ka_left.raw) << "]" // - << "(" << (drum.don_left.triggered ? "*" : " ") << "| " // - << std::setw(4) << drum.don_left.raw << "[" << std::setw(8) << bar(drum.don_left.raw) << "]" // - << "|" << (drum.don_right.triggered ? "*" : " ") << ") " // - << std::setw(4) << drum.don_right.raw << "[" << std::setw(8) << bar(drum.don_right.raw) << "]" // - << ")" << (drum.ka_right.triggered ? "*" : " ") << ") " << std::setw(4) << drum.ka_right.raw << "[" // - << std::setw(8) << bar(drum.ka_right.raw) << "]" // + out << "(" << (drum.ka_left.triggered ? "*" : " ") << "( " // + << std::setw(5) << drum.ka_left.analog << "[" << std::setw(8) << bar(drum.ka_left.analog) << "]" // + << "(" << (drum.don_left.triggered ? "*" : " ") << "| " // + << std::setw(5) << drum.don_left.analog << "[" << std::setw(8) << bar(drum.don_left.analog) << "]" // + << "|" << (drum.don_right.triggered ? "*" : " ") << ") " // + << std::setw(5) << drum.don_right.analog << "[" << std::setw(8) << bar(drum.don_right.analog) << "]" // + << ")" << (drum.ka_right.triggered ? "*" : " ") << ") " // + << std::setw(5) << drum.ka_right.analog << "[" << std::setw(8) << bar(drum.ka_right.analog) << "]" // << "\n"; } diff --git a/src/utils/Menu.cpp b/src/utils/Menu.cpp index 34756c2..b8b3990 100644 --- a/src/utils/Menu.cpp +++ b/src/utils/Menu.cpp @@ -14,20 +14,22 @@ const std::map Menu::descriptors = { {"BOOTSEL", Menu::Descriptor::Action::GotoPageBootsel}}, // 0}}, // - {Menu::Page::DeviceMode, // - {Menu::Descriptor::Type::Selection, // - "Mode", // - {{"Swtch Tata", Menu::Descriptor::Action::ChangeUsbModeSwitchTatacon}, // - {"Swtch Pro", Menu::Descriptor::Action::ChangeUsbModeSwitchHoripad}, // - {"Dualshock3", Menu::Descriptor::Action::ChangeUsbModeDS3}, // - {"PS4 Tata", Menu::Descriptor::Action::ChangeUsbModePS4Tatacon}, // - {"Dualshock4", Menu::Descriptor::Action::ChangeUsbModeDS4}, // - {"Keybrd P1", Menu::Descriptor::Action::ChangeUsbModeKeyboardP1}, // - {"Keybrd P2", Menu::Descriptor::Action::ChangeUsbModeKeyboardP2}, // - {"Xbox 360", Menu::Descriptor::Action::ChangeUsbModeXbox360}, // - {"MIDI", Menu::Descriptor::Action::ChangeUsbModeMidi}, // - {"Debug", Menu::Descriptor::Action::ChangeUsbModeDebug}}, // - 0}}, // + {Menu::Page::DeviceMode, // + {Menu::Descriptor::Type::Selection, // + "Mode", // + {{"Swtch Tata", Menu::Descriptor::Action::ChangeUsbModeSwitchTatacon}, // + {"Swtch Pro", Menu::Descriptor::Action::ChangeUsbModeSwitchHoripad}, // + {"Dualshock3", Menu::Descriptor::Action::ChangeUsbModeDS3}, // + {"PS4 Tata", Menu::Descriptor::Action::ChangeUsbModePS4Tatacon}, // + {"Dualshock4", Menu::Descriptor::Action::ChangeUsbModeDS4}, // + {"Keybrd P1", Menu::Descriptor::Action::ChangeUsbModeKeyboardP1}, // + {"Keybrd P2", Menu::Descriptor::Action::ChangeUsbModeKeyboardP2}, // + {"Xbox 360", Menu::Descriptor::Action::ChangeUsbModeXbox360}, // + {"Analog P1", Menu::Descriptor::Action::ChangeUsbModeXbox360AnalogP1}, // + {"Analog P2", Menu::Descriptor::Action::ChangeUsbModeXbox360AnalogP2}, // + {"MIDI", Menu::Descriptor::Action::ChangeUsbModeMidi}, // + {"Debug", Menu::Descriptor::Action::ChangeUsbModeDebug}}, // + 0}}, // {Menu::Page::TriggerThreshold, // {Menu::Descriptor::Type::Selection, // @@ -95,7 +97,7 @@ const std::map Menu::descriptors = { }; Menu::Menu(std::shared_ptr settings_store) - : m_store(settings_store), m_active(false), m_state_stack({{Page::Main, 0}}){}; + : m_store(settings_store), m_active(false), m_state_stack({{Page::Main, 0}}) {}; void Menu::activate() { m_state_stack = std::stack({{Page::Main, 0}}); @@ -287,6 +289,14 @@ void Menu::performSelectionAction(Menu::Descriptor::Action action) { m_store->setUsbMode(USB_MODE_XBOX360); gotoParent(); break; + case Descriptor::Action::ChangeUsbModeXbox360AnalogP1: + m_store->setUsbMode(USB_MODE_XBOX360_ANALOG_P1); + gotoParent(); + break; + case Descriptor::Action::ChangeUsbModeXbox360AnalogP2: + m_store->setUsbMode(USB_MODE_XBOX360_ANALOG_P2); + gotoParent(); + break; case Descriptor::Action::ChangeUsbModeMidi: m_store->setUsbMode(USB_MODE_MIDI); gotoParent();