7b9f5ca032
Clang Lint CI / lint-no-video (push) Successful in 1m17s
Clang Build CI / run-no-video (push) Successful in 1m18s
Clang Build CI / run-video (push) Successful in 1m17s
Clang Build CI / build-release (push) Successful in 1m54s
Clang Lint CI / lint-video (push) Successful in 1m22s
684 lines
22 KiB
C
684 lines
22 KiB
C
#include <log.h>
|
|
#include <pthread.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(MidiDevice midi, unsigned int code,
|
|
unsigned char value) {
|
|
if (code != UNSET_MIDI_CODE && code < 1000) {
|
|
midi_write(midi, code, value);
|
|
}
|
|
}
|
|
|
|
static void update_page(const Context *context, StateConfig state_config,
|
|
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 Context *context, StateConfig state_config,
|
|
MidiDevice midi, bool beat_active) {
|
|
unsigned int k;
|
|
|
|
for (unsigned int i = 0; i < state_config.group_active_counts.length; i++) {
|
|
for (unsigned int j = 0; j < state_config.group_active_counts.values[i];
|
|
j++) {
|
|
k = state_config.group_active_offsets.values[i] + j;
|
|
safe_midi_write(midi, state_config.group_active_codes.values[k],
|
|
context->active[i] == j && beat_active ? MIDI_MAX : 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void update_values(const Context *context, StateConfig state_config,
|
|
MidiDevice midi) {
|
|
unsigned int j;
|
|
unsigned int k;
|
|
unsigned int part;
|
|
|
|
for (unsigned int i = 0; i < state_config.codes.length; i++) {
|
|
j = i / 3;
|
|
part = arr_uint_remap_index(state_config.group_offsets, &j);
|
|
k = state_config.values_offsets.values[part] +
|
|
context->active[part] * state_config.group_counts.values[part] + j;
|
|
safe_midi_write(midi, state_config.codes.values[i],
|
|
context->values[k][i % 3] * MIDI_MAX);
|
|
}
|
|
}
|
|
|
|
static void reset(Context *context) {
|
|
memset(context->values, 0, sizeof(context->values));
|
|
memset(context->state.values, 0, sizeof(context->state.values));
|
|
}
|
|
|
|
static void randomize(Context *context, StateConfig state_config) {
|
|
unsigned int j;
|
|
unsigned int l;
|
|
unsigned int part;
|
|
|
|
for (unsigned int i = 0; i < state_config.codes.length; i++) {
|
|
j = i / 3;
|
|
part = arr_uint_remap_index(state_config.group_offsets, &j);
|
|
for (unsigned int k = 0; k < state_config.group_active_counts.values[part];
|
|
k++) {
|
|
l = state_config.values_offsets.values[part] +
|
|
k * state_config.group_counts.values[part] + j;
|
|
|
|
if (arr_uint_index_of(state_config.fader_codes,
|
|
state_config.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(Context *context, StateConfig state_config,
|
|
const 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.group_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(Context *context, 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(Context *context, 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 Context *context, 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.group_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 Context *context,
|
|
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 Context *context, 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->group_active_counts.length = state_config->group_counts.length =
|
|
state_config->group_active_offsets.length =
|
|
state_config->group_offsets.length =
|
|
state_config->values_offsets.length =
|
|
config_file_get_int(config, "GROUP_COUNT", 0);
|
|
|
|
count = 0;
|
|
for (unsigned int i = 0; i < state_config->group_active_counts.length; i++) {
|
|
snprintf(name, STR_LEN, "GROUP_%d_ACTIVE_COUNT", i + 1);
|
|
state_config->group_active_counts.values[i] =
|
|
config_file_get_int(config, name, 1);
|
|
state_config->group_active_offsets.values[i] = count;
|
|
count += state_config->group_active_counts.values[i];
|
|
}
|
|
|
|
state_config->group_active_codes.length = count;
|
|
|
|
for (unsigned int i = 0; i < state_config->group_active_counts.length; i++) {
|
|
for (unsigned int j = 0; j < state_config->group_active_counts.values[i];
|
|
j++) {
|
|
snprintf(name, STR_LEN, "GROUP_%d_ACTIVE_%d", i + 1, j + 1);
|
|
state_config->group_active_codes
|
|
.values[state_config->group_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->group_counts.length; i++) {
|
|
snprintf(name, STR_LEN, "GROUP_%d_COUNT", i + 1);
|
|
state_config->group_counts.values[i] = config_file_get_int(config, name, 0);
|
|
state_config->group_offsets.values[i] = count;
|
|
state_config->values_offsets.values[i] = offset;
|
|
offset += state_config->group_counts.values[i] *
|
|
state_config->group_active_counts.values[i];
|
|
count += state_config->group_counts.values[i];
|
|
}
|
|
|
|
state_config->value_count = offset;
|
|
|
|
state_config->codes.length = count * 3;
|
|
|
|
for (unsigned int i = 0; i < state_config->group_counts.length; i++) {
|
|
offset = state_config->group_offsets.values[i];
|
|
for (unsigned int j = 0; j < state_config->group_counts.values[i]; j++) {
|
|
snprintf(name, STR_LEN, "GROUP_%d_%d_X", i + 1, j + 1);
|
|
state_config->codes.values[(offset + j) * 3] =
|
|
config_file_get_int(config, name, UNSET_MIDI_CODE);
|
|
|
|
snprintf(name, STR_LEN, "GROUP_%d_%d_Y", i + 1, j + 1);
|
|
state_config->codes.values[(offset + j) * 3 + 1] =
|
|
config_file_get_int(config, name, UNSET_MIDI_CODE);
|
|
|
|
snprintf(name, STR_LEN, "GROUP_%d_%d_Z", i + 1, j + 1);
|
|
state_config->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);
|
|
|
|
state_config->key_randomize =
|
|
config_file_get_int(config, "KEY_RANDOMIZE", 1082);
|
|
state_config->key_reset = config_file_get_int(config, "KEY_RESET", 11082);
|
|
state_config->key_demo = config_file_get_int(config, "KEY_DEMO", 1068);
|
|
state_config->key_autorand =
|
|
config_file_get_int(config, "KEY_AUTORAND", 1065);
|
|
state_config->key_autorand_down =
|
|
config_file_get_int(config, "KEY_AUTORAND_DOWN", 1263);
|
|
state_config->key_autorand_up =
|
|
config_file_get_int(config, "KEY_AUTORAND_UP", 1262);
|
|
state_config->key_tempo_down =
|
|
config_file_get_int(config, "KEY_TEMPO_DOWN", 1264);
|
|
state_config->key_tempo_up =
|
|
config_file_get_int(config, "KEY_TEMPO_UP", 1265);
|
|
|
|
if (config_file_has(config, "KEY_LOAD_COUNT")) {
|
|
state_config->key_load.length =
|
|
config_file_get_int(config, "KEY_LOAD_COUNT", 0);
|
|
|
|
for (unsigned int i = 0; i < state_config->key_load.length; i++) {
|
|
snprintf(name, STR_LEN, "KEY_LOAD_%d", i + 1);
|
|
state_config->key_load.values[i] = config_file_get_int(config, name, 0);
|
|
}
|
|
} else {
|
|
state_config->key_load.length = 10;
|
|
state_config->key_load.values[0] = 1049;
|
|
state_config->key_load.values[1] = 1050;
|
|
state_config->key_load.values[2] = 1051;
|
|
state_config->key_load.values[3] = 1052;
|
|
state_config->key_load.values[4] = 1053;
|
|
state_config->key_load.values[5] = 1054;
|
|
state_config->key_load.values[6] = 1055;
|
|
state_config->key_load.values[7] = 1056;
|
|
state_config->key_load.values[8] = 1057;
|
|
state_config->key_load.values[9] = 1048;
|
|
}
|
|
|
|
if (config_file_has(config, "KEY_SAVE_COUNT")) {
|
|
state_config->key_save.length =
|
|
config_file_get_int(config, "KEY_SAVE_COUNT", 0);
|
|
|
|
for (unsigned int i = 0; i < state_config->key_save.length; i++) {
|
|
snprintf(name, STR_LEN, "KEY_SAVE_%d", i + 1);
|
|
state_config->key_save.values[i] = config_file_get_int(config, name, 0);
|
|
}
|
|
} else {
|
|
state_config->key_save.length = 10;
|
|
state_config->key_save.values[0] = 11049;
|
|
state_config->key_save.values[1] = 11050;
|
|
state_config->key_save.values[2] = 11051;
|
|
state_config->key_save.values[3] = 11052;
|
|
state_config->key_save.values[4] = 11053;
|
|
state_config->key_save.values[5] = 11054;
|
|
state_config->key_save.values[6] = 11055;
|
|
state_config->key_save.values[7] = 11056;
|
|
state_config->key_save.values[8] = 11057;
|
|
state_config->key_save.values[9] = 11048;
|
|
}
|
|
}
|
|
|
|
static bool compute_event(Context *context, StateConfig state_config,
|
|
MidiDevice midi, unsigned int code,
|
|
unsigned int value) {
|
|
unsigned int i;
|
|
unsigned int j;
|
|
unsigned int k;
|
|
unsigned int part;
|
|
|
|
// PAGE CHANGE
|
|
i = arr_uint_index_of(state_config.select_page_codes, code);
|
|
if (i != ARRAY_NOT_FOUND) {
|
|
if (value > 0) {
|
|
context->page = i;
|
|
update_page(context, state_config, midi);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// TARGET CHANGE
|
|
i = arr_uint_index_of(state_config.select_frag_codes, code);
|
|
if (i != ARRAY_NOT_FOUND) {
|
|
if (value > 0) {
|
|
context->selected = i;
|
|
update_page(context, state_config, midi);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ITEM CHANGE
|
|
i = arr_uint_index_of(state_config.select_item_codes, code);
|
|
if (i != ARRAY_NOT_FOUND) {
|
|
if (value > 0) {
|
|
context->state.values[context->selected] =
|
|
context->page * state_config.select_item_codes.length + i;
|
|
update_page(context, state_config, midi);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ACTIVE CHANGE
|
|
i = arr_uint_index_of(state_config.group_active_codes, code);
|
|
if (i != ARRAY_NOT_FOUND) {
|
|
if (value > 0) {
|
|
part = arr_uint_remap_index(state_config.group_active_offsets, &i);
|
|
context->active[part] = i;
|
|
update_active(context, state_config, midi, true);
|
|
update_values(context, state_config, midi);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// VALUE CHANGE
|
|
i = arr_uint_index_of(state_config.codes, code);
|
|
if (i != ARRAY_NOT_FOUND) {
|
|
j = i / 3;
|
|
part = arr_uint_remap_index(state_config.group_offsets, &j);
|
|
k = state_config.values_offsets.values[part] +
|
|
context->active[part] * state_config.group_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;
|
|
safe_midi_write(midi, code, 0);
|
|
} else {
|
|
context->values[k][i % 3] = 1;
|
|
safe_midi_write(midi, code, MIDI_MAX);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// TAP TEMPO
|
|
if (code == state_config.tap_tempo_code) {
|
|
safe_midi_write(midi, code, value);
|
|
if (value > 0) {
|
|
tempo_tap(&context->tempo);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// OTHER KEYS
|
|
if (code == state_config.key_randomize) {
|
|
if (value > 0) {
|
|
log_info("[%d] Randomized", code);
|
|
randomize(context, state_config);
|
|
update_values(context, state_config, midi);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (code == state_config.key_reset) {
|
|
if (value > 0) {
|
|
log_info("[%d] Reset", code);
|
|
reset(context);
|
|
update_values(context, state_config, midi);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (code == state_config.key_demo) {
|
|
if (value > 0) {
|
|
log_info((context->demo ? "[%d] Demo OFF" : "[%d] Demo ON"), code);
|
|
context->demo = !context->demo;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (code == state_config.key_autorand) {
|
|
if (value > 0) {
|
|
log_info((context->auto_random ? "[%d] Auto Random OFF"
|
|
: "[%d] Auto Random ON"),
|
|
code);
|
|
context->auto_random = !context->auto_random;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (code == state_config.key_autorand_down) {
|
|
if (value > 0) {
|
|
if (context->auto_random_cycle > 1) {
|
|
context->auto_random_cycle -= 1;
|
|
}
|
|
log_info("[%d] Auto Random Cycle: %d", code, context->auto_random_cycle);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (code == state_config.key_autorand_up) {
|
|
if (value > 0) {
|
|
context->auto_random_cycle += 1;
|
|
log_info("[%d] Auto Random Cycle: %d", code, context->auto_random_cycle);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (code == state_config.key_tempo_up) {
|
|
if (value > 0) {
|
|
tempo_set(&context->tempo, context->tempo.tempo + 1);
|
|
log_info("[%d] Tempo: %f", code, context->tempo);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (code == state_config.key_tempo_down) {
|
|
if (value > 0) {
|
|
if (context->tempo.tempo > 0) {
|
|
tempo_set(&context->tempo, context->tempo.tempo - 1);
|
|
}
|
|
log_info("[%d] Tempo: %f", code, context->tempo);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// LOAD STATE
|
|
i = arr_uint_index_of(state_config.key_load, code);
|
|
|
|
if (i != ARRAY_NOT_FOUND) {
|
|
if (value > 0) {
|
|
log_info("[%d] Loading state %d", code, i + 1);
|
|
load_from_index_file(context, state_config, i + 1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// SAVE STATE
|
|
i = arr_uint_index_of(state_config.key_save, code);
|
|
|
|
if (i != ARRAY_NOT_FOUND) {
|
|
if (value > 0) {
|
|
log_info("[%d] Saving state %d", code, i + 1);
|
|
save_to_index_file(context, state_config, i + 1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void state_midi_event(Context *context, StateConfig state_config,
|
|
MidiDevice midi, unsigned char code, unsigned char value,
|
|
bool trace_midi) {
|
|
if (!compute_event(context, state_config, midi, code, value)) {
|
|
log_warn("unknown midi: %d %d", 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, MidiDevice midi) {
|
|
if (!compute_event(context, state_config, midi, code, MIDI_MAX)) {
|
|
log_warn("[%d] No hotkey defined", code);
|
|
}
|
|
}
|
|
|
|
void *state_background_write(void *args) {
|
|
StateBackgroundWriteArgs *process_args = (StateBackgroundWriteArgs *)args;
|
|
Context *context = process_args->context;
|
|
StateConfig state_config = process_args->state_config;
|
|
MidiDevice *midi = process_args->midi;
|
|
|
|
bool beat_active;
|
|
bool last_active;
|
|
bool change;
|
|
bool last_change;
|
|
|
|
log_info("(state) background writing started");
|
|
|
|
last_active = false;
|
|
last_change = false;
|
|
|
|
while (!context->stop) {
|
|
beat_active = tempo_progress(&context->tempo, 1.0) < 0.5;
|
|
|
|
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.tap_tempo_code,
|
|
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");
|
|
pthread_exit(NULL);
|
|
}
|
|
|
|
void state_init(Context *context, StateConfig state_config, bool demo,
|
|
bool auto_random, unsigned int auto_random_cycles,
|
|
unsigned int base_tempo, bool load_state) {
|
|
tempo_init(&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 Context *context, StateConfig state_config) {
|
|
save_to_default_file(context, state_config);
|
|
}
|