diff --git a/src/jalv_console.c b/src/jalv_console.c index 9c77eaa..e1d0a63 100644 --- a/src/jalv_console.c +++ b/src/jalv_console.c @@ -73,6 +73,7 @@ jalv_init(int* argc, char*** argv, JalvOptions* opts) { int n_controls = 0; int a = 1; + opts->preset_path = jalv_strdup("./lv2"); for (; a < *argc && (*argv)[a][0] == '-'; ++a) { if ((*argv)[a][1] == 'h') { return print_usage((*argv)[0], true); @@ -153,12 +154,21 @@ jalv_print_controls(Jalv* jalv, bool writable, bool readable) } static int -jalv_print_preset(Jalv* ZIX_UNUSED(jalv), +jalv_print_preset(Jalv* jalv, const LilvNode* node, const LilvNode* title, void* ZIX_UNUSED(data)) { - printf("%s (%s)\n", lilv_node_as_string(node), lilv_node_as_string(title)); + LilvNode* bank = lilv_world_get( + jalv->world, node, jalv->nodes.pset_bank, NULL); + if (bank) { + LilvNode* bank_label = lilv_world_get( + jalv->world, bank, jalv->nodes.rdfs_label, NULL); + printf("%s (%s/%s)\n", lilv_node_as_string(node), lilv_node_as_string(bank_label), lilv_node_as_string(title)); + } else { + printf("%s (%s)\n", lilv_node_as_string(node), lilv_node_as_string(title)); + } + return 0; } @@ -168,6 +178,7 @@ jalv_process_command(Jalv* jalv, const char* cmd) char sym[1024]; uint32_t index = 0; float value = 0.0f; + int count; if (!strncmp(cmd, "help", 4)) { fprintf(stderr, "Commands:\n" @@ -176,6 +187,7 @@ jalv_process_command(Jalv* jalv, const char* cmd) " monitors Print output control values\n" " presets Print available presets\n" " preset URI Set preset\n" + " save preset LABEL Save preset (LABEL=optional_bank/preset)\n" " set INDEX VALUE Set control value by port index\n" " set SYMBOL VALUE Set control value by symbol\n" " SYMBOL = VALUE Set control value by symbol\n"); @@ -188,6 +200,15 @@ jalv_process_command(Jalv* jalv, const char* cmd) jalv_apply_preset(jalv, preset); lilv_node_free(preset); jalv_print_controls(jalv, true, false); + } else if (sscanf(cmd, "save preset %s", sym) > 0) { + for(int i = 0; i < sizeof(sym); ++i) { + if(cmd[i + 12] == '\0' || cmd[i + 12] == '\r' || cmd[i + 12] == '\n') { + sym[i] = '\0'; + break; + } + sym[i] = cmd[i + 12]; + } + jalv_save_preset(jalv, NULL, NULL, sym, NULL); } else if (strcmp(cmd, "controls\n") == 0) { jalv_print_controls(jalv, true, false); } else if (strcmp(cmd, "monitors\n") == 0) { diff --git a/src/jalv_gtk.c b/src/jalv_gtk.c index d6c7d0b..e515439 100644 --- a/src/jalv_gtk.c +++ b/src/jalv_gtk.c @@ -136,6 +136,8 @@ jalv_init(int* argc, char*** argv, JalvOptions* opts) "JACK client name", NULL}, { "exact-jack-name", 'x', 0, G_OPTION_ARG_NONE, &opts->name_exact, "Exact JACK client name (exit if taken)", NULL }, + { "preset-path", 'P', 0, G_OPTION_ARG_NONE, &opts->preset_path, + "Default path to save presets", "./lv2" }, { 0, 0, 0, G_OPTION_ARG_NONE, 0, 0, 0 } }; GError* error = NULL; const int err = gtk_init_with_args( @@ -1345,8 +1347,12 @@ static void jalv_process_command(Jalv* jalv, const char* cmd) { char sym[1024]; + char path[1024]; + char uri[1024]; + char name[256]; uint32_t index = 0; float value = 0.0f; + int count; if (!strncmp(cmd, "help", 4)) { fprintf(stderr, "Commands:\n" @@ -1355,12 +1361,23 @@ jalv_process_command(Jalv* jalv, const char* cmd) " monitors Print output control values\n" " presets Print available presets\n" " preset URI Set preset\n" + " save preset LABEL Save as preset\n" + " optional parameters: PATH FILENAME URI\n" " set INDEX VALUE Set control value by port index\n" " set SYMBOL VALUE Set control value by symbol\n" " SYMBOL = VALUE Set control value by symbol\n"); } else if (strcmp(cmd, "presets\n") == 0) { jalv_unload_presets(jalv); jalv_load_presets(jalv, jalv_print_preset, NULL); + } else if ((count = sscanf(cmd, "save preset %s %s %s", &sym, &path, &name, &uri)) > 0) { + if (count < 2) + sprintf(path, "%s", jalv->opts.preset_path); + if (count < 3) + sprintf(name, "%s.ttl", sym); + if (count < 4) + jalv_save_preset(jalv, path, NULL, sym, name); + else + jalv_save_preset(jalv, path, uri, sym, name); } else if (sscanf(cmd, "preset %1023[-a-zA-Z0-9_:/.%%#]", sym) == 1) { LilvNode* preset = lilv_new_uri(jalv->world, sym); lilv_world_load_resource(jalv->world, preset); diff --git a/src/jalv_internal.h b/src/jalv_internal.h index 40b22bd..2b1f0e0 100644 --- a/src/jalv_internal.h +++ b/src/jalv_internal.h @@ -177,6 +177,7 @@ typedef struct { int name_exact; ///< Exit if name is taken char* load; ///< Path for state to load char* preset; ///< URI of preset to load + char* preset_path; ///< Default path to save presets char** controls; ///< Control values uint32_t buffer_size; ///< Plugin <= >UI communication buffer size double update_rate; ///< UI update rate in Hz diff --git a/src/jalv_qt.cpp b/src/jalv_qt.cpp index 04e011f..6110f12 100644 --- a/src/jalv_qt.cpp +++ b/src/jalv_qt.cpp @@ -846,8 +846,12 @@ static void jalv_process_command(Jalv* jalv, const char* cmd) { char sym[1024]; + char path[1024]; + char uri[1024]; + char name[256]; uint32_t index = 0; float value = 0.0f; + int count; if (!strncmp(cmd, "help", 4)) { fprintf(stderr, "Commands:\n" @@ -855,6 +859,8 @@ jalv_process_command(Jalv* jalv, const char* cmd) " controls Print settable control values\n" " monitors Print output control values\n" " presets Print available presets\n" + " save preset LABEL Save as preset\n" + " optional parameters: PATH FILENAME URI\n" " preset URI Set preset\n" " set INDEX VALUE Set control value by port index\n" " set SYMBOL VALUE Set control value by symbol\n" @@ -862,6 +868,15 @@ jalv_process_command(Jalv* jalv, const char* cmd) } else if (strcmp(cmd, "presets\n") == 0) { jalv_unload_presets(jalv); jalv_load_presets(jalv, jalv_print_preset, NULL); + } else if ((count = sscanf(cmd, "save preset %s %s %s", &sym, &path, &name, &uri)) > 0) { + if (count < 2) + sprintf(path, "%s", jalv->opts.preset_path); + if (count < 3) + sprintf(name, "%s.ttl", sym); + if (count < 4) + jalv_save_preset(jalv, path, NULL, sym, name); + else + jalv_save_preset(jalv, path, uri, sym, name); } else if (sscanf(cmd, "preset %1023[-a-zA-Z0-9_:/.%%#]", sym) == 1) { LilvNode* preset = lilv_new_uri(jalv->world, sym); lilv_world_load_resource(jalv->world, preset); diff --git a/src/state.c b/src/state.c index d7dcb71..b424b2c 100644 --- a/src/state.c +++ b/src/state.c @@ -21,6 +21,7 @@ #include "lv2/core/lv2.h" #include "lv2/state/state.h" #include "lv2/urid/urid.h" +#include "lv2/presets/presets.h" #include "zix/common.h" #include "zix/ring.h" #include "zix/sem.h" @@ -29,6 +30,7 @@ #include #include #include +#include char* jalv_make_path(LV2_State_Make_Path_Handle handle, @@ -208,6 +210,36 @@ jalv_apply_preset(Jalv* jalv, const LilvNode* preset) return 0; } +/** + Fix symbol names to match LV2 spec, i.e. a-z A-Z _ 0-9 (except for first char) + symbol: c-string symbol + additional_chars: c-string containing additional allowable characters + returns: Quantity of characters converted + note: Converts invalid characters to underscore within the passed symbol +*/ +int +fix_symbol(char* symbol, const char* additional_chars) +{ + int ret = 0; + for (uint32_t i = 0; i < strlen(symbol); ++i) { + uint32_t bad_char = 1; + if (symbol[i] >= 'a' && symbol[i] <= 'z' || symbol[i] >= 'A' && symbol[i] <= 'Z' || symbol[i] == '_' || i > 0 && symbol[i] >= '0' && symbol[i] <= '9') { + bad_char = 0; + } else { + for (uint32_t j = 0; j < strlen(additional_chars); ++j) + if (symbol[i] == additional_chars[j]) { + bad_char = 0; + break; + } + } + if (bad_char) { + symbol[i] = '_'; + ++ret; + } + } + return ret; +} + int jalv_save_preset(Jalv* jalv, const char* dir, @@ -215,18 +247,74 @@ jalv_save_preset(Jalv* jalv, const char* label, const char* filename) { + if(!label) + return 1; + + const char* preset_name = NULL; + char bank_name[1024]; + char full_dir[1024]; + char full_filename[1024]; + + for(int i = 0; i < strlen(label); ++i) { + if(label[i] == '/') { + if(i + 1 < strlen(label)) + preset_name = label + i + 1; + bank_name[i] = '\0'; + break; + } + bank_name[i] = label[i]; + } + + if(!preset_name) { + // Not found '/' so no bank specified + preset_name = label; + bank_name[0] = '\0'; + } + fix_symbol(bank_name, ""); + + //char* plugin_name; + //LilvNode* name = lilv_plugin_get_name(jalv->plugin); + //plugin_name = jalv_strdup(lilv_node_as_string(name)); + //lilv_node_free(name); + + if(dir) + if(strlen(bank_name)) + sprintf(full_dir, "%s/%s", dir, bank_name); + //sprintf(full_dir, "%s/%s_%s", dir, plugin_name, bank_name); + else + sprintf(full_dir, "%s", dir); + else + if(strlen(bank_name)) + sprintf(full_dir, "./%s", bank_name); + //sprintf(full_dir, "./%s_%s", plugin_name, bank_name); + else + sprintf(full_dir, "."); + + if(filename) + sprintf(full_filename, "%s", filename); + else + sprintf(full_filename, "%s.ttl", preset_name); + + fix_symbol(full_filename, "."); + fix_symbol(full_dir, "./"); + LilvState* const state = lilv_state_new_from_instance( jalv->plugin, jalv->instance, &jalv->map, - jalv->temp_dir, dir, dir, dir, + jalv->temp_dir, full_dir, full_dir, full_dir, get_port_value, jalv, LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE, NULL); - if (label) { - lilv_state_set_label(state, label); + lilv_state_set_label(state, preset_name); + + if(strlen(bank_name)) { + const LV2_URID atom_urid = jalv->map.map(jalv, LV2_ATOM__URID); + const LV2_URID bank_urid = jalv->map.map(jalv, bank_name); + const LV2_URID bank_key_urid = jalv->map.map(jalv, LV2_PRESETS__bank); + lilv_state_set_metadata(state, bank_key_urid, &bank_urid, sizeof(bank_urid), atom_urid, LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); } int ret = lilv_state_save( - jalv->world, &jalv->map, &jalv->unmap, state, uri, dir, filename); + jalv->world, &jalv->map, &jalv->unmap, state, uri, full_dir, full_filename); lilv_state_free(jalv->preset); jalv->preset = state;