diff --git a/src/spice2x/games/gitadora/gitadora.cpp b/src/spice2x/games/gitadora/gitadora.cpp index e0935c1..92524cb 100644 --- a/src/spice2x/games/gitadora/gitadora.cpp +++ b/src/spice2x/games/gitadora/gitadora.cpp @@ -24,6 +24,7 @@ namespace games::gitadora { bool P1_LEFTY = false; bool P2_LEFTY = false; std::optional SUBSCREEN_OVERLAY_SIZE; + std::optional PICK_ALGO = socd::SocdAlgorithm::PreferRecent; /* * Prevent GitaDora from creating folders on F drive @@ -227,8 +228,15 @@ namespace games::gitadora { overlay::UI_SCALE_PERCENT = 250; } - // for guitar wail SOCD cleaning + // for guitar wail SOCD cleaning socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent; + + // for guitar picking + if (PICK_ALGO.has_value()) { + log_info("gitadora", "guitar pick SOCD algorithm: {}", static_cast(PICK_ALGO.value())); + } else { + log_info("gitadora", "guitar pick SOCD algorithm: legacy"); + } } } diff --git a/src/spice2x/games/gitadora/gitadora.h b/src/spice2x/games/gitadora/gitadora.h index a6ebf71..ffe0d57 100644 --- a/src/spice2x/games/gitadora/gitadora.h +++ b/src/spice2x/games/gitadora/gitadora.h @@ -4,6 +4,7 @@ #include "avs/game.h" #include "games/game.h" +#include "util/socd_cleaner.h" namespace games::gitadora { @@ -14,6 +15,7 @@ namespace games::gitadora { extern bool P1_LEFTY; extern bool P2_LEFTY; extern std::optional SUBSCREEN_OVERLAY_SIZE; + extern std::optional PICK_ALGO; class GitaDoraGame : public games::Game { public: diff --git a/src/spice2x/games/gitadora/j33i.cpp b/src/spice2x/games/gitadora/j33i.cpp index 424c964..82a0a59 100644 --- a/src/spice2x/games/gitadora/j33i.cpp +++ b/src/spice2x/games/gitadora/j33i.cpp @@ -92,22 +92,44 @@ bool games::gitadora::J33ISerialDevice::parse_msg( payload.buttons |= 1 << GUITAR_BTN_P; } - if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickUp])) { - if (!GFDM_GF_PICK_STATE_UP) { - GFDM_GF_PICK_STATE_UP = true; - payload.buttons |= 1 << GUITAR_PICK_UP; + + const auto pick_up = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickUp]); + const auto pick_down = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickDown]); + if (!games::gitadora::PICK_ALGO.has_value()) { + // legacy behavior (detect rising edges only, input for 1 frame) + if (pick_up) { + if (!GFDM_GF_PICK_STATE_UP) { + GFDM_GF_PICK_STATE_UP = true; + payload.buttons |= 1 << GUITAR_PICK_UP; + } + } else { + GFDM_GF_PICK_STATE_UP = false; + } + + if (pick_down) { + if (!GFDM_GF_PICK_STATE_DOWN) { + GFDM_GF_PICK_STATE_DOWN = true; + payload.buttons |= 1 << GUITAR_PICK_DOWN; + } + } else { + GFDM_GF_PICK_STATE_DOWN = false; } } else { - GFDM_GF_PICK_STATE_UP = false; - } - - if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickDown])) { - if (!GFDM_GF_PICK_STATE_DOWN) { - GFDM_GF_PICK_STATE_DOWN = true; + const auto socd = socd::socd_clean( + 0, + pick_up, + pick_down, + get_performance_milliseconds(), + games::gitadora::PICK_ALGO.value() + ); + if (socd == socd::SocdCCW) { + payload.buttons |= 1 << GUITAR_PICK_UP; + } else if (socd == socd::SocdCW) { + payload.buttons |= 1 << GUITAR_PICK_DOWN; + } else if (socd == socd::SocdBoth) { + payload.buttons |= 1 << GUITAR_PICK_UP; payload.buttons |= 1 << GUITAR_PICK_DOWN; } - } else { - GFDM_GF_PICK_STATE_DOWN = false; } auto &analogs = get_analogs(); diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index 6f99a1c..ed1b5d4 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -595,6 +595,16 @@ int main_implementation(int argc, char *argv[]) { if (options[launcher::Options::GitaDoraWailHold].is_active()) { socd::TILT_HOLD_MS = options[launcher::Options::GitaDoraWailHold].value_uint32(); } + if (options[launcher::Options::GitaDoraPickAlgo].is_active()) { + const auto text = options[launcher::Options::GitaDoraPickAlgo].value_text(); + if (text == "legacy") { + games::gitadora::PICK_ALGO.reset(); + } else if (text == "neutral") { + games::gitadora::PICK_ALGO = socd::SocdAlgorithm::Neutral; + } else if (text == "raw") { + games::gitadora::PICK_ALGO = socd::SocdAlgorithm::None; + } + } if (options[launcher::Options::GitaDoraSubOverlaySize].is_active()) { games::gitadora::SUBSCREEN_OVERLAY_SIZE = options[launcher::Options::GitaDoraSubOverlaySize].value_text(); } diff --git a/src/spice2x/launcher/options.cpp b/src/spice2x/launcher/options.cpp index 19acc2d..c6a599b 100644 --- a/src/spice2x/launcher/options.cpp +++ b/src/spice2x/launcher/options.cpp @@ -1020,6 +1020,25 @@ static const std::vector OPTION_DEFINITIONS = { .game_name = "GitaDora", .category = "Game Options", }, + { + // GitaDoraPickAlgo + .title = "GitaDora Picking Algorithm", + .name = "gdpickalgo", + .desc = "Select picking algorithm for guitar.\n\n" + "recent (default): use SOCD cleaner algorithm that prioritizes recent input\n\n" + "legacy: only the rising edge of the pick input is considered; cannot hold pick for menu navigation\n\n" + "neutral: if both up and down are pressed, they cancel out\n\n" + "raw: no filtering done by spice; game receives both input as-is, possible to input both up and down at the same time", + .type = OptionType::Enum, + .game_name = "GitaDora", + .category = "Game Options", + .elements = { + {"recent", ""}, + {"legacy", ""}, + {"neutral", ""}, + {"raw", ""} + }, + }, { // GitaDoraSubOverlaySize .title = "GitaDora Subscreen Overlay Size", diff --git a/src/spice2x/launcher/options.h b/src/spice2x/launcher/options.h index bbe1ba5..7c0fa31 100644 --- a/src/spice2x/launcher/options.h +++ b/src/spice2x/launcher/options.h @@ -103,6 +103,7 @@ namespace launcher { GitaDoraArenaSingleWindow, GitaDoraLefty, GitaDoraWailHold, + GitaDoraPickAlgo, GitaDoraSubOverlaySize, LoadJubeatModule, LoadReflecBeatModule, diff --git a/src/spice2x/misc/extdev.cpp b/src/spice2x/misc/extdev.cpp index 4fff9fa..c0b9c0f 100644 --- a/src/spice2x/misc/extdev.cpp +++ b/src/spice2x/misc/extdev.cpp @@ -522,24 +522,42 @@ static long __cdecl gfdm_unit_get_input_p(int device, size_t player) { if (games::gitadora::is_guitar()) { auto offset = player * 11; - // pick up - if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[3 + offset]))) { - if (!GFDM_GF_PICK_STATE_UP[player]) { - GFDM_GF_PICK_STATE_UP[player] = true; - ret |= 0x80 | 0x20; + const auto pick_up = Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[3 + offset])); + const auto pick_down = Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[4 + offset])); + if (!games::gitadora::PICK_ALGO.has_value()) { + // legacy behavior (detect rising edges only, input for 1 frame) + if (pick_up) { + if (!GFDM_GF_PICK_STATE_UP[player]) { + GFDM_GF_PICK_STATE_UP[player] = true; + ret |= 0x80 | 0x20; + } + } else { + GFDM_GF_PICK_STATE_UP[player] = false; } - } else { - GFDM_GF_PICK_STATE_UP[player] = false; - } - // pick down - if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[4 + offset]))) { - if (!GFDM_GF_PICK_STATE_DOWN[player]) { - GFDM_GF_PICK_STATE_DOWN[player] = true; - ret |= 0x100 | 0x20; + if (pick_down) { + if (!GFDM_GF_PICK_STATE_DOWN[player]) { + GFDM_GF_PICK_STATE_DOWN[player] = true; + ret |= 0x100 | 0x20; + } + } else { + GFDM_GF_PICK_STATE_DOWN[player] = false; } } else { - GFDM_GF_PICK_STATE_DOWN[player] = false; + const auto socd = socd::socd_clean( + player, + pick_up, + pick_down, + get_performance_milliseconds(), + games::gitadora::PICK_ALGO.value() + ); + if (socd == socd::SocdCCW) { + ret |= 0x80 | 0x20; // pick up + } else if (socd == socd::SocdCW) { + ret |= 0x100 | 0x20; // pick down + } else if (socd == socd::SocdBoth) { + ret |= 0x100 | 0x80 | 0x20; // up and down + } } // button R diff --git a/src/spice2x/util/socd_cleaner.cpp b/src/spice2x/util/socd_cleaner.cpp index 167db77..1fa6ed2 100644 --- a/src/spice2x/util/socd_cleaner.cpp +++ b/src/spice2x/util/socd_cleaner.cpp @@ -15,18 +15,23 @@ namespace socd { SocdAlgorithm ALGORITHM = SocdAlgorithm::Neutral; - static double last_rising_edge[2][2] = {}; - static bool last_button_state[2][2] = {}; + static double last_rising_edge[4][2] = {}; + static bool last_button_state[4][2] = {}; // this has no locking, use only if you know there is only one i/o thread that will call this - SocdResult socd_clean(uint8_t device, bool ccw, bool cw, double time_now) { - if (device >= 2) { + SocdResult socd_clean( + uint8_t device, bool ccw, bool cw, double time_now, + std::optional algorithm_override) { + + if (device >= 4) { log_fatal("socd", "invalid device index in socd_clean: {}", device); } - // SOCD last input algorithm needs to keep track of rising edge times - if (ALGORITHM != SocdAlgorithm::Neutral) { - // detect rising edges + SocdAlgorithm algo = + algorithm_override.has_value() ? algorithm_override.value() : ALGORITHM; + + // keep track of rising edge times + if (algo == SocdAlgorithm::PreferRecent || algo == SocdAlgorithm::PreferFirst) { if (!last_button_state[device][SocdCCW] && ccw) { last_rising_edge[device][SocdCCW] = time_now; } @@ -55,7 +60,7 @@ namespace socd { const auto cw_time = last_rising_edge[device][SocdCW]; log_debug("socd", "ccw={}, cw ={}", ccw_time, cw_time); - if (ALGORITHM == SocdAlgorithm::PreferRecent) { + if (algo == SocdAlgorithm::PreferRecent) { // SOCD: prefer last input if (ccw_time < cw_time) { // while CCW is being held, CW got pressed @@ -67,7 +72,7 @@ namespace socd { // it's a tie; instead of none, we'll pick a direction return SocdCW; } - } else if (ALGORITHM == SocdAlgorithm::PreferFirst) { + } else if (algo == SocdAlgorithm::PreferFirst) { // SOCD: keep first input if (ccw_time < cw_time) { // while CCW is being held, CW got pressed @@ -79,9 +84,15 @@ namespace socd { // it's a tie; instead of none, we'll pick a direction return SocdCW; } - } else { + } else if (algo == SocdAlgorithm::Neutral) { // SOCD: neutral when both are pressed return SocdNone; + } else if (algo == SocdAlgorithm::None) { + // SOCD: both are pressed + return SocdBoth; + } else { + log_fatal("socd", "invalid SOCD algorithm"); + return SocdNone; } } @@ -97,7 +108,8 @@ namespace socd { log_fatal("socd", "invalid device index in socd_clean: {}", device); } - const auto socd_result = socd_clean(device, up, down, time_now); + const auto socd_result = socd_clean( + device + 2, up, down, time_now, SocdAlgorithm::PreferRecent); if (up) { most_recent_active[device][TiltUp] = time_now; @@ -125,7 +137,7 @@ namespace socd { result = TiltUp; } else if (socd_result == SocdCW) { result = TiltDown; - } else { + } else if (socd_result == SocdBoth) { result = TiltUp; } } else if (is_up) { diff --git a/src/spice2x/util/socd_cleaner.h b/src/spice2x/util/socd_cleaner.h index 1ed2c08..c6432c4 100644 --- a/src/spice2x/util/socd_cleaner.h +++ b/src/spice2x/util/socd_cleaner.h @@ -1,5 +1,6 @@ #pragma once +#include #include namespace socd { @@ -9,7 +10,8 @@ namespace socd { enum class SocdAlgorithm { Neutral, PreferRecent, - PreferFirst + PreferFirst, + None, }; extern SocdAlgorithm ALGORITHM; @@ -17,10 +19,16 @@ namespace socd { typedef enum _SocdResult { SocdCCW = 0, SocdCW = 1, - SocdNone = 2 + SocdNone = 2, + SocdBoth = 3 } SocdResult; - SocdResult socd_clean(uint8_t device, bool ccw, bool cw, double time_now); + SocdResult socd_clean( + uint8_t device, + bool ccw, + bool cw, + double time_now, + std::optional algorithm = std::nullopt); // for guitar wail (up/down only)