diff --git a/config.def.h b/config.def.h index f936b2a5fd..f029fcda3e 100644 --- a/config.def.h +++ b/config.def.h @@ -324,8 +324,9 @@ static const bool stdin_cmd_enable = false; // How far an axis must be tilted to result in a button press static const float axis_threshold = 0.5; -// To figure out which joypad buttons to use, check jstest or similar. -// SDL sometimes reverses the axes for some odd reason, but hey. :D +// Describes speed of which turbo-enabled buttons toggle. +static const unsigned turbo_period = 6; +static const unsigned turbo_duty_cycle = 3; // Player 1 static const struct retro_keybind retro_keybinds_1[] = { @@ -347,6 +348,7 @@ static const struct retro_keybind retro_keybinds_1[] = { { true, RETRO_DEVICE_ID_JOYPAD_L3, RETROK_UNKNOWN, NO_BTN, AXIS_NONE }, { true, RETRO_DEVICE_ID_JOYPAD_R3, RETROK_UNKNOWN, NO_BTN, AXIS_NONE }, + { true, RARCH_TURBO_ENABLE, RETROK_UNKNOWN, NO_BTN, AXIS_NONE }, { true, RARCH_ANALOG_LEFT_X_PLUS, RETROK_UNKNOWN, NO_BTN, AXIS_NONE }, { true, RARCH_ANALOG_LEFT_X_MINUS, RETROK_UNKNOWN, NO_BTN, AXIS_NONE }, { true, RARCH_ANALOG_LEFT_Y_PLUS, RETROK_UNKNOWN, NO_BTN, AXIS_NONE }, @@ -402,6 +404,7 @@ static const struct retro_keybind retro_keybinds_rest[] = { { true, RETRO_DEVICE_ID_JOYPAD_L3, RETROK_UNKNOWN, NO_BTN, AXIS_NONE }, { true, RETRO_DEVICE_ID_JOYPAD_R3, RETROK_UNKNOWN, NO_BTN, AXIS_NONE }, + { true, RARCH_TURBO_ENABLE, RETROK_UNKNOWN, NO_BTN, AXIS_NONE }, { true, RARCH_ANALOG_LEFT_X_PLUS, RETROK_UNKNOWN, NO_BTN, AXIS_NONE }, { true, RARCH_ANALOG_LEFT_X_MINUS, RETROK_UNKNOWN, NO_BTN, AXIS_NONE }, { true, RARCH_ANALOG_LEFT_Y_PLUS, RETROK_UNKNOWN, NO_BTN, AXIS_NONE }, diff --git a/driver.h b/driver.h index 024cecaef5..5e711f939f 100644 --- a/driver.h +++ b/driver.h @@ -40,12 +40,16 @@ // Analog binds use RETRO_DEVICE_ANALOG, but we follow the same scheme internally // in RetroArch for simplicity, // so they are mapped into [16, 23]. -#define RARCH_FIRST_ANALOG_BIND 16 -#define RARCH_FIRST_META_KEY RARCH_ANALOG_BIND_LIST_END +#define RARCH_FIRST_CUSTOM_BIND 16 +#define RARCH_FIRST_META_KEY RARCH_CUSTOM_BIND_LIST_END enum // RetroArch specific bind IDs. { + // Custom binds that extend the scope of RETRO_DEVICE_JOYPAD for RetroArch specifically. + // Turbo + RARCH_TURBO_ENABLE = RARCH_FIRST_CUSTOM_BIND, + // Analogs (RETRO_DEVICE_ANALOG) - RARCH_ANALOG_LEFT_X_PLUS = RARCH_FIRST_ANALOG_BIND, + RARCH_ANALOG_LEFT_X_PLUS, RARCH_ANALOG_LEFT_X_MINUS, RARCH_ANALOG_LEFT_Y_PLUS, RARCH_ANALOG_LEFT_Y_MINUS, @@ -54,10 +58,10 @@ enum // RetroArch specific bind IDs. RARCH_ANALOG_RIGHT_Y_PLUS, RARCH_ANALOG_RIGHT_Y_MINUS, - RARCH_ANALOG_BIND_LIST_END, + RARCH_CUSTOM_BIND_LIST_END, - // Command binds. - RARCH_FAST_FORWARD_KEY = RARCH_ANALOG_BIND_LIST_END, + // Command binds. Not related to game input, only usable for port 0. + RARCH_FAST_FORWARD_KEY = RARCH_FIRST_META_KEY, RARCH_FAST_FORWARD_HOLD_KEY, RARCH_LOAD_STATE_KEY, RARCH_SAVE_STATE_KEY, diff --git a/dynamic.c b/dynamic.c index 0410cedebf..4b1f2156ea 100644 --- a/dynamic.c +++ b/dynamic.c @@ -365,7 +365,7 @@ static bool environment_cb(unsigned cmd, void *data) if (desc->device != RETRO_DEVICE_JOYPAD) // Ignore all others for now. continue; - if (desc->id >= RARCH_FIRST_ANALOG_BIND) + if (desc->id >= RARCH_FIRST_CUSTOM_BIND) continue; g_extern.system.input_desc_btn[desc->port][desc->id] = desc->description; @@ -381,7 +381,7 @@ static bool environment_cb(unsigned cmd, void *data) RARCH_LOG("Environ SET_INPUT_DESCRIPTORS:\n"); for (unsigned p = 0; p < MAX_PLAYERS; p++) { - for (unsigned id = 0; id < RARCH_FIRST_ANALOG_BIND; id++) + for (unsigned id = 0; id < RARCH_FIRST_CUSTOM_BIND; id++) { const char *desc = g_extern.system.input_desc_btn[p][id]; if (desc) diff --git a/general.h b/general.h index 966fd64364..63db4e97c8 100644 --- a/general.h +++ b/general.h @@ -156,6 +156,9 @@ struct settings unsigned device[MAX_PLAYERS]; #endif bool netplay_client_swap_input; + + unsigned turbo_period; + unsigned turbo_duty_cycle; } input; char libretro[PATH_MAX]; @@ -338,7 +341,7 @@ struct global bool force_nonblock; - const char *input_desc_btn[MAX_PLAYERS][RARCH_FIRST_ANALOG_BIND]; + const char *input_desc_btn[MAX_PLAYERS][RARCH_FIRST_CUSTOM_BIND]; } system; struct @@ -422,6 +425,11 @@ struct global bool is_oneshot; bool is_slowmotion; + // Turbo support + bool turbo_frame_enable[MAX_PLAYERS]; + uint16_t turbo_enable[MAX_PLAYERS]; + unsigned turbo_count; + // Autosave support. autosave_t *autosave[2]; diff --git a/retroarch.c b/retroarch.c index 45f094dced..f62c425e54 100644 --- a/retroarch.c +++ b/retroarch.c @@ -474,6 +474,22 @@ static void input_poll(void) input_poll_func(); } +// Turbo scheme: If turbo button is held, all buttons pressed except for D-pad will go into +// a turbo mode. Until the button is released again, the input state will be modulated by a periodic pulse defined +// by the configured duty cycle. +static bool input_apply_turbo(unsigned port, unsigned id, bool res) +{ + if (res && g_extern.turbo_frame_enable[port]) + g_extern.turbo_enable[port] |= (1 << id); + else if (!res) + g_extern.turbo_enable[port] &= ~(1 << id); + + if (g_extern.turbo_enable[port] & (1 << id)) + return res & ((g_extern.turbo_count % g_settings.input.turbo_period) < g_settings.input.turbo_duty_cycle); + else + return res; +} + static int16_t input_state(unsigned port, unsigned device, unsigned index, unsigned id) { device &= RETRO_DEVICE_MASK; @@ -504,6 +520,10 @@ static int16_t input_state(unsigned port, unsigned device, unsigned index, unsig if (id < RARCH_FIRST_META_KEY || device == RETRO_DEVICE_KEYBOARD) res = input_input_state_func(binds, port, device, index, id); + // Don't allow turbo for D-pad. + if (device == RETRO_DEVICE_JOYPAD && (id < RETRO_DEVICE_ID_JOYPAD_UP || id > RETRO_DEVICE_ID_JOYPAD_RIGHT)) + res = input_apply_turbo(port, id, res); + #ifdef HAVE_BSV_MOVIE if (g_extern.bsv.movie && !g_extern.bsv.movie_playback) bsv_movie_set_input(g_extern.bsv.movie, res); @@ -2261,6 +2281,26 @@ static void check_reset(void) old_state = new_state; } +static void check_turbo(void) +{ + g_extern.turbo_count++; + + static const struct retro_keybind *binds[MAX_PLAYERS] = { + g_settings.input.binds[0], + g_settings.input.binds[1], + g_settings.input.binds[2], + g_settings.input.binds[3], + g_settings.input.binds[4], + g_settings.input.binds[5], + g_settings.input.binds[6], + g_settings.input.binds[7], + }; + + for (unsigned i = 0; i < MAX_PLAYERS; i++) + g_extern.turbo_frame_enable[i] = + input_input_state_func(binds, i, RETRO_DEVICE_JOYPAD, 0, RARCH_TURBO_ENABLE); +} + #ifdef HAVE_XML static void check_shader_dir(void) { @@ -2416,6 +2456,8 @@ static void do_state_checks(void) check_mute(); #endif + check_turbo(); + #ifdef HAVE_NETPLAY if (!g_extern.netplay) { diff --git a/retroarch.cfg b/retroarch.cfg index 4f799a87c0..f2f4c9348a 100644 --- a/retroarch.cfg +++ b/retroarch.cfg @@ -248,7 +248,7 @@ # input_player1_l3_btn = # input_player1_r3_btn = -# Axis for SNES DPAD. +# Axis for RetroArch D-Pad. # Needs to be either '+' or '-' in the first character signaling either positive or negative direction of the axis, then the axis number. # Do note that every other input option has the corresponding _btn and _axis binds as well; they are omitted here for clarity. # input_player1_left_axis = @@ -256,7 +256,18 @@ # input_player1_up_axis = # input_player1_down_axis = +# Holding the turbo while pressing another button will let the button enter a turbo mode +# where the button state is modulated with a periodic signal. +# The modulation stops when the button itself (not turbo button) is released. +# input_player1_turbo = + +# Describes the period and how long of that period a turbo-enabled button should behave. +# Numbers are described in frames. +# input_turbo_period = 6 +# input_turbo_duty_cycle = 3 + # This goes all the way to player 8 (*_player2_*, *_player3_*, etc), but omitted for clarity. +# All input binds have corresponding binds for keyboard (none), joykeys (_btn) and joyaxes (_axis) as well. # Toggles fullscreen. # input_toggle_fullscreen = f diff --git a/settings.c b/settings.c index 22b36e2708..79540c8283 100644 --- a/settings.c +++ b/settings.c @@ -221,6 +221,8 @@ void config_set_defaults(void) g_settings.input.axis_threshold = axis_threshold; g_settings.input.netplay_client_swap_input = netplay_client_swap_input; + g_settings.input.turbo_period = turbo_period; + g_settings.input.turbo_duty_cycle = turbo_duty_cycle; for (int i = 0; i < MAX_PLAYERS; i++) g_settings.input.joypad_map[i] = i; @@ -494,6 +496,9 @@ bool config_load_file(const char *path) CONFIG_GET_INT(network_cmd_port, "network_cmd_port"); CONFIG_GET_BOOL(stdin_cmd_enable, "stdin_cmd_enable"); + CONFIG_GET_INT(input.turbo_period, "input_turbo_period"); + CONFIG_GET_INT(input.turbo_duty_cycle, "input_duty_cycle"); + if (config_get_string(conf, "environment_variables", &g_extern.system.environment)) { @@ -567,6 +572,7 @@ struct bind_map DECLARE_BIND(player##P##_r2, RETRO_DEVICE_ID_JOYPAD_R2), \ DECLARE_BIND(player##P##_l3, RETRO_DEVICE_ID_JOYPAD_L3), \ DECLARE_BIND(player##P##_r3, RETRO_DEVICE_ID_JOYPAD_R3), \ + DECLARE_BIND(player##P##_turbo, RARCH_TURBO_ENABLE), \ DECLARE_BIND(player##P##_l_x_plus, RARCH_ANALOG_LEFT_X_PLUS), \ DECLARE_BIND(player##P##_l_x_minus, RARCH_ANALOG_LEFT_X_MINUS), \ DECLARE_BIND(player##P##_l_y_plus, RARCH_ANALOG_LEFT_Y_PLUS), \