From d094a6c895adbafd8913767003527eeda5c72095 Mon Sep 17 00:00:00 2001 From: klemek Date: Fri, 14 Nov 2025 11:49:02 +0100 Subject: [PATCH] feat: hotkeys in config --- DEVELOPMENT.md | 2 +- README.md | 2 + default/forge_project.cfg | 59 ++++++++++++++++- src/config_file.c | 11 ++++ src/config_file.h | 2 + src/state.c | 129 +++++++++++++++++++++++++++++--------- src/tempo.c | 5 +- src/tempo.h | 2 +- src/types.h | 12 ++++ 9 files changed, 189 insertions(+), 35 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index fed0f33..fd1ad85 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -122,7 +122,7 @@ make -f Makefile.dev release-arch - [x] Arrows (up-down: bpm / left-right: cycle) - [x] Save states (numkey: load / shift + numkey: save) - [ ] (clean) static functions at top of files - - [ ] Configurable key codes + - [x] Configurable key codes - [ ] Key codes as inputs - [ ] Mouse position and scroll as inputs - [ ] Joystick as input diff --git a/README.md b/README.md index 6b18920..23fb8c9 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,8 @@ When running, the following hotkeys are available: | 0-9 | Load state 0 to 9 | | Shift + 0-9 | Save state 0 to 9 | +These are configurable in the [`forge_project.cfg`](#forge_projectcfg). + ### CLI arguments ```txt diff --git a/default/forge_project.cfg b/default/forge_project.cfg index edb7a51..9c24bdf 100644 --- a/default/forge_project.cfg +++ b/default/forge_project.cfg @@ -269,4 +269,61 @@ MIDI_3_2_Z= # OTHER # ===== -SAVE_FILE_PREFIX=forge_default_save \ No newline at end of file +# === SAVE FILES +# When loading/saving from last run or using save states +SAVE_FILE_PREFIX=forge_default_save + +# === HOTKEYS +# You can change the default keycodes used on runtime +# Modifiers are encoded like this: +# 1000 -> shift +# 10000 -> control +# 100000 -> alt +# This means 110082 is control+alt+R + +# R on (qwerty) +HOTKEY_RANDOMIZE=82 +# SHIFT+R (qwerty) +HOTKEY_RESET=1082 +# D (qwerty) +HOTKEY_DEMO=68 +# A (qwerty) +HOTKEY_AUTORAND=65 +# Left arrow +HOTKEY_AUTORAND_DOWN=263 +# Right arrow +HOTKEY_AUTORAND_UP=262 +# Down arrow +HOTKEY_TEMPO_DOWN=264 +# Up arrow +HOTKEY_TEMPO_UP=265 + +# Number of load states keys +HOTKEY_LOAD_COUNT=10 + +# 1 to 9 then 0 keys +HOTKEY_LOAD_1=49 +HOTKEY_LOAD_2=50 +HOTKEY_LOAD_3=51 +HOTKEY_LOAD_4=52 +HOTKEY_LOAD_5=53 +HOTKEY_LOAD_6=54 +HOTKEY_LOAD_7=55 +HOTKEY_LOAD_8=56 +HOTKEY_LOAD_9=57 +HOTKEY_LOAD_10=48 + +# Number of save states keys +HOTKEY_SAVE_COUNT=10 + +# 1 to 9 then 0 keys with shift +HOTKEY_SAVE_1=1049 +HOTKEY_SAVE_2=1050 +HOTKEY_SAVE_3=1051 +HOTKEY_SAVE_4=1052 +HOTKEY_SAVE_5=1053 +HOTKEY_SAVE_6=1054 +HOTKEY_SAVE_7=1055 +HOTKEY_SAVE_8=1056 +HOTKEY_SAVE_9=1057 +HOTKEY_SAVE_10=1048 \ No newline at end of file diff --git a/src/config_file.c b/src/config_file.c index 749cdfe..8b64166 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -91,6 +91,17 @@ void config_file_read(ConfigFile *config, const char *path) { file_free(&file); } +bool config_file_has(const ConfigFile *config, const char *key) { + ConfigFileItem c_key; + const ConfigFileItem *item; + + strlcpy(c_key.key, key, STR_LEN); + + item = (const ConfigFileItem *)hashmap_get(config->map, &c_key); + + return item != NULL && strnlen(item->value, STR_LEN) > 0; +} + const char *config_file_get_str(const ConfigFile *config, const char *key, const char *default_value) { ConfigFileItem c_key; diff --git a/src/config_file.h b/src/config_file.h index d4ad7a1..f1627f9 100644 --- a/src/config_file.h +++ b/src/config_file.h @@ -5,6 +5,8 @@ void config_file_read(ConfigFile *config, const char *path); +bool config_file_has(const ConfigFile *config, const char *key); + const char *config_file_get_str(const ConfigFile *config, const char *key, const char *default_value); diff --git a/src/state.c b/src/state.c index eaef068..1a93eb5 100644 --- a/src/state.c +++ b/src/state.c @@ -348,6 +348,68 @@ void state_parse_config(StateConfig *state_config, const ConfigFile *config) { strlcpy(state_config->save_file_prefix, config_file_get_str(config, "SAVE_FILE_PREFIX", "forge_save"), STR_LEN); + + state_config->hotkey_randomize = + config_file_get_int(config, "HOTKEY_RANDOMIZE", 82); + state_config->hotkey_reset = + config_file_get_int(config, "HOTKEY_RESET", 1082); + state_config->hotkey_demo = config_file_get_int(config, "HOTKEY_DEMO", 68); + state_config->hotkey_autorand = + config_file_get_int(config, "HOTKEY_AUTORAND", 65); + state_config->hotkey_autorand_down = + config_file_get_int(config, "HOTKEY_AUTORAND_DOWN", 263); + state_config->hotkey_autorand_up = + config_file_get_int(config, "HOTKEY_AUTORAND_UP", 262); + state_config->hotkey_tempo_down = + config_file_get_int(config, "HOTKEY_TEMPO_DOWN", 264); + state_config->hotkey_tempo_up = + config_file_get_int(config, "HOTKEY_TEMPO_UP", 265); + + if (config_file_has(config, "HOTKEY_LOAD_COUNT")) { + state_config->hotkey_load.length = + config_file_get_int(config, "HOTKEY_LOAD_COUNT", 0); + + for (unsigned int i = 0; i < state_config->hotkey_load.length; i++) { + snprintf(name, STR_LEN, "HOTKEY_LOAD_%d", i + 1); + state_config->hotkey_load.values[i] = + config_file_get_int(config, name, 0); + } + } else { + state_config->hotkey_load.length = 10; + state_config->hotkey_load.values[0] = 49; + state_config->hotkey_load.values[1] = 50; + state_config->hotkey_load.values[2] = 51; + state_config->hotkey_load.values[3] = 52; + state_config->hotkey_load.values[4] = 53; + state_config->hotkey_load.values[5] = 54; + state_config->hotkey_load.values[6] = 55; + state_config->hotkey_load.values[7] = 56; + state_config->hotkey_load.values[8] = 57; + state_config->hotkey_load.values[9] = 48; + } + + if (config_file_has(config, "HOTKEY_SAVE_COUNT")) { + state_config->hotkey_save.length = + config_file_get_int(config, "HOTKEY_SAVE_COUNT", 0); + + for (unsigned int i = 0; i < state_config->hotkey_save.length; i++) { + snprintf(name, STR_LEN, "HOTKEY_SAVE_%d", i + 1); + state_config->hotkey_save.values[i] = + config_file_get_int(config, name, 0); + } + } else { + state_config->hotkey_save.length = 10; + state_config->hotkey_save.values[0] = 1049; + state_config->hotkey_save.values[1] = 1050; + state_config->hotkey_save.values[2] = 1051; + state_config->hotkey_save.values[3] = 1052; + state_config->hotkey_save.values[4] = 1053; + state_config->hotkey_save.values[5] = 1054; + state_config->hotkey_save.values[6] = 1055; + state_config->hotkey_save.values[7] = 1056; + state_config->hotkey_save.values[8] = 1057; + state_config->hotkey_save.values[9] = 1048; + } } void state_midi_event(SharedContext *context, const StateConfig *state_config, @@ -443,51 +505,57 @@ void state_midi_event(SharedContext *context, const StateConfig *state_config, void state_key_event(SharedContext *context, const StateConfig *state_config, unsigned int code, const MidiDevice *midi) { - if (code == 82) { - // R: randomize - log_info("[R] Randomized"); + unsigned int index; + + if (code == state_config->hotkey_randomize) { + log_info("[%d] Randomized", code); randomize(context, state_config); update_values(context, state_config, midi); - } else if (code == 1082) { - log_info("[SHIFT+R] Reset"); + } else if (code == state_config->hotkey_reset) { + log_info("[%d] Reset", code); reset(context); update_values(context, state_config, midi); - } else if (code == 68) { - // D: demo on/off - log_info((context->demo ? "[D] Demo OFF" : "[D] Demo ON")); + } else if (code == state_config->hotkey_demo) { + log_info((context->demo ? "[%d] Demo OFF" : "[%d] Demo ON"), code); context->demo = !context->demo; - } else if (code == 65) { - // A: auto random on/off + } else if (code == state_config->hotkey_autorand) { log_info( - (context->auto_random ? "[A] Auto Random OFF" : "[A] Auto Random ON")); + (context->auto_random ? "[%d] Auto Random OFF" : "[%d] Auto Random ON"), + code); context->auto_random = !context->auto_random; - } else if (code == 263) { - // LEFT ARROW + } else if (code == state_config->hotkey_autorand_down) { if (context->auto_random_cycle > 1) { context->auto_random_cycle -= 1; } - log_info("[LEFT] Auto Random Cycle: %d", context->auto_random_cycle); - } else if (code == 262) { - // RIGHT ARROW + log_info("[%d] Auto Random Cycle: %d", code, context->auto_random_cycle); + } else if (code == state_config->hotkey_autorand_up) { context->auto_random_cycle += 1; - log_info("[RIGHT] Auto Random Cycle: %d", context->auto_random_cycle); - } else if (code == 265) { - // UP ARROW + log_info("[%d] Auto Random Cycle: %d", code, context->auto_random_cycle); + } else if (code == state_config->hotkey_tempo_up) { tempo_set(&context->tempo, context->tempo.tempo + 1); - log_info("[UP] Tempo: %f", context->tempo); - } else if (code == 264) { - // DOWN ARROW + log_info("[%d] Tempo: %f", code, context->tempo); + } else if (code == state_config->hotkey_tempo_down) { if (context->tempo.tempo > 0) { tempo_set(&context->tempo, context->tempo.tempo - 1); } - log_info("[DOWN] Tempo: %f", context->tempo); - } else if (code >= 48 && code <= 57) { - log_info("[%d] Loading state %d", code - 48, code - 48); - load_from_index_file(context, state_config, code - 48); - } else if (code >= 1048 && code <= 1057) { - log_info("[%d] Saving state %d", code - 1048, code - 1048); - save_to_index_file(context, state_config, code - 1048); + log_info("[%d] Tempo: %f", code, context->tempo); } else { + index = arr_uint_index_of(state_config->hotkey_load, code); + + if (index != ARRAY_NOT_FOUND) { + log_info("[%d] Loading state %d", code, index + 1); + load_from_index_file(context, state_config, index + 1); + return; + } + + index = arr_uint_index_of(state_config->hotkey_save, code); + + if (index != ARRAY_NOT_FOUND) { + log_info("[%d] Saving state %d", code, index + 1); + save_to_index_file(context, state_config, index + 1); + return; + } + log_info("[%d] No hotkey defined", code); } } @@ -550,8 +618,7 @@ bool state_background_write(SharedContext *context, void state_init(SharedContext *context, const StateConfig *state_config, bool demo, bool auto_random, unsigned int auto_random_cycles, unsigned int base_tempo, bool load_state) { - tempo_init(&context->tempo); - tempo_set(&context->tempo, base_tempo); + tempo_init(&context->tempo, base_tempo); context->demo = demo; context->auto_random = auto_random; context->auto_random_cycle = auto_random_cycles; diff --git a/src/tempo.c b/src/tempo.c index fb15833..675d34d 100644 --- a/src/tempo.c +++ b/src/tempo.c @@ -25,12 +25,15 @@ static void reset_tap_chain(Tempo *tempo, long t) { memset(tempo->tap_durations, 0, sizeof(tempo->tap_durations)); } -void tempo_init(Tempo *tempo) { +void tempo_init(Tempo *tempo, float value) { long t; t = now(); reset_tap_chain(tempo, t); + + tempo->tempo = value; + tempo->beat_length = 60000.0 / value; } static bool is_chain_active(const Tempo tempo, long t) { diff --git a/src/tempo.h b/src/tempo.h index 932cbc3..bc123b4 100644 --- a/src/tempo.h +++ b/src/tempo.h @@ -3,7 +3,7 @@ #ifndef TEMPO_H #define TEMPO_H -void tempo_init(Tempo *tempo); +void tempo_init(Tempo *tempo, float value); void tempo_tap(Tempo *tempo); diff --git a/src/types.h b/src/types.h index 1db22d3..99ed7f3 100644 --- a/src/types.h +++ b/src/types.h @@ -214,6 +214,18 @@ typedef struct StateConfig { unsigned int tap_tempo_code; char save_file_prefix[STR_LEN]; + + unsigned int hotkey_randomize; + unsigned int hotkey_reset; + unsigned int hotkey_demo; + unsigned int hotkey_autorand; + unsigned int hotkey_autorand_up; + unsigned int hotkey_autorand_down; + unsigned int hotkey_tempo_up; + unsigned int hotkey_tempo_down; + + UintArray hotkey_load; + UintArray hotkey_save; } StateConfig; // timer.c