From e5a90b60c1cc62c782ac6b63025696dda95dae38 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sun, 18 Dec 2011 00:56:34 +0000 Subject: Preset saving. git-svn-id: http://svn.drobilla.net/lad/trunk/jalv@3880 a436a847-0d15-0410-975c-d299462d15a1 --- src/jalv.c | 4 +- src/jalv_gtk2.c | 40 ++++++++- src/jalv_internal.h | 6 +- src/presets.c | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 273 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/jalv.c b/src/jalv.c index de40077..743f1dc 100644 --- a/src/jalv.c +++ b/src/jalv.c @@ -62,7 +62,7 @@ typedef struct { LV2_URID map_uri(LV2_URID_Map_Handle handle, - const char* uri) + const char* uri) { //return symap_map(((Jalv*)handle)->symap, uri); const LV2_URID id = symap_map(((Jalv*)handle)->symap, uri); @@ -72,7 +72,7 @@ map_uri(LV2_URID_Map_Handle handle, const char* unmap_uri(LV2_URID_Unmap_Handle handle, - LV2_URID urid) + LV2_URID urid) { return symap_unmap(((Jalv*)handle)->symap, urid); } diff --git a/src/jalv_gtk2.c b/src/jalv_gtk2.c index c56bab1..029376a 100644 --- a/src/jalv_gtk2.c +++ b/src/jalv_gtk2.c @@ -60,7 +60,7 @@ on_save_activate(GtkWidget* widget, void* ptr) Jalv* jalv = (Jalv*)ptr; GtkWidget* dialog = gtk_file_chooser_dialog_new( "Save State", - NULL, // FIXME: parent + jalv->window, GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, @@ -90,6 +90,36 @@ typedef struct { LilvNode* preset; } PresetRecord; +static void +on_save_preset_activate(GtkWidget* widget, void* ptr) +{ + Jalv* jalv = (Jalv*)ptr; + + GtkDialog* dialog = GTK_DIALOG(gtk_dialog_new_with_buttons( + "Save Preset", jalv->window, + GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL)); + + GtkWidget* content = gtk_dialog_get_content_area(dialog); + GtkWidget* hbox = gtk_hbox_new(FALSE, 0); + GtkWidget* entry = gtk_entry_new(); + gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("Label:"), FALSE, TRUE, 6); + gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, TRUE, 6); + gtk_box_pack_start(GTK_BOX(content), hbox, TRUE, TRUE, 12); + + gtk_widget_show_all(GTK_WIDGET(dialog)); + gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE); + gtk_dialog_set_default_response(dialog, GTK_RESPONSE_ACCEPT); + if (gtk_dialog_run(dialog) == GTK_RESPONSE_ACCEPT) { + const char* label_str = gtk_entry_get_text(GTK_ENTRY(entry)); + jalv_save_preset(jalv, label_str); + } + + gtk_widget_destroy(GTK_WIDGET(dialog)); +} + static void on_preset_activate(GtkWidget* widget, gpointer data) { @@ -145,6 +175,7 @@ jalv_open_ui(Jalv* jalv, SuilInstance* instance) { GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + jalv->window = window; g_signal_connect(window, "destroy", G_CALLBACK(on_window_destroy), NULL); @@ -169,7 +200,11 @@ jalv_open_ui(Jalv* jalv, GtkWidget* presets = gtk_menu_item_new_with_mnemonic("_Presets"); GtkWidget* presets_menu = gtk_menu_new(); + GtkWidget* save_preset = gtk_menu_item_new_with_mnemonic("_Save Preset..."); gtk_menu_item_set_submenu(GTK_MENU_ITEM(presets), presets_menu); + gtk_menu_shell_append(GTK_MENU_SHELL(presets_menu), save_preset); + gtk_menu_shell_append(GTK_MENU_SHELL(presets_menu), + gtk_separator_menu_item_new()); gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), presets); jalv_load_presets(jalv, add_preset_to_menu, presets_menu); @@ -182,6 +217,9 @@ jalv_open_ui(Jalv* jalv, g_signal_connect(G_OBJECT(save), "activate", G_CALLBACK(on_save_activate), jalv); + g_signal_connect(G_OBJECT(save_preset), "activate", + G_CALLBACK(on_save_preset_activate), jalv); + GtkWidget* alignment = gtk_alignment_new(0.5, 0.5, 1.0, 1.0); gtk_box_pack_start(GTK_BOX(vbox), alignment, TRUE, TRUE, 0); diff --git a/src/jalv_internal.h b/src/jalv_internal.h index d44b5c5..95bc659 100644 --- a/src/jalv_internal.h +++ b/src/jalv_internal.h @@ -92,6 +92,7 @@ typedef struct { const LilvUI* ui; /**< Plugin UI (RDF data) */ LilvInstance* instance; /**< Plugin instance (shared library) */ SuilInstance* ui_instance; /**< Plugin UI instance (shared library) */ + void* window; /**< Window (if applicable) */ struct Port* ports; /**< Port array of size num_ports */ size_t midi_buf_size; /**< Size of MIDI port buffers */ uint32_t num_ports; /**< Size of the two following arrays: */ @@ -153,7 +154,10 @@ int jalv_load_presets(Jalv* jalv, PresetSink sink, void* data); int -jalv_apply_preset(Jalv* jalv, LilvNode* preset); +jalv_apply_preset(Jalv* jalv, const LilvNode* preset); + +int +jalv_save_preset(Jalv* jalv, const char* label); void jalv_save(Jalv* jalv, const char* dir); diff --git a/src/presets.c b/src/presets.c index bf7fd78..1963e87 100644 --- a/src/presets.c +++ b/src/presets.c @@ -14,11 +14,20 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include +#include +#include + +#include "lilv/lilv.h" + #include "jalv-config.h" #include "jalv_internal.h" #define NS_LV2 "http://lv2plug.in/ns/lv2core#" #define NS_PSET "http://lv2plug.in/ns/ext/presets#" +#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#" +#define NS_RDFS "http://www.w3.org/2000/01/rdf-schema#" +#define NS_XSD "http://www.w3.org/2001/XMLSchema#" #define USTR(s) ((const uint8_t*)s) @@ -28,14 +37,14 @@ jalv_load_presets(Jalv* jalv, PresetSink sink, void* data) LilvNodes* presets = lilv_plugin_get_related(jalv->plugin, jalv->preset_class); LILV_FOREACH(nodes, i, presets) { - const LilvNode* preset = lilv_nodes_get(presets, i); + const LilvNode* preset = lilv_nodes_get(presets, i); lilv_world_load_resource(jalv->world, preset); - LilvNodes* titles = lilv_world_find_nodes( + LilvNodes* labels = lilv_world_find_nodes( jalv->world, preset, jalv->label_pred, NULL); - if (titles) { - const LilvNode* title = lilv_nodes_get_first(titles); - sink(jalv, preset, title, data); - lilv_nodes_free(titles); + if (labels) { + const LilvNode* label = lilv_nodes_get_first(labels); + sink(jalv, preset, label, data); + lilv_nodes_free(labels); } else { fprintf(stderr, "Preset <%s> has no rdfs:label\n", lilv_node_as_string(lilv_nodes_get(presets, i))); @@ -54,7 +63,7 @@ get_value(LilvWorld* world, const LilvNode* subject, const LilvNode* predicate) } int -jalv_apply_preset(Jalv* jalv, LilvNode* preset) +jalv_apply_preset(Jalv* jalv, const LilvNode* preset) { LilvNode* lv2_port = lilv_new_uri(jalv->world, NS_LV2 "port"); LilvNode* lv2_symbol = lilv_new_uri(jalv->world, NS_LV2 "symbol"); @@ -86,9 +95,219 @@ jalv_apply_preset(Jalv* jalv, LilvNode* preset) lilv_nodes_free(ports); lilv_node_free(pset_value); - lilv_node_free(preset); lilv_node_free(lv2_symbol); lilv_node_free(lv2_port); return 0; } + +static size_t +file_sink(const void* buf, size_t len, void* stream) +{ + FILE* file = (FILE*)stream; + return fwrite(buf, 1, len, file); +} + +static char* +pathify(const char* in, const char* ext) +{ + const size_t in_len = strlen(in); + const size_t ext_len = ext ? strlen(ext) : 0; + + char* out = malloc(in_len + ext_len + 1); + for (size_t i = 0; i < in_len; ++i) { + char c = in[i]; + if (!((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9'))) { + c = '-'; + } + out[i] = c; + } + if (ext) { + memcpy(out + in_len, ext, ext_len); + } + return out; +} + +static char* +strjoin(const char* a, const char* b) +{ + const size_t a_len = strlen(a); + const size_t b_len = strlen(a); + char* const out = malloc(a_len + b_len + 1); + + memcpy(out, a, a_len); + memcpy(out + a_len, b, b_len); + out[a_len + b_len] = '\0'; + + return out; +} + +int +jalv_save_preset(Jalv* jalv, const char* label) +{ + const char* const home = getenv("HOME"); + if (!home) { + fprintf(stderr, "error: $HOME is undefined\n"); + return 1; + } + + const char* const bundle_name = "presets.lv2/"; + + // Create ~/.lv2/ and ~/.lv2/presets.lv2/ if necessary + char* const lv2dir = strjoin(home, "/.lv2/"); + char* const bundle = strjoin(lv2dir, bundle_name); + char* const filename = pathify(label, ".ttl"); + char* const path = strjoin(bundle, filename); + char* const uri = strjoin("file://", path); + char* const manifest_path = strjoin(bundle, "manifest.ttl"); + char* const manifest_uri = strjoin("file: //", manifest_path); + + int ret = 0; + if ((mkdir(lv2dir, 0755) && errno != EEXIST) + || (mkdir(bundle, 0755) && errno != EEXIST)) { + fprintf(stderr, "error: Unable to create %s (%s)\n", + lv2dir, strerror(errno)); + ret = 2; + goto done; + } + + // Open preset file + FILE* fd = fopen((char*)path, "w"); + if (!fd) { + fprintf(stderr, "error: Failed to open %s (%s)\n", + path, strerror(errno)); + ret = 3; + goto done; + } + + SerdURI base_uri; + SerdNode base = serd_node_new_uri_from_string((const uint8_t*)uri, + NULL, &base_uri); + + SerdEnv* env = serd_env_new(&base); + serd_env_set_prefix_from_strings(env, USTR("lv2"), USTR(NS_LV2)); + serd_env_set_prefix_from_strings(env, USTR("pset"), USTR(NS_PSET)); + serd_env_set_prefix_from_strings(env, USTR("rdfs"), USTR(NS_RDFS)); + + // Write preset file + + SerdWriter* writer = serd_writer_new( + SERD_TURTLE, SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED, + env, &base_uri, + file_sink, + fd); + + serd_env_foreach(env, (SerdPrefixSink)serd_writer_set_prefix, writer); + + // <> a pset:Preset + SerdNode s = serd_node_from_string(SERD_URI, USTR("")); + SerdNode p = serd_node_from_string(SERD_URI, USTR(NS_RDF "type")); + SerdNode o = serd_node_from_string(SERD_CURIE, USTR("pset:Preset")); + serd_writer_write_statement(writer, 0, NULL, &s, &p, &o, NULL, NULL); + + // <> rdfs:label label + p = serd_node_from_string(SERD_URI, USTR(NS_RDFS "label")); + o = serd_node_from_string(SERD_LITERAL, USTR(label)); + serd_writer_write_statement(writer, 0, + NULL, &s, &p, &o, NULL, NULL); + + for (uint32_t i = 0; i < jalv->num_ports; ++i) { + struct Port* const port = &jalv->ports[i]; + if (port->type != TYPE_CONTROL || port->flow != FLOW_INPUT) { + continue; + } + + const LilvNode* sym = lilv_port_get_symbol(jalv->plugin, port->lilv_port); + LilvNode* val = lilv_new_float(jalv->world, port->control); + + const SerdNode port_node = serd_node_from_string( + SERD_BLANK, USTR(lilv_node_as_string(sym))); + + // <> lv2:port _:symbol + p = serd_node_from_string(SERD_URI, USTR(NS_LV2 "port")); + serd_writer_write_statement(writer, SERD_ANON_O_BEGIN, + NULL, &s, &p, &port_node, NULL, NULL); + + // _:symbol lv2:symbol "symbol" + p = serd_node_from_string(SERD_URI, USTR(NS_LV2 "symbol")); + o = serd_node_from_string(SERD_LITERAL, + USTR(lilv_node_as_string(sym))); + serd_writer_write_statement(writer, SERD_ANON_CONT, + NULL, &port_node, &p, &o, NULL, NULL); + + // _:symbol pset:value value + p = serd_node_from_string(SERD_URI, USTR(NS_PSET "value")); + o = serd_node_from_string(SERD_LITERAL, + USTR(lilv_node_as_string(val))); + SerdNode t = serd_node_from_string(SERD_URI, USTR(NS_XSD "decimal")); + serd_writer_write_statement(writer, SERD_ANON_CONT, + NULL, &port_node, &p, &o, &t, NULL); + + lilv_node_free(val); + serd_writer_end_anon(writer, &port_node); + } + + serd_writer_free(writer); + serd_node_free(&base); + + // Append record to manifest + + fclose(fd); + fd = fopen((char*)manifest_path, "a"); + if (!fd) { + fprintf(stderr, "error: Failed to open %s (%s)\n", + path, strerror(errno)); + serd_env_free(env); + ret = 4; + goto done; + } + + base = serd_node_new_uri_from_string((const uint8_t*)manifest_uri, + NULL, &base_uri); + + writer = serd_writer_new( + SERD_TURTLE, SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED, + env, &base_uri, + file_sink, + fd); + + fseek(fd, 0, SEEK_END); + if (ftell(fd) == 0) { + serd_env_foreach(env, (SerdPrefixSink)serd_writer_set_prefix, writer); + } + + s = serd_node_from_string(SERD_URI, USTR(filename)); + + // a pset:Preset + p = serd_node_from_string(SERD_URI, USTR(NS_RDF "type")); + o = serd_node_from_string(SERD_CURIE, USTR("pset:Preset")); + serd_writer_write_statement(writer, 0, NULL, &s, &p, &o, NULL, NULL); + + // rdfs:seeAlso + p = serd_node_from_string(SERD_URI, USTR(NS_RDFS "seeAlso")); + serd_writer_write_statement(writer, 0, NULL, &s, &p, &s, NULL, NULL); + + // lv2:appliesTo + p = serd_node_from_string(SERD_URI, USTR(NS_LV2 "appliesTo")); + o = serd_node_from_string( + SERD_URI, USTR(lilv_node_as_string(lilv_plugin_get_uri(jalv->plugin)))); + serd_writer_write_statement(writer, 0, NULL, &s, &p, &o, NULL, NULL); + + serd_writer_free(writer); + serd_env_free(env); + serd_node_free(&base); + fclose(fd); + +done: + free(manifest_uri); + free(manifest_path); + free(uri); + free(path); + free(filename); + free(bundle); + free(lv2dir); + + return ret; +} -- cgit v1.2.1