diff --git a/.gitignore b/.gitignore index 2b9891c..0a62d5c 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ configure pkg forge-* confdeps.* -conftest.* \ No newline at end of file +conftest.* +state.txt \ No newline at end of file diff --git a/README.md b/README.md index 3592533..825fd3c 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ make -f Makefile.dev release-arch - [x] Read midi mapping config file - [x] Write Midi events - [x] Send midi data to shaders - - [ ] Save midi state + - [x] Save midi state - [ ] Load midi state from last save - [x] State machine with A/B switch - [x] Tap-tempo feature diff --git a/src/args.c b/src/args.c index 6a38a49..739d364 100644 --- a/src/args.c +++ b/src/args.c @@ -22,6 +22,8 @@ static void print_help(int status_code) { "[-mo] " "[-f=DIR_PATH] " "[-c=CFG_PATH] " + "[-sf=STATE_PATH] " + "[-es] " "[-is=SIZE] " "[-v=FILE] " "[-vs=SIZE] " @@ -39,6 +41,8 @@ static void print_help(int status_code) { " -mo, --monitor-only no output screen\n" " -f, --frag fragment shaders directory " "(default: " DATADIR "/shaders)\n" + " -sf, --state-file state save file (default: state.txt)\n" + " -es, --empty-state do not load state save file\n" " -c, --config fragment shaders config file " "(default: " DATADIR "/default.cfg)\n" " -is, --internal-size internal texture height (default: 720)\n" @@ -93,6 +97,8 @@ Parameters args_parse(int argc, char **argv) { params.monitor_screen = 0; params.frag_path = DATADIR "/shaders"; params.config_path = DATADIR "/default.cfg"; + params.state_file = "state.txt"; + params.empty_state = false; params.internal_size = 720; params.video_size = 0; params.base_tempo = 60.0f; @@ -116,6 +122,10 @@ Parameters args_parse(int argc, char **argv) { params.frag_path = value; } else if (is_arg(arg, "-c") || is_arg(arg, "--config")) { params.config_path = value; + } else if (is_arg(arg, "-sf") || is_arg(arg, "--state-file")) { + params.state_file = value; + } else if (is_arg(arg, "-es") || is_arg(arg, "--empty-state")) { + params.empty_state = true; } else if (is_arg(arg, "-is") || is_arg(arg, "--internal-size")) { params.internal_size = parse_uint(arg, value); if (params.internal_size == 0) { diff --git a/src/file.c b/src/file.c index c2200ba..ddc11a9 100644 --- a/src/file.c +++ b/src/file.c @@ -75,6 +75,28 @@ File file_read(char *path) { return file; } +void file_write(char *path, ConstStringArray lines) { + unsigned int i; + FILE *file_pointer; + + log_info("Writing %s...", path); + + // open file + file_pointer = fopen(path, "w"); + if (file_pointer == NULL) { + log_warn("Cannot open file '%s'", path); + return; + } + + // write file + for (i = 0; i < lines.length; i++) { + fprintf(file_pointer, "%s\n", lines.values[i]); + } + + // close file + fclose(file_pointer); +} + void file_prepend(File *src, File extra) { char *old_src_content; diff --git a/src/file.h b/src/file.h index 48a33b5..9f68abe 100644 --- a/src/file.h +++ b/src/file.h @@ -13,4 +13,6 @@ void file_prepend(File *src, File extra); void file_free(File *file, bool free_path); +void file_write(char *path, ConstStringArray lines); + #endif /* FILE_H */ \ No newline at end of file diff --git a/src/forge.c b/src/forge.c index cbe2573..b09bdbb 100644 --- a/src/forge.c +++ b/src/forge.c @@ -58,7 +58,8 @@ static void compute_fps() { static void init_context(Parameters params, unsigned int in_count) { unsigned int i; - state_init(context, state_config, params.demo, params.base_tempo); + state_init(context, state_config, params.demo, params.base_tempo, + params.state_file, params.empty_state); context->monitor = params.monitor; @@ -314,6 +315,8 @@ void forge_run(Parameters params) { context->stop = true; + state_save(context, state_config, params.state_file); + shaders_free(program); if (window_output != NULL) { diff --git a/src/state.c b/src/state.c index bf31f29..a427f1c 100644 --- a/src/state.c +++ b/src/state.c @@ -1,8 +1,10 @@ #include +#include #include "arr.h" #include "config.h" #include "config_file.h" +#include "file.h" #include "midi.h" #include "rand.h" #include "state.h" @@ -317,8 +319,23 @@ bool state_background_midi_write(SharedContext *context, return false; } +void state_load(SharedContext *context, StateConfig state_config, + char *state_file) { + File saved_state; + + saved_state = file_read(state_file); + + if (saved_state.error) { + return; + } + + // TODO load state + + file_free(&saved_state, false); +} + void state_init(SharedContext *context, StateConfig state_config, bool demo, - unsigned int base_tempo) { + unsigned int base_tempo, char *state_file, bool empty_state) { unsigned int i; context->tempo = tempo_init(); @@ -340,9 +357,13 @@ void state_init(SharedContext *context, StateConfig state_config, bool demo, memset(context->seeds, 0, sizeof(context->seeds)); - for (i = 0; i < state_config.select_frag_codes.length; i++) { + for (i = 0; i < context->state.length; i++) { context->seeds[i] = rand_uint(1000); } + + if (!empty_state) { + state_load(context, state_config, state_file); + } } void state_randomize(SharedContext *context, StateConfig state_config) { @@ -351,4 +372,41 @@ void state_randomize(SharedContext *context, StateConfig state_config) { for (i = 0; i < context->state.length; i++) { context->state.values[i] = rand_uint(state_config.state_max); } -} \ No newline at end of file +} + +void state_save(SharedContext *context, StateConfig state_config, + char *state_file) { + ConstStringArray lines; + unsigned int i; + + log_info("Saving state to '%s'...", state_file); + + lines.length = 0; + + sprintf(lines.values[lines.length++], "tempo=%d", + (unsigned int)context->tempo.tempo); + sprintf(lines.values[lines.length++], "page=%d", context->page); + sprintf(lines.values[lines.length++], "selected=%d", context->selected); + + for (i = 0; i < context->state.length; i++) { + sprintf(lines.values[lines.length++], "seed_%d=%d", i, context->seeds[i]); + sprintf(lines.values[lines.length++], "state_%d=%d", i, + context->state.values[i]); + } + + for (i = 0; i < state_config.midi_active_counts.length; i++) { + sprintf(lines.values[lines.length++], "active_%d=%d", i, + context->active[i]); + } + + for (i = 0; i < state_config.midi_codes.length; i++) { + sprintf(lines.values[lines.length++], "value_%d_x=%d", i, + (unsigned int)(context->values[i][0] * MIDI_MAX)); + sprintf(lines.values[lines.length++], "value_%d_y=%d", i, + (unsigned int)(context->values[i][1] * MIDI_MAX)); + sprintf(lines.values[lines.length++], "value_%d_z=%d", i, + (unsigned int)(context->values[i][2] * MIDI_MAX)); + } + + file_write(state_file, lines); +} diff --git a/src/state.h b/src/state.h index f2a8c94..9b6155a 100644 --- a/src/state.h +++ b/src/state.h @@ -13,8 +13,11 @@ bool state_background_midi_write(SharedContext *context, StateConfig state_config, MidiDevice midi); void state_init(SharedContext *context, StateConfig state_config, bool demo, - unsigned int base_tempo); + unsigned int base_tempo, char *state_file, bool empty_state); void state_randomize(SharedContext *context, StateConfig state_config); +void state_save(SharedContext *context, StateConfig state_config, + char *state_file); + #endif /* STATE_H */ \ No newline at end of file diff --git a/src/types.h b/src/types.h index 7f75777..095eda3 100644 --- a/src/types.h +++ b/src/types.h @@ -25,6 +25,11 @@ typedef ARRAY(StringArray, char *); typedef ARRAY(Vec3Array, vec3); typedef ARRAY(GLuintArray, GLuint); +typedef struct ConstStringArray { + char values[ARRAY_SIZE][1000]; + unsigned int length; +} ConstStringArray; + typedef struct Parameters { bool hot_reload; bool output; @@ -33,6 +38,8 @@ typedef struct Parameters { unsigned int monitor_screen; char *frag_path; char *config_path; + char *state_file; + bool empty_state; unsigned int internal_size; unsigned int video_size; float base_tempo;