mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
gitadora: option for guitar picking algorithm (#528)
## Link to GitHub Issue, if one exists n/a ## Description of change Adds an option to pick from four algorithms for guitar picking input. The default behavior is being switched from legacy (rising edge only, always 1 frame of input) to SOCD "prefer recent" algorithm. This makes it slightly easier to navigate the menu with the guitar. ## Testing Validated gw, gwdelta, and sdvx just in case.
This commit is contained in:
@@ -24,6 +24,7 @@ namespace games::gitadora {
|
|||||||
bool P1_LEFTY = false;
|
bool P1_LEFTY = false;
|
||||||
bool P2_LEFTY = false;
|
bool P2_LEFTY = false;
|
||||||
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||||
|
std::optional<socd::SocdAlgorithm> PICK_ALGO = socd::SocdAlgorithm::PreferRecent;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prevent GitaDora from creating folders on F drive
|
* Prevent GitaDora from creating folders on F drive
|
||||||
@@ -227,8 +228,15 @@ namespace games::gitadora {
|
|||||||
overlay::UI_SCALE_PERCENT = 250;
|
overlay::UI_SCALE_PERCENT = 250;
|
||||||
}
|
}
|
||||||
|
|
||||||
// for guitar wail SOCD cleaning
|
// for guitar wail SOCD cleaning
|
||||||
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
|
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
|
||||||
|
|
||||||
|
// for guitar picking
|
||||||
|
if (PICK_ALGO.has_value()) {
|
||||||
|
log_info("gitadora", "guitar pick SOCD algorithm: {}", static_cast<int>(PICK_ALGO.value()));
|
||||||
|
} else {
|
||||||
|
log_info("gitadora", "guitar pick SOCD algorithm: legacy");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
#include "games/game.h"
|
#include "games/game.h"
|
||||||
|
#include "util/socd_cleaner.h"
|
||||||
|
|
||||||
namespace games::gitadora {
|
namespace games::gitadora {
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ namespace games::gitadora {
|
|||||||
extern bool P1_LEFTY;
|
extern bool P1_LEFTY;
|
||||||
extern bool P2_LEFTY;
|
extern bool P2_LEFTY;
|
||||||
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||||
|
extern std::optional<socd::SocdAlgorithm> PICK_ALGO;
|
||||||
|
|
||||||
class GitaDoraGame : public games::Game {
|
class GitaDoraGame : public games::Game {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -92,22 +92,44 @@ bool games::gitadora::J33ISerialDevice::parse_msg(
|
|||||||
payload.buttons |= 1 << GUITAR_BTN_P;
|
payload.buttons |= 1 << GUITAR_BTN_P;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickUp])) {
|
|
||||||
if (!GFDM_GF_PICK_STATE_UP) {
|
const auto pick_up = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickUp]);
|
||||||
GFDM_GF_PICK_STATE_UP = true;
|
const auto pick_down = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickDown]);
|
||||||
payload.buttons |= 1 << GUITAR_PICK_UP;
|
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 {
|
} else {
|
||||||
GFDM_GF_PICK_STATE_UP = false;
|
const auto socd = socd::socd_clean(
|
||||||
}
|
0,
|
||||||
|
pick_up,
|
||||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickDown])) {
|
pick_down,
|
||||||
if (!GFDM_GF_PICK_STATE_DOWN) {
|
get_performance_milliseconds(),
|
||||||
GFDM_GF_PICK_STATE_DOWN = true;
|
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;
|
payload.buttons |= 1 << GUITAR_PICK_DOWN;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
GFDM_GF_PICK_STATE_DOWN = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &analogs = get_analogs();
|
auto &analogs = get_analogs();
|
||||||
|
|||||||
@@ -595,6 +595,16 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
if (options[launcher::Options::GitaDoraWailHold].is_active()) {
|
if (options[launcher::Options::GitaDoraWailHold].is_active()) {
|
||||||
socd::TILT_HOLD_MS = options[launcher::Options::GitaDoraWailHold].value_uint32();
|
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()) {
|
if (options[launcher::Options::GitaDoraSubOverlaySize].is_active()) {
|
||||||
games::gitadora::SUBSCREEN_OVERLAY_SIZE = options[launcher::Options::GitaDoraSubOverlaySize].value_text();
|
games::gitadora::SUBSCREEN_OVERLAY_SIZE = options[launcher::Options::GitaDoraSubOverlaySize].value_text();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1020,6 +1020,25 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.game_name = "GitaDora",
|
.game_name = "GitaDora",
|
||||||
.category = "Game Options",
|
.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
|
// GitaDoraSubOverlaySize
|
||||||
.title = "GitaDora Subscreen Overlay Size",
|
.title = "GitaDora Subscreen Overlay Size",
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ namespace launcher {
|
|||||||
GitaDoraArenaSingleWindow,
|
GitaDoraArenaSingleWindow,
|
||||||
GitaDoraLefty,
|
GitaDoraLefty,
|
||||||
GitaDoraWailHold,
|
GitaDoraWailHold,
|
||||||
|
GitaDoraPickAlgo,
|
||||||
GitaDoraSubOverlaySize,
|
GitaDoraSubOverlaySize,
|
||||||
LoadJubeatModule,
|
LoadJubeatModule,
|
||||||
LoadReflecBeatModule,
|
LoadReflecBeatModule,
|
||||||
|
|||||||
+32
-14
@@ -522,24 +522,42 @@ static long __cdecl gfdm_unit_get_input_p(int device, size_t player) {
|
|||||||
if (games::gitadora::is_guitar()) {
|
if (games::gitadora::is_guitar()) {
|
||||||
auto offset = player * 11;
|
auto offset = player * 11;
|
||||||
|
|
||||||
// pick up
|
const auto pick_up = Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[3 + offset]));
|
||||||
if (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 (!GFDM_GF_PICK_STATE_UP[player]) {
|
if (!games::gitadora::PICK_ALGO.has_value()) {
|
||||||
GFDM_GF_PICK_STATE_UP[player] = true;
|
// legacy behavior (detect rising edges only, input for 1 frame)
|
||||||
ret |= 0x80 | 0x20;
|
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 (pick_down) {
|
||||||
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[4 + offset]))) {
|
if (!GFDM_GF_PICK_STATE_DOWN[player]) {
|
||||||
if (!GFDM_GF_PICK_STATE_DOWN[player]) {
|
GFDM_GF_PICK_STATE_DOWN[player] = true;
|
||||||
GFDM_GF_PICK_STATE_DOWN[player] = true;
|
ret |= 0x100 | 0x20;
|
||||||
ret |= 0x100 | 0x20;
|
}
|
||||||
|
} else {
|
||||||
|
GFDM_GF_PICK_STATE_DOWN[player] = false;
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
// button R
|
||||||
|
|||||||
@@ -15,18 +15,23 @@ namespace socd {
|
|||||||
|
|
||||||
SocdAlgorithm ALGORITHM = SocdAlgorithm::Neutral;
|
SocdAlgorithm ALGORITHM = SocdAlgorithm::Neutral;
|
||||||
|
|
||||||
static double last_rising_edge[2][2] = {};
|
static double last_rising_edge[4][2] = {};
|
||||||
static bool last_button_state[2][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
|
// 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) {
|
SocdResult socd_clean(
|
||||||
if (device >= 2) {
|
uint8_t device, bool ccw, bool cw, double time_now,
|
||||||
|
std::optional<SocdAlgorithm> algorithm_override) {
|
||||||
|
|
||||||
|
if (device >= 4) {
|
||||||
log_fatal("socd", "invalid device index in socd_clean: {}", device);
|
log_fatal("socd", "invalid device index in socd_clean: {}", device);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SOCD last input algorithm needs to keep track of rising edge times
|
SocdAlgorithm algo =
|
||||||
if (ALGORITHM != SocdAlgorithm::Neutral) {
|
algorithm_override.has_value() ? algorithm_override.value() : ALGORITHM;
|
||||||
// detect rising edges
|
|
||||||
|
// keep track of rising edge times
|
||||||
|
if (algo == SocdAlgorithm::PreferRecent || algo == SocdAlgorithm::PreferFirst) {
|
||||||
if (!last_button_state[device][SocdCCW] && ccw) {
|
if (!last_button_state[device][SocdCCW] && ccw) {
|
||||||
last_rising_edge[device][SocdCCW] = time_now;
|
last_rising_edge[device][SocdCCW] = time_now;
|
||||||
}
|
}
|
||||||
@@ -55,7 +60,7 @@ namespace socd {
|
|||||||
const auto cw_time = last_rising_edge[device][SocdCW];
|
const auto cw_time = last_rising_edge[device][SocdCW];
|
||||||
log_debug("socd", "ccw={}, cw ={}", ccw_time, cw_time);
|
log_debug("socd", "ccw={}, cw ={}", ccw_time, cw_time);
|
||||||
|
|
||||||
if (ALGORITHM == SocdAlgorithm::PreferRecent) {
|
if (algo == SocdAlgorithm::PreferRecent) {
|
||||||
// SOCD: prefer last input
|
// SOCD: prefer last input
|
||||||
if (ccw_time < cw_time) {
|
if (ccw_time < cw_time) {
|
||||||
// while CCW is being held, CW got pressed
|
// 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
|
// it's a tie; instead of none, we'll pick a direction
|
||||||
return SocdCW;
|
return SocdCW;
|
||||||
}
|
}
|
||||||
} else if (ALGORITHM == SocdAlgorithm::PreferFirst) {
|
} else if (algo == SocdAlgorithm::PreferFirst) {
|
||||||
// SOCD: keep first input
|
// SOCD: keep first input
|
||||||
if (ccw_time < cw_time) {
|
if (ccw_time < cw_time) {
|
||||||
// while CCW is being held, CW got pressed
|
// 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
|
// it's a tie; instead of none, we'll pick a direction
|
||||||
return SocdCW;
|
return SocdCW;
|
||||||
}
|
}
|
||||||
} else {
|
} else if (algo == SocdAlgorithm::Neutral) {
|
||||||
// SOCD: neutral when both are pressed
|
// SOCD: neutral when both are pressed
|
||||||
return SocdNone;
|
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);
|
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) {
|
if (up) {
|
||||||
most_recent_active[device][TiltUp] = time_now;
|
most_recent_active[device][TiltUp] = time_now;
|
||||||
@@ -125,7 +137,7 @@ namespace socd {
|
|||||||
result = TiltUp;
|
result = TiltUp;
|
||||||
} else if (socd_result == SocdCW) {
|
} else if (socd_result == SocdCW) {
|
||||||
result = TiltDown;
|
result = TiltDown;
|
||||||
} else {
|
} else if (socd_result == SocdBoth) {
|
||||||
result = TiltUp;
|
result = TiltUp;
|
||||||
}
|
}
|
||||||
} else if (is_up) {
|
} else if (is_up) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace socd {
|
namespace socd {
|
||||||
@@ -9,7 +10,8 @@ namespace socd {
|
|||||||
enum class SocdAlgorithm {
|
enum class SocdAlgorithm {
|
||||||
Neutral,
|
Neutral,
|
||||||
PreferRecent,
|
PreferRecent,
|
||||||
PreferFirst
|
PreferFirst,
|
||||||
|
None,
|
||||||
};
|
};
|
||||||
|
|
||||||
extern SocdAlgorithm ALGORITHM;
|
extern SocdAlgorithm ALGORITHM;
|
||||||
@@ -17,10 +19,16 @@ namespace socd {
|
|||||||
typedef enum _SocdResult {
|
typedef enum _SocdResult {
|
||||||
SocdCCW = 0,
|
SocdCCW = 0,
|
||||||
SocdCW = 1,
|
SocdCW = 1,
|
||||||
SocdNone = 2
|
SocdNone = 2,
|
||||||
|
SocdBoth = 3
|
||||||
} SocdResult;
|
} 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<SocdAlgorithm> algorithm = std::nullopt);
|
||||||
|
|
||||||
// for guitar wail (up/down only)
|
// for guitar wail (up/down only)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user