feat: auto-reconnect midi
Clang Lint CI / lint-no-video (push) Successful in 59s
Clang Build CI / run-no-video (push) Successful in 59s
Clang Build CI / run-video (push) Successful in 59s
Clang Build CI / build-release (push) Successful in 1m31s
Clang Lint CI / lint-video (push) Successful in 1m7s

This commit is contained in:
2026-05-14 15:28:38 +02:00
parent adc520bc8b
commit 28b87d316a
8 changed files with 100 additions and 48 deletions
+3 -1
View File
@@ -176,7 +176,7 @@ These are configurable in the [`forge_project.cfg`](#forge_projectcfg).
```txt
forge steel-dev
usage: forge [-h] [-v] [-p=PROJECT_PATH] [-c=CFG_FILE] [-hr] [-s=SCREEN] [-m=SCREEN] [-mo] [-w] [-t=TEMPO] [-d] [-ar / -nar] [-arc=CYCLES] [-vi=FILE] [-vs=SIZE] [-vr / -nvr] [-is=SIZE] [-ls / -nls] [-ss / -nss] [-tm] [-tf]
usage: forge [-h] [-v] [-p=PROJECT_PATH] [-c=CFG_FILE] [-hr] [-s=SCREEN] [-m=SCREEN] [-mo] [-w] [-t=TEMPO] [-d] [-ar / -nar] [-arc=CYCLES] [-vi=FILE] [-vs=SIZE] [-vr / -nvr] [-is=SIZE] [-ls / -nls] [-ss / -nss] [-mr / -nmr] [-tm] [-tf]
Fusion Of Real-time Generative Effects.
@@ -204,6 +204,8 @@ options:
-nls, --no-load-state do not load saved state
-ss, --save-state save state (default)
-nss, --no-save-state do not save state
-mr, --midi-reconnect auto-reconnect midi (default)
-nmr, --no-midi-reconnect do not auto-reconnect midi
-tm, --trace-midi print midi code and values
-tf, --trace-fps print fps status of subsystems
```
+8
View File
@@ -37,6 +37,7 @@ static void print_help(int status_code) {
"[-is=SIZE] "
"[-ls / -nls] "
"[-ss / -nss] "
"[-mr / -nmr] "
"[-tm] "
"[-tf] "
"\n\n"
@@ -72,6 +73,8 @@ static void print_help(int status_code) {
" -nls, --no-load-state do not load saved state\n"
" -ss, --save-state save state (default)\n"
" -nss, --no-save-state do not save state\n"
" -mr, --midi-reconnect auto-reconnect midi (default)\n"
" -nmr, --no-midi-reconnect do not auto-reconnect midi\n"
" -tm, --trace-midi print midi code and values\n"
" -tf, --trace-fps print fps status of subsystems\n");
exit(status_code);
@@ -137,6 +140,7 @@ void args_parse(Parameters *params, int argc, char **argv) {
params->internal_size = 720;
params->load_state = true;
params->save_state = true;
params->midi_reconnect = true;
params->trace_midi = false;
params->trace_fps = false;
@@ -225,6 +229,10 @@ void args_parse(Parameters *params, int argc, char **argv) {
params->save_state = true;
} else if (is_arg(arg, "-nss") || is_arg(arg, "--no-save-state")) {
params->save_state = false;
} else if (is_arg(arg, "-mr") || is_arg(arg, "--midi-reconnect")) {
params->midi_reconnect = true;
} else if (is_arg(arg, "-nmr") || is_arg(arg, "--no-midi-reconnect")) {
params->midi_reconnect = false;
} else if (is_arg(arg, "-tm") || is_arg(arg, "--trace-midi")) {
params->trace_midi = true;
} else if (is_arg(arg, "-tf") || is_arg(arg, "--trace-fps")) {
+34 -9
View File
@@ -181,12 +181,12 @@ static void key_callback(Window *window, int key,
log_info("[ESC] Closing...");
window_close(window);
} else if (event > 0) {
state_key_event(&context, project.state_config, event, &midi);
state_key_event(&context, project.state_config, event, midi);
}
}
static void midi_callback(unsigned char code, unsigned char value) {
state_midi_event(&context, project.state_config, &midi, code, value,
state_midi_event(&context, project.state_config, midi, code, value,
init_params.trace_midi);
}
@@ -214,6 +214,35 @@ static void start_midi_background_listen() {
pthread_detach(thread);
}
static void *background_reconnect_midi(__attribute__((unused)) void *args) {
log_info("background midi reconnect started");
while (!context.stop) {
sleep(1);
if (!midi.connected) {
midi_open(&midi, config_file_get_str(&project.config, "MIDI_HW", "hw"));
if (midi.connected) {
start_midi_background_listen();
}
}
}
pthread_exit(NULL);
}
static void init_midi() {
pthread_t thread;
midi_open(&midi, config_file_get_str(&project.config, "MIDI_HW", "hw"));
if (midi.connected) {
start_midi_background_listen();
}
if (init_params.midi_reconnect) {
pthread_create(&thread, NULL, background_reconnect_midi, NULL);
pthread_detach(thread);
}
}
static bool init(const Parameters *params) {
init_params = *params;
@@ -231,13 +260,7 @@ static bool init(const Parameters *params) {
start_video_captures();
#endif /* VIDEO_IN */
midi_open(&midi, config_file_get_str(&project.config, "MIDI_HW", "hw"));
if (midi.error) {
context.demo = true;
} else {
start_midi_background_listen();
}
init_midi();
start_state_background_write();
@@ -357,6 +380,8 @@ static void shutdown() {
free_video_captures();
#endif /* VIDEO_IN */
midi_close(&midi);
project_free(&project);
window_terminate();
+30 -8
View File
@@ -13,14 +13,17 @@ void midi_open(MidiDevice *device, const char *name) {
snd_rawmidi_open(&device->input, &device->output, name, SND_RAWMIDI_NONBLOCK);
device->error = device->input == NULL || device->output == NULL;
device->connected = device->input != NULL && device->output != NULL;
log_info("(%s) MIDI open", name);
if (device->connected) {
log_info("(%s) MIDI open", name);
} else {
log_warn("(%s) MIDI open failed", name);
}
}
void midi_write(const MidiDevice *device, unsigned char code,
unsigned char value) {
if (device->error) {
void midi_write(MidiDevice device, unsigned char code, unsigned char value) {
if (!device.connected) {
return;
}
@@ -30,7 +33,15 @@ void midi_write(const MidiDevice *device, unsigned char code,
buffer[1] = code;
buffer[2] = value;
snd_rawmidi_write(device->output, buffer, 3);
snd_rawmidi_write(device.output, buffer, 3);
}
void midi_close(MidiDevice *device) {
if (device->connected) {
snd_rawmidi_close(device->input);
snd_rawmidi_close(device->output);
device->connected = false;
}
}
void *midi_background_listen(void *args) {
@@ -39,17 +50,28 @@ void *midi_background_listen(void *args) {
Context *context = process_args->context;
int bytes_read;
snd_rawmidi_info_t *info;
unsigned char buffer[3];
log_info("(%s) background acquisition started", device->name);
while (!context->stop) {
snd_rawmidi_info_malloc(&info);
while (!context->stop && snd_rawmidi_info(device->output, info) == 0) {
bytes_read = snd_rawmidi_read(device->input, buffer, 3);
if (bytes_read == 3) {
process_args->event_callback(buffer[1], buffer[2]);
}
}
log_info("(%s) background acquisition stopped by main thread", device->name);
snd_rawmidi_info_free(info);
if (context->stop) {
log_info("(%s) background acquisition stopped by main thread",
device->name);
} else {
log_info("(%s) background acquisition stopped after error", device->name);
midi_close(device);
}
pthread_exit(NULL);
}
+2 -2
View File
@@ -4,8 +4,8 @@
#define MIDI_H
void midi_open(MidiDevice *device, const char *name);
void midi_write(const MidiDevice *device, unsigned char code,
unsigned char value);
void midi_write(MidiDevice device, unsigned char code, unsigned char value);
void *midi_background_listen(void *args);
void midi_close(MidiDevice *device);
#endif /* MIDI_H */
+19 -25
View File
@@ -13,7 +13,7 @@
#include "state.h"
#include "tempo.h"
static void safe_midi_write(const MidiDevice *midi, unsigned int code,
static void safe_midi_write(MidiDevice midi, unsigned int code,
unsigned char value) {
if (code != UNSET_MIDI_CODE) {
midi_write(midi, code, value);
@@ -21,7 +21,7 @@ static void safe_midi_write(const MidiDevice *midi, unsigned int code,
}
static void update_page(const Context *context, StateConfig state_config,
const MidiDevice *midi) {
MidiDevice midi) {
unsigned int page_item_min;
unsigned int page_item_max;
// SHOW PAGE
@@ -51,7 +51,7 @@ static void update_page(const Context *context, StateConfig state_config,
}
static void update_active(const Context *context, StateConfig state_config,
const MidiDevice *midi) {
MidiDevice midi, bool beat_active) {
unsigned int k;
for (unsigned int i = 0; i < state_config.midi_active_counts.length; i++) {
@@ -59,13 +59,13 @@ static void update_active(const Context *context, StateConfig state_config,
j++) {
k = state_config.midi_active_offsets.values[i] + j;
safe_midi_write(midi, state_config.midi_active_codes.values[k],
context->active[i] == j ? MIDI_MAX : 0);
context->active[i] == j && beat_active ? MIDI_MAX : 0);
}
}
}
static void update_values(const Context *context, StateConfig state_config,
const MidiDevice *midi) {
MidiDevice midi) {
unsigned int j;
unsigned int k;
unsigned int part;
@@ -407,8 +407,8 @@ void state_parse_config(StateConfig *state_config, const ConfigFile *config) {
}
void state_midi_event(Context *context, StateConfig state_config,
const MidiDevice *midi, unsigned char code,
unsigned char value, bool trace_midi) {
MidiDevice midi, unsigned char code, unsigned char value,
bool trace_midi) {
unsigned int i;
unsigned int j;
unsigned int k;
@@ -455,7 +455,7 @@ void state_midi_event(Context *context, StateConfig state_config,
if (value > 0) {
part = arr_uint_remap_index(state_config.midi_active_offsets, &i);
context->active[part] = i;
update_active(context, state_config, midi);
update_active(context, state_config, midi, true);
update_values(context, state_config, midi);
}
}
@@ -474,17 +474,17 @@ void state_midi_event(Context *context, StateConfig state_config,
} else if (value > 0) {
if (context->values[k][i % 3] > 0.5) {
context->values[k][i % 3] = 0;
midi_write(midi, code, 0);
safe_midi_write(midi, code, 0);
} else {
context->values[k][i % 3] = 1;
midi_write(midi, code, MIDI_MAX);
safe_midi_write(midi, code, MIDI_MAX);
}
}
}
if (code == state_config.tap_tempo_code) {
found = true;
midi_write(midi, code, value);
safe_midi_write(midi, code, value);
if (value > 0) {
tempo_tap(&context->tempo);
}
@@ -494,14 +494,14 @@ void state_midi_event(Context *context, StateConfig state_config,
if (trace_midi) {
log_trace("unknown midi: %d %d", code, value);
}
midi_write(midi, code, value);
safe_midi_write(midi, code, value);
} else if (trace_midi) {
log_trace("midi: %d %d", code, value);
}
}
void state_key_event(Context *context, StateConfig state_config,
unsigned int code, const MidiDevice *midi) {
unsigned int code, MidiDevice midi) {
unsigned int index;
if (code == state_config.hotkey_randomize) {
@@ -570,24 +570,18 @@ void *state_background_write(void *args) {
log_info("(state) background writing started");
if (!midi->error) {
update_page(context, state_config, midi);
update_active(context, state_config, midi);
update_values(context, state_config, midi);
}
last_active = false;
last_change = false;
while (!context->stop) {
beat_active = tempo_progress(&context->tempo, 1.0) < 0.5;
if (!midi->error && beat_active != last_active) {
safe_midi_write(midi, state_config.tap_tempo_code,
beat_active ? MIDI_MAX : 0);
if (midi->connected && beat_active != last_active) {
update_values(context, state_config, *midi);
update_page(context, state_config, *midi);
update_active(context, state_config, *midi, beat_active);
safe_midi_write(midi,
state_config.select_frag_codes.values[context->selected],
safe_midi_write(*midi, state_config.tap_tempo_code,
beat_active ? MIDI_MAX : 0);
}
@@ -599,7 +593,7 @@ void *state_background_write(void *args) {
if (context->auto_random && change && !last_change) {
randomize(context, state_config);
update_values(context, state_config, midi);
update_values(context, state_config, *midi);
}
last_change = change;
+2 -2
View File
@@ -6,11 +6,11 @@
void state_parse_config(StateConfig *state_config, const ConfigFile *config);
void state_midi_event(Context *context, StateConfig state_config,
const MidiDevice *midi, unsigned char code,
MidiDevice midi, unsigned char code,
unsigned char value, bool trace_midi);
void state_key_event(Context *context, StateConfig state_config,
unsigned int code, const MidiDevice *midi);
unsigned int code, MidiDevice midi);
void *state_background_write(void *args);
+2 -1
View File
@@ -51,6 +51,7 @@ typedef struct Parameters {
bool video_reconnect;
bool load_state;
bool save_state;
bool midi_reconnect;
bool trace_midi;
bool trace_fps;
} Parameters;
@@ -219,7 +220,7 @@ typedef struct VideoBackgroundReadArgs {
// midi.c
typedef struct MidiDevice {
bool error;
_Atomic bool connected;
char name[STR_LEN];
snd_rawmidi_t *input;
snd_rawmidi_t *output;