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