aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/jalv.c231
-rw-r--r--src/jalv_console.c43
-rw-r--r--src/jalv_gtk2.c22
-rw-r--r--src/jalv_internal.h57
-rw-r--r--src/jalv_qt4.cpp5
-rw-r--r--src/persist.c274
-rw-r--r--wscript6
7 files changed, 518 insertions, 120 deletions
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 <string.h>
+#include <stdbool.h>
#include <stdio.h>
#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 <jack/jack.h>
#include <jack/ringbuffer.h>
+#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 <assert.h>
+#include <locale.h>
#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
#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 <http://example.org/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);
}
+
}
diff --git a/wscript b/wscript
index d9a7ec0..3d066cf 100644
--- a/wscript
+++ b/wscript
@@ -22,6 +22,7 @@ out = 'build'
def options(opt):
autowaf.set_options(opt)
opt.load('compiler_c')
+ opt.load('compiler_cxx')
opt.add_option('--no-jack-session', action='store_true', default=False,
dest='no_jack_session',
help="Do not build JACK session support")
@@ -31,10 +32,13 @@ def configure(conf):
autowaf.configure(conf)
autowaf.display_header('Jalv Configuration')
conf.load('compiler_c')
+ conf.load('compiler_cxx')
autowaf.check_pkg(conf, 'lv2core', uselib_store='LV2CORE', mandatory=True)
autowaf.check_pkg(conf, 'lilv-0', uselib_store='LILV',
atleast_version='0.4.0', mandatory=True)
+ autowaf.check_pkg(conf, 'serd-0', uselib_store='SERD',
+ atleast_version='0.4.5', mandatory=True)
autowaf.check_pkg(conf, 'suil-0', uselib_store='SUIL',
atleast_version='0.4.0', mandatory=True)
autowaf.check_pkg(conf, 'jack', uselib_store='JACK',
@@ -65,7 +69,7 @@ def configure(conf):
print('')
def build(bld):
- libs = 'LILV SUIL JACK'
+ libs = 'LILV SUIL JACK SERD'
source = 'src/jalv.c src/symap.c src/persist.c'