From bdfc77e8cd8a586e535f5eab109dd0411e0554a0 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sun, 21 Aug 2011 05:00:54 +0000 Subject: Preliminary support for Jack Session and LV2 Persist. Real command line argument support. git-svn-id: http://svn.drobilla.net/lad/trunk/jalv@3441 a436a847-0d15-0410-975c-d299462d15a1 --- src/jalv.c | 231 ++++++++++++++++++++++++------------------- src/jalv_console.c | 43 ++++++++- src/jalv_gtk2.c | 22 ++++- src/jalv_internal.h | 57 ++++++++++- src/jalv_qt4.cpp | 5 +- src/persist.c | 274 +++++++++++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 513 insertions(+), 119 deletions(-) (limited to 'src') diff --git a/src/jalv.c b/src/jalv.c index 56f1c01..bdf0efc 100644 --- a/src/jalv.c +++ b/src/jalv.c @@ -33,7 +33,6 @@ #endif #include "lv2/lv2plug.in/ns/ext/event/event-helpers.h" -#include "lv2/lv2plug.in/ns/ext/event/event.h" #include "lv2/lv2plug.in/ns/ext/uri-map/uri-map.h" #include "lilv/lilv.h" @@ -49,21 +48,6 @@ typedef struct { float value; } ControlChange; -enum PortType { - CONTROL, - AUDIO, - EVENT -}; - -struct Port { - const LilvPort* lilv_port; - enum PortType type; - jack_port_t* jack_port; /**< For audio/MIDI ports, otherwise NULL */ - float control; /**< For control ports, otherwise 0.0f */ - LV2_Event_Buffer* ev_buffer; /**< For MIDI ports, otherwise NULL */ - bool is_input; -}; - /** Map function for URI map extension. */ @@ -94,13 +78,9 @@ die(const char* msg) exit(EXIT_FAILURE); } -/** Creates a port and connects the plugin instance to its data location. - * - * For audio ports, creates a jack port and connects plugin port to buffer. - * - * For control ports, sets controls array to default value and connects plugin - * port to that element. - */ +/** + Create a port structure from data (pre-instantiation/pre-Jack). +*/ void create_port(Jalv* host, uint32_t port_index, @@ -112,8 +92,7 @@ create_port(Jalv* host, port->jack_port = NULL; port->control = 0.0f; port->ev_buffer = NULL; - - lilv_instance_connect_port(host->instance, port_index, NULL); + port->flow = FLOW_UNKNOWN; /* Get the port symbol for console printing */ const LilvNode* symbol = lilv_port_get_symbol(host->plugin, port->lilv_port); @@ -123,46 +102,99 @@ create_port(Jalv* host, port->lilv_port, host->optional); - enum JackPortFlags jack_flags = 0; + /* Set the port flow (input or output) */ if (lilv_port_is_a(host->plugin, port->lilv_port, host->input_class)) { - jack_flags = JackPortIsInput; - port->is_input = true; + port->flow = FLOW_INPUT; } else if (lilv_port_is_a(host->plugin, port->lilv_port, host->output_class)) { - jack_flags = JackPortIsOutput; - port->is_input = false; - } else if (optional) { - lilv_instance_connect_port(host->instance, port_index, NULL); - return; - } else { + port->flow = FLOW_OUTPUT; + } else if (!optional) { die("Mandatory port has unknown type (neither input nor output)"); } /* Set control values */ if (lilv_port_is_a(host->plugin, port->lilv_port, host->control_class)) { - port->type = CONTROL; + port->type = TYPE_CONTROL; port->control = isnan(default_value) ? 0.0 : default_value; - printf("%s = %f\n", symbol_str, host->ports[port_index].control); } else if (lilv_port_is_a(host->plugin, port->lilv_port, host->audio_class)) { - port->type = AUDIO; + port->type = TYPE_AUDIO; } else if (lilv_port_is_a(host->plugin, port->lilv_port, host->event_class)) { - port->type = EVENT; - } else if (optional) { + port->type = TYPE_EVENT; + } else if (!optional) { + die("Mandatory port has unknown type (neither control nor audio nor event)"); + } + + const size_t sym_len = strlen(symbol_str); + host->longest_sym = (sym_len > host->longest_sym) ? sym_len : host->longest_sym; +} + +void +jalv_create_ports(Jalv* jalv) +{ + jalv->num_ports = lilv_plugin_get_num_ports(jalv->plugin); + jalv->ports = calloc((size_t)jalv->num_ports, sizeof(struct Port)); + float* default_values = calloc(lilv_plugin_get_num_ports(jalv->plugin), + sizeof(float)); + lilv_plugin_get_port_ranges_float(jalv->plugin, NULL, NULL, default_values); + + for (uint32_t i = 0; i < jalv->num_ports; ++i) { + create_port(jalv, i, default_values[i]); + } + + free(default_values); +} + +struct Port* +jalv_port_by_symbol(Jalv* jalv, const char* sym) +{ + for (uint32_t i = 0; i < jalv->num_ports; ++i) { + struct Port* const port = &jalv->ports[i]; + const LilvNode* port_sym = lilv_port_get_symbol(jalv->plugin, + port->lilv_port); + + if (!strcmp(lilv_node_as_string(port_sym), sym)) { + return port; + } + } + + return NULL; +} + +/** + Expose a port to Jack (if applicable) and connect it to its buffer. +*/ +void +activate_port(Jalv* host, + uint32_t port_index) +{ + struct Port* const port = &host->ports[port_index]; + + /* Get the port symbol for console printing */ + const LilvNode* symbol = lilv_port_get_symbol(host->plugin, port->lilv_port); + const char* symbol_str = lilv_node_as_string(symbol); + + /* Connect unsupported ports to NULL (known to be optional by this point) */ + if (port->flow == FLOW_UNKNOWN || port->type == TYPE_UNKNOWN) { lilv_instance_connect_port(host->instance, port_index, NULL); return; - } else { - die("Mandatory port has unknown type (neither control nor audio nor event)"); } + /* Build Jack flags for port */ + enum JackPortFlags jack_flags = (port->flow == FLOW_INPUT) + ? JackPortIsInput + : JackPortIsOutput; + /* Connect the port based on its type */ switch (port->type) { - case CONTROL: + case TYPE_CONTROL: + printf("%-*s = %f\n", host->longest_sym, symbol_str, + host->ports[port_index].control); lilv_instance_connect_port(host->instance, port_index, &port->control); break; - case AUDIO: + case TYPE_AUDIO: port->jack_port = jack_port_register( host->jack_client, symbol_str, JACK_DEFAULT_AUDIO_TYPE, jack_flags, 0); break; - case EVENT: + case TYPE_EVENT: port->jack_port = jack_port_register( host->jack_client, symbol_str, JACK_DEFAULT_MIDI_TYPE, jack_flags, 0); port->ev_buffer = lv2_event_buffer_new(MIDI_BUFFER_SIZE, LV2_EVENT_AUDIO_STAMP); @@ -184,19 +216,19 @@ jack_process_cb(jack_nframes_t nframes, void* data) if (!host->ports[p].jack_port) continue; - if (host->ports[p].type == AUDIO) { + if (host->ports[p].type == TYPE_AUDIO) { /* Connect plugin port directly to Jack port buffer. */ lilv_instance_connect_port( host->instance, p, jack_port_get_buffer(host->ports[p].jack_port, nframes)); - } else if (host->ports[p].type == EVENT) { + } else if (host->ports[p].type == TYPE_EVENT) { /* Clear Jack event port buffer. */ lv2_event_buffer_reset(host->ports[p].ev_buffer, LV2_EVENT_AUDIO_STAMP, (uint8_t*)(host->ports[p].ev_buffer + 1)); - if (host->ports[p].is_input) { + if (host->ports[p].flow == FLOW_INPUT) { void* buf = jack_port_get_buffer(host->ports[p].jack_port, nframes); @@ -239,8 +271,8 @@ jack_process_cb(jack_nframes_t nframes, void* data) /* Deliver MIDI output and UI events */ for (uint32_t p = 0; p < host->num_ports; ++p) { if (host->ports[p].jack_port - && !host->ports[p].is_input - && host->ports[p].type == EVENT) { + && !host->ports[p].flow == FLOW_INPUT + && host->ports[p].type == TYPE_EVENT) { void* buf = jack_port_get_buffer(host->ports[p].jack_port, nframes); @@ -257,8 +289,8 @@ jack_process_cb(jack_nframes_t nframes, void* data) lv2_event_increment(&iter); } } else if (send_ui_updates - && !host->ports[p].is_input - && host->ports[p].type == CONTROL) { + && !host->ports[p].flow == FLOW_INPUT + && host->ports[p].type == TYPE_CONTROL) { const ControlChange ev = { p, host->ports[p].control }; jack_ringbuffer_write(host->plugin_events, (const char*)&ev, sizeof(ev)); } @@ -279,7 +311,6 @@ jack_session_cb(jack_session_event_t* event, void* arg) event->client_uuid); event->command_line = strdup(cmd); - jack_session_reply(host->jack_client, event); switch (event->type) { case JackSessionSave: @@ -292,6 +323,7 @@ jack_session_cb(jack_session_event_t* event, void* arg) break; } + jack_session_reply(host->jack_client, event); jack_session_event_free(event); } #endif /* JALV_JACK_SESSION */ @@ -336,15 +368,16 @@ signal_handler(int ignored) int main(int argc, char** argv) { - jalv_init(&argc, &argv); - Jalv host; - host.jack_client = NULL; - host.num_ports = 0; - host.ports = NULL; - host.ui_events = NULL; - host.plugin_events = NULL; - host.event_delta_t = 0; + memset(&host, '\0', sizeof(Jalv)); + + if (jalv_init(&argc, &argv, &host.opts)) { + return EXIT_FAILURE; + } + + if (host.opts.uuid) { + printf("UUID: %s\n", host.opts.uuid); + } host.symap = symap_new(); uri_map.callback_data = &host; @@ -374,31 +407,27 @@ main(int argc, char** argv) host.optional = lilv_new_uri(world, LILV_NS_LV2 "connectionOptional"); -#ifdef JALV_JACK_SESSION - if (argc != 2 && argc != 3) { - fprintf(stderr, "Usage: %s PLUGIN_URI [JACK_UUID]\n", argv[0]); -#else - if (argc != 2) { - fprintf(stderr, "Usage: %s PLUGIN_URI\n", argv[0]); -#endif - lilv_world_free(world); - return EXIT_FAILURE; - } - - const char* const plugin_uri_str = argv[1]; - - printf("Plugin: %s\n", plugin_uri_str); + if (host.opts.load) { + jalv_restore(&host, host.opts.load); + } else { + const char* const plugin_uri_str = argv[1]; + + /* Get the plugin */ + LilvNode* plugin_uri = lilv_new_uri(world, plugin_uri_str); + host.plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); + lilv_node_free(plugin_uri); + if (!host.plugin) { + fprintf(stderr, "Failed to find plugin %s\n", plugin_uri_str); + lilv_world_free(world); + return EXIT_FAILURE; + } - /* Get the plugin */ - LilvNode* plugin_uri = lilv_new_uri(world, plugin_uri_str); - host.plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); - lilv_node_free(plugin_uri); - if (!host.plugin) { - fprintf(stderr, "Failed to find plugin %s.\n", plugin_uri_str); - lilv_world_free(world); - return EXIT_FAILURE; + jalv_create_ports(&host); } + printf("Plugin: %s\n", + lilv_node_as_string(lilv_plugin_get_uri(host.plugin))); + /* Get a plugin UI */ LilvNode* native_ui_type = jalv_native_ui_type(&host); const LilvNode* ui_type = NULL; @@ -446,10 +475,9 @@ main(int argc, char** argv) /* Connect to JACK */ printf("JACK Name: %s\n\n", jack_name); #ifdef JALV_JACK_SESSION - const char* const jack_uuid_str = (argc > 2) ? argv[2] : NULL; - if (jack_uuid_str) { + if (host.opts.uuid) { host.jack_client = jack_client_open(jack_name, JackSessionID, NULL, - jack_uuid_str); + host.opts.uuid); } #endif @@ -469,6 +497,11 @@ main(int argc, char** argv) if (!host.instance) die("Failed to instantiate plugin.\n"); + /* Apply restored state to plugin instance (if applicable) */ + if (host.opts.load) { + jalv_restore_instance(&host, host.opts.load); + } + /* Set instance for instance-access extension */ instance_feature.data = lilv_instance_get_handle(host.instance); @@ -478,20 +511,15 @@ main(int argc, char** argv) jack_set_session_callback(host.jack_client, &jack_session_cb, (void*)(&host)); #endif - /* Create ports */ - host.num_ports = lilv_plugin_get_num_ports(host.plugin); - host.ports = calloc((size_t)host.num_ports, sizeof(struct Port)); - float* default_values = calloc(lilv_plugin_get_num_ports(host.plugin), - sizeof(float)); - lilv_plugin_get_port_ranges_float(host.plugin, NULL, NULL, default_values); - - for (uint32_t i = 0; i < host.num_ports; ++i) - create_port(&host, i, default_values[i]); - - free(default_values); + /* Create Jack ports and connect plugin ports to buffers */ + for (uint32_t i = 0; i < host.num_ports; ++i) { + activate_port(&host, i); + } - /* Activate plugin and JACK */ + /* Activate plugin */ lilv_instance_activate(host.instance); + + /* Activate Jack */ jack_activate(host.jack_client); host.sample_rate = jack_get_sample_rate(host.jack_client); @@ -510,6 +538,15 @@ main(int argc, char** argv) lilv_uri_to_path(lilv_node_as_uri(lilv_ui_get_bundle_uri(host.ui))), lilv_uri_to_path(lilv_node_as_uri(lilv_ui_get_binary_uri(host.ui))), features); + + /* Set initial control values for UI */ + for (uint32_t i = 0; i < host.num_ports; ++i) { + if (host.ports[i].type == TYPE_CONTROL) { + suil_instance_port_event(host.ui_instance, i, + sizeof(float), 0, + &host.ports[i].control); + } + } } /* Run UI (or prompt at console) */ diff --git a/src/jalv_console.c b/src/jalv_console.c index 48c3ed3..6c0addd 100644 --- a/src/jalv_console.c +++ b/src/jalv_console.c @@ -14,14 +14,53 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#define _XOPEN_SOURCE 500 + +#include +#include #include #include "jalv-config.h" #include "jalv_internal.h" -void -jalv_init(int* argc, char*** argv) +static int +print_usage(const char* name, bool error) { + FILE* const os = error ? stderr : stdout; + fprintf(os, "Usage: %s [OPTION...] PLUGIN_URI\n", name); + fprintf(os, "Run an LV2 plugin as a Jack application.\n"); + fprintf(os, " -h Display this help and exit\n"); + fprintf(os, " -u UUID UUID for Jack session restoration\n"); + fprintf(os, " -l DIR Load state from save directory\n"); + return error ? 1 : 0; +} + +int +jalv_init(int* argc, char*** argv, JalvOptions* opts) +{ + int a = 1; + for (; a < *argc && (*argv)[a][0] == '-'; ++a) { + if ((*argv)[a][1] == 'h') { + return print_usage((*argv)[0], false); + } else if ((*argv)[a][1] == 'u') { + if (++a == *argc) { + fprintf(stderr, "Missing argument for -u\n"); + return 1; + } + opts->uuid = strdup((*argv)[a]); + } else if ((*argv)[a][1] == 'l') { + if (++a == *argc) { + fprintf(stderr, "Missing argument for -l\n"); + return 1; + } + opts->load = strdup((*argv)[a]); + } else { + fprintf(stderr, "Unknown option %s\n", (*argv)[a]); + return print_usage((*argv)[0], true); + } + } + + return 0; } LilvNode* diff --git a/src/jalv_gtk2.c b/src/jalv_gtk2.c index 59e9772..7d5bd5a 100644 --- a/src/jalv_gtk2.c +++ b/src/jalv_gtk2.c @@ -24,14 +24,26 @@ static void destroy(GtkWidget* widget, gtk_main_quit(); } -void -jalv_init(int* argc, char*** argv) +int +jalv_init(int* argc, char*** argv, JalvOptions* opts) { + GOptionEntry entries[] = { + { "uuid", 'u', 0, G_OPTION_ARG_STRING, &opts->uuid, + "UUID for Jack session restoration", "UUID" }, + { "load", 'l', 0, G_OPTION_ARG_STRING, &opts->load, + "Load state from save directory", "DIR" }, + { 0,0,0,0,0,0,0 } }; GError* error = NULL; - gtk_init_with_args( + const int err = gtk_init_with_args( argc, argv, - "PLUGIN_URI [JACK_UUID] - Run an LV2 plugin as a Jack application", - NULL, NULL, &error); + "PLUGIN_URI - Run an LV2 plugin as a Jack application", + entries, NULL, &error); + + if (!err) { + fprintf(stderr, "%s\n", error->message); + } + + return !err; } LilvNode* diff --git a/src/jalv_internal.h b/src/jalv_internal.h index 8c4743b..c98da05 100644 --- a/src/jalv_internal.h +++ b/src/jalv_internal.h @@ -22,6 +22,10 @@ #include #include +#include "lv2/lv2plug.in/ns/ext/event/event.h" + +#include "serd/serd.h" + #include "lilv/lilv.h" #include "suil/suil.h" @@ -34,8 +38,47 @@ extern "C" { #define JALV_UI_UPDATE_HZ 15 +enum PortFlow { + FLOW_UNKNOWN, + FLOW_INPUT, + FLOW_OUTPUT +}; + +enum PortType { + TYPE_UNKNOWN, + TYPE_CONTROL, + TYPE_AUDIO, + TYPE_EVENT +}; + +struct Port { + const LilvPort* lilv_port; + enum PortType type; + enum PortFlow flow; + jack_port_t* jack_port; /**< For audio/MIDI ports, otherwise NULL */ + float control; /**< For control ports, otherwise 0.0f */ + LV2_Event_Buffer* ev_buffer; /**< For MIDI ports, otherwise NULL */ +}; + +struct Property { + uint32_t key; + SerdNode value; + SerdNode datatype; +}; + +typedef struct { + char* uuid; + char* load; +} JalvOptions; + typedef struct { + JalvOptions opts; /**< Command-line options */ LilvWorld* world; /**< Lilv World */ + SerdReader* reader; /**< RDF reader (for persistence) */ + SerdWriter* writer; /**< RDF writer (for persistence) */ + struct Property* props; /**< Restored state properties */ + SerdNode state_node; /**< Instance state node (for persistence) */ + SerdNode last_sym; /**< Last port symbol encountered in state */ Symap* symap; /**< Symbol (URI) map */ jack_client_t* jack_client; /**< Jack client */ jack_ringbuffer_t* ui_events; /**< Port events from UI */ @@ -47,6 +90,8 @@ typedef struct { SuilInstance* ui_instance; /**< Plugin UI instance (shared library) */ struct Port* ports; /**< Port array of size num_ports */ uint32_t num_ports; /**< Size of the two following arrays: */ + uint32_t num_props; /**< Number of properties */ + uint32_t longest_sym; /**< Longest port symbol */ jack_nframes_t sample_rate; /**< Sample rate */ jack_nframes_t event_delta_t; /**< Frames since last update sent to UI */ LilvNode* input_class; /**< Input port class (URI) */ @@ -57,10 +102,17 @@ typedef struct { LilvNode* midi_class; /**< MIDI event class (URI) */ LilvNode* optional; /**< lv2:connectionOptional port property */ uint32_t midi_event_id; /**< MIDI event class ID */ + bool in_state; /**< True iff reading instance state */ } Jalv; +int +jalv_init(int* argc, char*** argv, JalvOptions* opts); + void -jalv_init(int* argc, char*** argv); +jalv_create_ports(Jalv* jalv); + +struct Port* +jalv_port_by_symbol(Jalv* jalv, const char* sym); LilvNode* jalv_native_ui_type(Jalv* jalv); @@ -78,6 +130,9 @@ jalv_save(Jalv* jalv, const char* dir); void jalv_restore(Jalv* jalv, const char* dir); +void +jalv_restore_instance(Jalv* jalv, const char* dir); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/jalv_qt4.cpp b/src/jalv_qt4.cpp index eac67ec..7673165 100644 --- a/src/jalv_qt4.cpp +++ b/src/jalv_qt4.cpp @@ -25,10 +25,11 @@ static QApplication* app = NULL; extern "C" { -void -jalv_init(int* argc, char*** argv) +int +jalv_init(int* argc, char*** argv, JalvOptions* opts) { app = new QApplication(*argc, *argv, true); + return 0; } LilvNode* diff --git a/src/persist.c b/src/persist.c index b893923..b1f2272 100644 --- a/src/persist.c +++ b/src/persist.c @@ -14,12 +14,34 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#define _XOPEN_SOURCE 500 + +#include +#include #include +#include +#include #include "lv2/lv2plug.in/ns/ext/persist/persist.h" #include "jalv_internal.h" +#define NS_JALV (const uint8_t*)"http://drobilla.net/ns/jalv#" +#define NS_LV2 (const uint8_t*)"http://lv2plug.in/ns/lv2core#" +#define NS_XSD (const uint8_t*)"http://www.w3.org/2001/XMLSchema#" +#define NS_ATOM (const uint8_t*)"http://lv2plug.in/ns/ext/atom#" +#define NS_PERSIST (const uint8_t*)"http://lv2plug.in/ns/ext/persist#" + +#define USTR(s) ((const uint8_t*)s) + +static int +property_cmp(const void* a, const void* b) +{ + const struct Property* pa = (const struct Property*)a; + const struct Property* pb = (const struct Property*)b; + return pa->key - pb->key; +} + static int store_callback(void* host_data, uint32_t key, @@ -28,15 +50,27 @@ store_callback(void* host_data, uint32_t type, uint32_t flags) { - Jalv* jalv = (Jalv*)host_data; - const char* key_uri = symap_unmap(jalv->symap, key); - if (key_uri) { - printf("STORE %s\n", key_uri); - } else { - fprintf(stderr, "error: Failed to unmap URI ID %u\n", key); + Jalv* jalv = (Jalv*)host_data; + const char* key_uri = symap_unmap(jalv->symap, key); + const char* type_uri = symap_unmap(jalv->symap, type); + if (strcmp(type_uri, (const char*)(NS_ATOM "String"))) { + fprintf(stderr, "error: Unsupported (not atom:String) value stored\n"); return 1; } - return 0; + + if (key_uri && type_uri && value) { + const SerdNode p = serd_node_from_string(SERD_URI, USTR(key_uri)); + const SerdNode o = serd_node_from_string(SERD_LITERAL, USTR(value)); + const SerdNode t = serd_node_from_string(SERD_URI, USTR(type_uri)); + + serd_writer_write_statement(jalv->writer, SERD_ANON_CONT, NULL, + &jalv->state_node, &p, &o, &t, NULL); + + return 0; + } + + fprintf(stderr, "error: Failed to store property (key %d)\n", key); + return 1; } static const void* @@ -46,31 +80,246 @@ retrieve_callback(void* host_data, uint32_t* type, uint32_t* flags) { - //Jalv* jalv = (Jalv*)host_data; - printf("RETRIEVE %d\n", key); - return 0; + Jalv* jalv = (Jalv*)host_data; + struct Property search_key = { key, SERD_NODE_NULL, SERD_NODE_NULL }; + struct Property* prop = (struct Property*)bsearch( + &search_key, jalv->props, jalv->num_props, + sizeof(struct Property), property_cmp); + + if (prop) { + *size = prop->value.n_bytes; + *type = symap_map(jalv->symap, (const char*)(NS_ATOM "String")); + *flags = 0; + return prop->value.buf; + } + + return NULL; + } +static size_t +file_sink(const void* buf, size_t len, void* stream) +{ + FILE* file = (FILE*)stream; + return fwrite(buf, 1, len, file); +} void jalv_save(Jalv* jalv, const char* dir) { - printf("SAVE %s\n", dir); + assert(!jalv->writer); + + // Set numeric locale to C so snprintf %f is Turtle compatible + char* locale = strdup(setlocale(LC_NUMERIC, NULL)); + setlocale(LC_NUMERIC, "C"); + + const size_t dir_len = strlen(dir); + const char* const filename = "state.ttl"; + const size_t path_len = dir_len + strlen(filename); + char* const path = (char*)malloc(path_len + 1); + + snprintf(path, path_len + 1, "%s%s", dir, filename); + FILE* out_fd = fopen(path, "w"); + + SerdNode jalv_name = serd_node_from_string(SERD_LITERAL, USTR("jalv")); + SerdNode jalv_prefix = serd_node_from_string(SERD_URI, NS_JALV); + SerdNode lv2_name = serd_node_from_string(SERD_LITERAL, USTR("lv2")); + SerdNode lv2_prefix = serd_node_from_string(SERD_URI, NS_LV2); + SerdNode persist_name = serd_node_from_string(SERD_LITERAL, USTR("persist")); + SerdNode persist_prefix = serd_node_from_string(SERD_URI, NS_PERSIST); + SerdNode atom_name = serd_node_from_string(SERD_LITERAL, USTR("atom")); + SerdNode atom_prefix = serd_node_from_string(SERD_URI, NS_ATOM); + SerdNode jalv_plugin = serd_node_from_string(SERD_URI, NS_JALV "plugin"); + SerdNode jalv_value = serd_node_from_string(SERD_URI, (NS_JALV "value")); + SerdNode lv2_symbol = serd_node_from_string(SERD_URI, (NS_LV2 "symbol")); + SerdNode xsd_decimal = serd_node_from_string(SERD_URI, (NS_XSD "decimal")); + SerdNode jalv_port = serd_node_from_string(SERD_URI, (NS_JALV "port")); + + SerdNode persist_instanceState = serd_node_from_string( + SERD_URI, (NS_PERSIST "instanceState")); + + SerdNode plugin_uri = serd_node_from_string(SERD_URI, USTR(lilv_node_as_uri( + lilv_plugin_get_uri(jalv->plugin)))); + + SerdEnv* env = serd_env_new(NULL); + + SerdNode subject = serd_node_from_string(SERD_URI, USTR("")); + + jalv->writer = serd_writer_new( + SERD_TURTLE, + SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED, + env, + &SERD_URI_NULL, + file_sink, + out_fd); + + serd_writer_set_prefix(jalv->writer, &atom_name, &atom_prefix); + serd_writer_set_prefix(jalv->writer, &jalv_name, &jalv_prefix); + serd_writer_set_prefix(jalv->writer, &lv2_name, &lv2_prefix); + serd_writer_set_prefix(jalv->writer, &persist_name, &persist_prefix); + + // <> jalv:plugin + serd_writer_write_statement(jalv->writer, 0, NULL, + &subject, + &jalv_plugin, + &plugin_uri, NULL, NULL); + + for (uint32_t i = 0; i < jalv->num_ports; ++i) { + struct Port* port = &jalv->ports[i]; + if (port->flow != FLOW_INPUT || port->type != TYPE_CONTROL) { + continue; + } + + const uint8_t* sym = (const uint8_t*)lilv_node_as_string( + lilv_port_get_symbol(jalv->plugin, port->lilv_port)); + + const SerdNode sym_node = serd_node_from_string(SERD_LITERAL, sym); + const SerdNode blank = serd_node_from_string(SERD_BLANK, sym); + + // <> jalv:port [] + serd_writer_write_statement(jalv->writer, SERD_ANON_O_BEGIN, NULL, + &subject, + &jalv_port, + &blank, NULL, NULL); + + char value_str[128]; + snprintf(value_str, sizeof(value_str), "%f", port->control); + + SerdNode value = serd_node_from_string(SERD_LITERAL, USTR(value_str)); + + // [] lv2:symbol "example" + serd_writer_write_statement(jalv->writer, SERD_ANON_CONT, NULL, + &blank, &lv2_symbol, &sym_node, + NULL, NULL); + + // [] jalv:value 1.0 + serd_writer_write_statement(jalv->writer, SERD_ANON_CONT, NULL, + &blank, &jalv_value, &value, + &xsd_decimal, NULL); + + serd_writer_end_anon(jalv->writer, &blank); + } + + assert(jalv->symap); const LV2_Persist* persist = (const LV2_Persist*) lilv_instance_get_extension_data(jalv->instance, "http://lv2plug.in/ns/ext/persist"); if (persist) { + // [] persist:instanceState [ + jalv->state_node = serd_node_from_string(SERD_BLANK, USTR("state")); + serd_writer_write_statement(jalv->writer, SERD_ANON_O_BEGIN, NULL, + &subject, + &persist_instanceState, + &jalv->state_node, NULL, NULL); + + // Write properties to state blank node persist->save(lilv_instance_get_handle(jalv->instance), store_callback, jalv); + + // ] + serd_writer_end_anon(jalv->writer, &jalv->state_node); + jalv->state_node = SERD_NODE_NULL; + } + + // Close state file and clean up Serd + serd_writer_free(jalv->writer); + jalv->writer = NULL; + fclose(out_fd); + serd_env_free(env); + + // Reset numeric locale to original value + setlocale(LC_NUMERIC, locale); + free(locale); + + free(path); +} + +static SerdStatus +on_statement(void* handle, + SerdStatementFlags flags, + const SerdNode* graph, + const SerdNode* subject, + const SerdNode* predicate, + const SerdNode* object, + const SerdNode* object_datatype, + const SerdNode* object_lang) +{ + Jalv* jalv = (Jalv*)handle; + if (jalv->in_state) { + jalv->props = (struct Property*)realloc( + jalv->props, + sizeof(struct Property) * (++jalv->num_props)); + struct Property* prop = &jalv->props[jalv->num_props - 1]; + prop->key = symap_map(jalv->symap, (const char*)predicate->buf); + prop->value = serd_node_copy(object); + prop->datatype = serd_node_copy(object_datatype); + } else if (!strcmp((const char*)predicate->buf, "jalv:plugin")) { + const LilvPlugins* plugins = lilv_world_get_all_plugins(jalv->world); + LilvNode* plugin_uri = lilv_new_uri(jalv->world, + (const char*)object->buf); + jalv->plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); + lilv_node_free(plugin_uri); + + jalv->num_ports = lilv_plugin_get_num_ports(jalv->plugin); + jalv->ports = calloc((size_t)jalv->num_ports, sizeof(struct Port)); + + jalv_create_ports(jalv); + } else if (!strcmp((const char*)predicate->buf, "lv2:symbol")) { + serd_node_free(&jalv->last_sym); + jalv->last_sym = serd_node_copy(object); + } else if (!strcmp((const char*)predicate->buf, "jalv:value")) { + const char* sym = (const char*)jalv->last_sym.buf; + struct Port* port = jalv_port_by_symbol(jalv, sym); + if (port) { + port->control = atof((const char*)object->buf); // FIXME: Check type + } else { + fprintf(stderr, "error: Failed to find port `%s'\n", sym); + } + } else if (!strcmp((const char*)predicate->buf, "persist:instanceState")) { + jalv->in_state = true; } + + return SERD_SUCCESS; } void jalv_restore(Jalv* jalv, const char* dir) { - printf("RESTORE %s\n", dir); + jalv->reader = serd_reader_new( + SERD_TURTLE, + jalv, NULL, + NULL, + NULL, + on_statement, + NULL); + + const size_t dir_len = strlen(dir); + const size_t state_uri_len = strlen("file:///state.ttl") + dir_len + 1; + char* state_uri = (char*)malloc(state_uri_len); + snprintf(state_uri, state_uri_len, "file://%s/state.ttl", dir); + + SerdStatus st = serd_reader_read_file(jalv->reader, USTR(state_uri)); + serd_node_free(&jalv->last_sym); + if (st) { + fprintf(stderr, "Error reading state from %s (%s)\n", + state_uri, serd_strerror(st)); + return; + } + + serd_reader_free(jalv->reader); + jalv->reader = NULL; + jalv->in_state = false; + + if (jalv->props) { + qsort(jalv->props, jalv->num_props, sizeof(struct Property), property_cmp); + } +} + +void +jalv_restore_instance(Jalv* jalv, const char* dir) +{ const LV2_Persist* persist = (const LV2_Persist*) lilv_instance_get_extension_data(jalv->instance, "http://lv2plug.in/ns/ext/persist"); @@ -79,4 +328,5 @@ jalv_restore(Jalv* jalv, const char* dir) retrieve_callback, jalv); } + } -- cgit v1.2.1