mirror of
https://github.com/ravinrabbid/DonCon2040.git
synced 2024-11-20 03:37:07 +01:00
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.
This commit is contained in:
parent
1644e1e611
commit
2bcbf1e458
@ -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
|
||||
|
@ -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<Id, uint16_t> &raw_values);
|
||||
void updateAnalogInputState(Utils::InputState &input_state, const std::map<Id, uint16_t> &raw_values);
|
||||
std::map<Id, uint16_t> sampleInputs();
|
||||
|
||||
public:
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -66,6 +66,8 @@ class Menu {
|
||||
ChangeUsbModeKeyboardP1,
|
||||
ChangeUsbModeKeyboardP2,
|
||||
ChangeUsbModeXbox360,
|
||||
ChangeUsbModeXbox360AnalogP1,
|
||||
ChangeUsbModeXbox360AnalogP2,
|
||||
ChangeUsbModeMidi,
|
||||
ChangeUsbModeDebug,
|
||||
|
||||
|
@ -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:
|
||||
|
@ -4,14 +4,17 @@
|
||||
#include "pico/time.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
|
||||
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::Id, uint16_t> Drum::sampleInputs() {
|
||||
std::map<Id, uint32_t> 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<Drum::Id, uint16_t> &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<Drum::Id, uint16_t> &raw_values) {
|
||||
struct buffer_entry {
|
||||
uint16_t value;
|
||||
uint32_t timestamp;
|
||||
};
|
||||
|
||||
static std::map<Id, std::deque<buffer_entry>> 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; }
|
||||
|
@ -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;
|
||||
|
@ -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,11 +238,11 @@ 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() {
|
||||
usb_report_t InputState::getXinputBaseReport() {
|
||||
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.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
|
||||
@ -249,8 +253,8 @@ usb_report_t InputState::getXinputReport() {
|
||||
| (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.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
|
||||
|
||||
@ -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) << "]" //
|
||||
<< std::setw(5) << drum.ka_left.analog << "[" << std::setw(8) << bar(drum.ka_left.analog) << "]" //
|
||||
<< "(" << (drum.don_left.triggered ? "*" : " ") << "| " //
|
||||
<< std::setw(4) << drum.don_left.raw << "[" << std::setw(8) << bar(drum.don_left.raw) << "]" //
|
||||
<< std::setw(5) << drum.don_left.analog << "[" << std::setw(8) << bar(drum.don_left.analog) << "]" //
|
||||
<< "|" << (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) << "]" //
|
||||
<< 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";
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,8 @@ const std::map<Menu::Page, const Menu::Descriptor> Menu::descriptors = {
|
||||
{"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}}, //
|
||||
@ -95,7 +97,7 @@ const std::map<Menu::Page, const Menu::Descriptor> Menu::descriptors = {
|
||||
};
|
||||
|
||||
Menu::Menu(std::shared_ptr<SettingsStore> 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<State>({{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();
|
||||
|
Loading…
Reference in New Issue
Block a user