Files
forge-steel/src/state.c
T

586 lines
19 KiB
C

#include <log.h>
#include <stdio.h>
#include "types.h"
#include "arr.h"
#include "config.h"
#include "config_file.h"
#include "file.h"
#include "midi.h"
#include "rand.h"
#include "state.h"
#include "tempo.h"
static void safe_midi_write(const MidiDevice *midi, unsigned int code,
unsigned char value) {
if (code != UNSET_MIDI_CODE) {
midi_write(midi, code, value);
}
}
static void update_page(const SharedContext *context,
const StateConfig *state_config,
const MidiDevice *midi) {
unsigned int page_item_min;
unsigned int page_item_max;
// SHOW PAGE
for (unsigned int i = 0; i < state_config->select_page_codes.length; i++) {
safe_midi_write(midi, state_config->select_page_codes.values[i],
i == context->page ? MIDI_MAX : 0);
}
// SHOW PAGE ITEM
page_item_min = state_config->select_item_codes.length * context->page;
page_item_max = page_item_min + state_config->select_item_codes.length;
if (context->state.values[context->selected] >= page_item_min &&
context->state.values[context->selected] < page_item_max) {
for (unsigned int i = 0; i < state_config->select_item_codes.length; i++) {
safe_midi_write(midi, state_config->select_item_codes.values[i],
i == context->state.values[context->selected] -
page_item_min
? MIDI_MAX
: 0);
}
} else {
for (unsigned int i = 0; i < state_config->select_item_codes.length; i++) {
safe_midi_write(midi, state_config->select_item_codes.values[i], 0);
}
}
}
static void update_active(const SharedContext *context,
const StateConfig *state_config,
const MidiDevice *midi) {
unsigned int k;
for (unsigned int i = 0; i < state_config->midi_active_counts.length; i++) {
for (unsigned int j = 0; j < state_config->midi_active_counts.values[i];
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);
}
}
}
static void update_values(const SharedContext *context,
const StateConfig *state_config,
const MidiDevice *midi) {
unsigned int j;
unsigned int k;
unsigned int part;
for (unsigned int i = 0; i < state_config->midi_codes.length; i++) {
j = i / 3;
part = arr_uint_remap_index(state_config->midi_offsets, &j);
k = state_config->values_offsets.values[part] +
context->active[part] * state_config->midi_counts.values[part] + j;
safe_midi_write(midi, state_config->midi_codes.values[i],
context->values[k][i % 3] * MIDI_MAX);
}
}
static void reset(SharedContext *context) {
memset(context->values, 0, sizeof(context->values));
memset(context->state.values, 0, sizeof(context->state.values));
}
static void randomize(SharedContext *context, const StateConfig *state_config) {
unsigned int j;
unsigned int l;
unsigned int part;
for (unsigned int i = 0; i < state_config->midi_codes.length; i++) {
j = i / 3;
part = arr_uint_remap_index(state_config->midi_offsets, &j);
for (unsigned int k = 0; k < state_config->midi_active_counts.values[part];
k++) {
l = state_config->values_offsets.values[part] +
k * state_config->midi_counts.values[part] + j;
if (arr_uint_index_of(state_config->fader_codes,
state_config->midi_codes.values[i]) !=
ARRAY_NOT_FOUND) {
context->values[l][i % 3] = (float)rand_uint(MIDI_MAX + 1) / MIDI_MAX;
} else {
context->values[l][i % 3] = rand_uint(2) == 1 ? 1 : 0;
}
}
}
for (unsigned int i = 0; i < context->state.length; i++) {
context->state.values[i] = rand_uint(state_config->state_max);
}
}
static void load_from_file(SharedContext *context,
const StateConfig *state_config, char *state_file) {
ConfigFile saved_state;
char key[STR_LEN];
config_file_read(&saved_state, state_file);
if (saved_state.error) {
return;
}
tempo_set(&context->tempo,
config_file_get_int(&saved_state, "tempo", context->tempo.tempo));
context->page = config_file_get_int(&saved_state, "page", 0);
context->selected = config_file_get_int(&saved_state, "selected", 0);
for (unsigned int i = 0; i < context->state.length; i++) {
snprintf(key, STR_LEN, "seed_%d", i);
context->seeds[i] =
config_file_get_int(&saved_state, key, context->seeds[i]);
snprintf(key, STR_LEN, "state_%d", i);
context->state.values[i] = config_file_get_int(&saved_state, key, 0);
}
for (unsigned int i = 0; i < state_config->midi_active_counts.length; i++) {
snprintf(key, STR_LEN, "active_%d", i);
context->active[i] = config_file_get_int(&saved_state, key, 0);
}
for (unsigned int i = 0; i < state_config->value_count; i++) {
snprintf(key, STR_LEN, "value_%d_x", i);
context->values[i][0] =
(float)config_file_get_int(&saved_state, key, 0) / MIDI_MAX;
snprintf(key, STR_LEN, "value_%d_y", i);
context->values[i][1] =
(float)config_file_get_int(&saved_state, key, 0) / MIDI_MAX;
snprintf(key, STR_LEN, "value_%d_z", i);
context->values[i][2] =
(float)config_file_get_int(&saved_state, key, 0) / MIDI_MAX;
}
config_file_free(&saved_state);
}
static void load_from_default_file(SharedContext *context,
const StateConfig *state_config) {
char state_file[STR_LEN];
snprintf(state_file, STR_LEN, "%s.txt", state_config->save_file_prefix);
load_from_file(context, state_config, state_file);
}
static void load_from_index_file(SharedContext *context,
const StateConfig *state_config,
unsigned int index) {
char state_file[STR_LEN];
snprintf(state_file, STR_LEN, "%s.%d.txt", state_config->save_file_prefix,
index);
load_from_file(context, state_config, state_file);
}
static void save_to_file(const SharedContext *context,
const StateConfig *state_config,
const char *state_file) {
StringArray lines;
log_info("Saving state to '%s'...", state_file);
lines.length = 0;
snprintf(lines.values[lines.length++], STR_LEN, "tempo=%d",
(unsigned int)context->tempo.tempo);
snprintf(lines.values[lines.length++], STR_LEN, "page=%d", context->page);
snprintf(lines.values[lines.length++], STR_LEN, "selected=%d",
context->selected);
for (unsigned int i = 0; i < context->state.length; i++) {
snprintf(lines.values[lines.length++], STR_LEN, "seed_%d=%d", i,
context->seeds[i]);
snprintf(lines.values[lines.length++], STR_LEN, "state_%d=%d", i,
context->state.values[i]);
}
for (unsigned int i = 0; i < state_config->midi_active_counts.length; i++) {
snprintf(lines.values[lines.length++], STR_LEN, "active_%d=%d", i,
context->active[i]);
}
for (unsigned int i = 0; i < state_config->value_count; i++) {
snprintf(lines.values[lines.length++], STR_LEN, "value_%d_x=%d", i,
(unsigned int)(context->values[i][0] * MIDI_MAX));
snprintf(lines.values[lines.length++], STR_LEN, "value_%d_y=%d", i,
(unsigned int)(context->values[i][1] * MIDI_MAX));
snprintf(lines.values[lines.length++], STR_LEN, "value_%d_z=%d", i,
(unsigned int)(context->values[i][2] * MIDI_MAX));
}
file_write(state_file, &lines);
}
static void save_to_default_file(const SharedContext *context,
const StateConfig *state_config) {
char state_file[STR_LEN];
snprintf(state_file, STR_LEN, "%s.txt", state_config->save_file_prefix);
save_to_file(context, state_config, state_file);
}
static void save_to_index_file(const SharedContext *context,
const StateConfig *state_config,
unsigned int index) {
char state_file[STR_LEN];
snprintf(state_file, STR_LEN, "%s.%d.txt", state_config->save_file_prefix,
index);
save_to_file(context, state_config, state_file);
}
void state_parse_config(StateConfig *state_config, const ConfigFile *config) {
unsigned int offset;
unsigned int count;
char name[STR_LEN];
state_config->select_page_codes.length =
config_file_get_int(config, "SELECT_PAGE_COUNT", 0);
for (unsigned int i = 0; i < state_config->select_page_codes.length; i++) {
snprintf(name, STR_LEN, "SELECT_PAGE_%d", i + 1);
state_config->select_page_codes.values[i] =
config_file_get_int(config, name, UNSET_MIDI_CODE);
}
state_config->select_item_codes.length =
config_file_get_int(config, "SELECT_ITEM_COUNT", 0);
for (unsigned int i = 0; i < state_config->select_item_codes.length; i++) {
snprintf(name, STR_LEN, "SELECT_ITEM_%d", i + 1);
state_config->select_item_codes.values[i] =
config_file_get_int(config, name, UNSET_MIDI_CODE);
}
state_config->state_max = state_config->select_page_codes.length *
state_config->select_item_codes.length;
state_config->select_frag_codes.length =
config_file_get_int(config, "FRAG_COUNT", 1);
for (unsigned int i = 0; i < state_config->select_frag_codes.length; i++) {
snprintf(name, STR_LEN, "SELECT_FRAG_%d", i + 1);
state_config->select_frag_codes.values[i] =
config_file_get_int(config, name, UNSET_MIDI_CODE);
}
state_config->midi_active_counts.length = state_config->midi_counts.length =
state_config->midi_active_offsets.length =
state_config->midi_offsets.length =
state_config->values_offsets.length =
config_file_get_int(config, "MIDI_COUNT", 0);
count = 0;
for (unsigned int i = 0; i < state_config->midi_active_counts.length; i++) {
snprintf(name, STR_LEN, "MIDI_%d_ACTIVE_COUNT", i + 1);
state_config->midi_active_counts.values[i] =
config_file_get_int(config, name, 1);
state_config->midi_active_offsets.values[i] = count;
count += state_config->midi_active_counts.values[i];
}
state_config->midi_active_codes.length = count;
for (unsigned int i = 0; i < state_config->midi_active_counts.length; i++) {
for (unsigned int j = 0; j < state_config->midi_active_counts.values[i];
j++) {
snprintf(name, STR_LEN, "MIDI_%d_ACTIVE_%d", i + 1, j + 1);
state_config->midi_active_codes
.values[state_config->midi_active_offsets.values[i] + j] =
config_file_get_int(config, name, UNSET_MIDI_CODE);
}
}
count = 0;
offset = 0;
for (unsigned int i = 0; i < state_config->midi_counts.length; i++) {
snprintf(name, STR_LEN, "MIDI_%d_COUNT", i + 1);
state_config->midi_counts.values[i] = config_file_get_int(config, name, 0);
state_config->midi_offsets.values[i] = count;
state_config->values_offsets.values[i] = offset;
offset += state_config->midi_counts.values[i] *
state_config->midi_active_counts.values[i];
count += state_config->midi_counts.values[i];
}
state_config->value_count = offset;
state_config->midi_codes.length = count * 3;
for (unsigned int i = 0; i < state_config->midi_counts.length; i++) {
offset = state_config->midi_offsets.values[i];
for (unsigned int j = 0; j < state_config->midi_counts.values[i]; j++) {
snprintf(name, STR_LEN, "MIDI_%d_%d_X", i + 1, j + 1);
state_config->midi_codes.values[(offset + j) * 3] =
config_file_get_int(config, name, UNSET_MIDI_CODE);
snprintf(name, STR_LEN, "MIDI_%d_%d_Y", i + 1, j + 1);
state_config->midi_codes.values[(offset + j) * 3 + 1] =
config_file_get_int(config, name, UNSET_MIDI_CODE);
snprintf(name, STR_LEN, "MIDI_%d_%d_Z", i + 1, j + 1);
state_config->midi_codes.values[(offset + j) * 3 + 2] =
config_file_get_int(config, name, UNSET_MIDI_CODE);
}
}
state_config->fader_codes.length =
config_file_get_int(config, "FADER_COUNT", 0);
for (unsigned int i = 0; i < state_config->fader_codes.length; i++) {
snprintf(name, STR_LEN, "FADER_%d", i + 1);
state_config->fader_codes.values[i] =
config_file_get_int(config, name, UNSET_MIDI_CODE);
}
state_config->tap_tempo_code =
config_file_get_int(config, "TAP_TEMPO", UNSET_MIDI_CODE);
strlcpy(state_config->save_file_prefix,
config_file_get_str(config, "SAVE_FILE_PREFIX", "forge_save"),
STR_LEN);
}
void state_midi_event(SharedContext *context, const StateConfig *state_config,
const MidiDevice *midi, unsigned char code,
unsigned char value, bool trace_midi) {
unsigned int i, j, k, part;
bool found;
found = false;
// PAGE CHANGE
i = arr_uint_index_of(state_config->select_page_codes, code);
if (i != ARRAY_NOT_FOUND) {
found = true;
if (value > 0) {
context->page = i;
update_page(context, state_config, midi);
}
}
// TARGET CHANGE
i = arr_uint_index_of(state_config->select_frag_codes, code);
if (i != ARRAY_NOT_FOUND) {
found = true;
if (value > 0) {
context->selected = i;
update_page(context, state_config, midi);
}
}
// ITEM CHANGE
i = arr_uint_index_of(state_config->select_item_codes, code);
if (i != ARRAY_NOT_FOUND) {
found = true;
if (value > 0) {
context->state.values[context->selected] =
context->page * state_config->select_item_codes.length + i;
update_page(context, state_config, midi);
}
}
// ACTIVE CHANGE
i = arr_uint_index_of(state_config->midi_active_codes, code);
if (i != ARRAY_NOT_FOUND) {
found = true;
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_values(context, state_config, midi);
}
}
// VALUE CHANGE
i = arr_uint_index_of(state_config->midi_codes, code);
if (i != ARRAY_NOT_FOUND) {
found = true;
j = i / 3;
part = arr_uint_remap_index(state_config->midi_offsets, &j);
k = state_config->values_offsets.values[part] +
context->active[part] * state_config->midi_counts.values[part] + j;
if (arr_uint_index_of(state_config->fader_codes, code) != ARRAY_NOT_FOUND) {
context->values[k][i % 3] = (float)value / MIDI_MAX;
} else if (value > 0) {
if (context->values[k][i % 3] > 0.5) {
context->values[k][i % 3] = 0;
midi_write(midi, code, 0);
} else {
context->values[k][i % 3] = 1;
midi_write(midi, code, MIDI_MAX);
}
}
}
if (code == state_config->tap_tempo_code) {
found = true;
midi_write(midi, code, value);
if (value > 0) {
tempo_tap(&context->tempo);
}
}
if (!found) {
if (trace_midi) {
log_trace("unknown midi: %d %d", code, value);
}
midi_write(midi, code, value);
} else if (trace_midi) {
log_trace("midi: %d %d", code, value);
}
}
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");
randomize(context, state_config);
update_values(context, state_config, midi);
} else if (code == 1082) {
log_info("[SHIFT+R] Reset");
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"));
context->demo = !context->demo;
} else if (code == 65) {
// A: auto random on/off
log_info(
(context->auto_random ? "[A] Auto Random OFF" : "[A] Auto Random ON"));
context->auto_random = !context->auto_random;
} else if (code == 263) {
// LEFT ARROW
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
context->auto_random_cycle += 1;
log_info("[RIGHT] Auto Random Cycle: %d", context->auto_random_cycle);
} else if (code == 265) {
// UP ARROW
tempo_set(&context->tempo, context->tempo.tempo + 1);
log_info("[UP] Tempo: %f", context->tempo);
} else if (code == 264) {
// DOWN ARROW
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);
} else {
log_info("[%d] No hotkey defined", code);
}
}
bool state_background_write(SharedContext *context,
const StateConfig *state_config,
const MidiDevice *midi) {
pid_t pid;
bool beat_active, last_active, change, last_change;
pid = fork();
if (pid < 0) {
log_error("Could not create subprocess");
return false;
}
if (pid == 0) {
return true;
}
log_info("(state) background writing started (pid: %d)", pid);
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);
safe_midi_write(midi,
state_config->select_frag_codes.values[context->selected],
beat_active ? MIDI_MAX : 0);
}
last_active = beat_active;
change = tempo_progress(&context->tempo,
(double)context->auto_random_cycle) < 0.5;
if (context->auto_random && change && !last_change) {
randomize(context, state_config);
update_values(context, state_config, midi);
}
last_change = change;
}
log_info("(state) background writing stopped by main thread (pid: %d)", pid);
return false;
}
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);
context->demo = demo;
context->auto_random = auto_random;
context->auto_random_cycle = auto_random_cycles;
context->state.length = state_config->select_frag_codes.length;
memset(context->state.values, 0, sizeof(context->state.values));
if (auto_random) {
randomize(context, state_config);
}
memset(context->active, 0, sizeof(context->active));
memset(context->values, 0, sizeof(context->values));
context->page = 0;
context->selected = 0;
memset(context->seeds, 0, sizeof(context->seeds));
for (unsigned int i = 0; i < context->state.length; i++) {
context->seeds[i] = rand_uint(1000);
}
if (load_state) {
load_from_default_file(context, state_config);
}
}
void state_save(const SharedContext *context, const StateConfig *state_config) {
save_to_default_file(context, state_config);
}