summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2011-12-30 08:23:19 +0000
committerDavid Robillard <d@drobilla.net>2011-12-30 08:23:19 +0000
commit8708ccb858ddbf5d521c4755e137bd04544a6ae5 (patch)
tree2da5030db36592ff19b13f8ff802a5f5c3bfb3b8
parentc5c4a6e935eecc2d2dac7bb9cccd36057b8dc123 (diff)
downloadlilv-8708ccb858ddbf5d521c4755e137bd04544a6ae5.tar.gz
lilv-8708ccb858ddbf5d521c4755e137bd04544a6ae5.tar.bz2
lilv-8708ccb858ddbf5d521c4755e137bd04544a6ae5.zip
Support arbitrary binary data in plugin state via base64 encoding.
Unit testing for plugin instantiation and state. Build without LV2 state available. Support URID values in plugin state nicely. Fix various holes in state implementation exposed by tests. git-svn-id: http://svn.drobilla.net/lad/trunk/lilv@3908 a436a847-0d15-0410-975c-d299462d15a1
-rw-r--r--lilv/lilv.h7
-rw-r--r--src/instance.c2
-rw-r--r--src/lilv_internal.h8
-rw-r--r--src/node.c26
-rw-r--r--src/state.c184
-rw-r--r--src/world.c2
-rw-r--r--test/lilv_test.c154
-rw-r--r--test/manifest.ttl7
-rw-r--r--test/test_plugin.c235
-rw-r--r--test/test_plugin.ttl40
-rw-r--r--wscript37
11 files changed, 650 insertions, 52 deletions
diff --git a/lilv/lilv.h b/lilv/lilv.h
index 7d04cda..c664855 100644
--- a/lilv/lilv.h
+++ b/lilv/lilv.h
@@ -1107,6 +1107,13 @@ void
lilv_state_free(LilvState* state);
/**
+ Return true iff @c a is equivalent to @c b.
+*/
+LILV_API
+bool
+lilv_state_equals(const LilvState* a, const LilvState* b);
+
+/**
Get the URI of the plugin @c state applies to.
*/
LILV_API
diff --git a/src/instance.c b/src/instance.c
index e9d4591..4823f59 100644
--- a/src/instance.c
+++ b/src/instance.c
@@ -27,6 +27,8 @@ lilv_plugin_instantiate(const LilvPlugin* plugin,
double sample_rate,
const LV2_Feature*const* features)
{
+ lilv_plugin_load_if_necessary(plugin);
+
LilvInstance* result = NULL;
const LV2_Feature** local_features = NULL;
diff --git a/src/lilv_internal.h b/src/lilv_internal.h
index 75e21a2..9f19c1c 100644
--- a/src/lilv_internal.h
+++ b/src/lilv_internal.h
@@ -150,6 +150,7 @@ struct LilvWorldImpl {
SordNode* rdfs_label_node;
SordNode* rdfs_seealso_node;
SordNode* rdfs_subclassof_node;
+ SordNode* xsd_base64Binary_node;
SordNode* xsd_boolean_node;
SordNode* xsd_decimal_node;
SordNode* xsd_double_node;
@@ -168,7 +169,8 @@ typedef enum {
LILV_VALUE_INT,
LILV_VALUE_FLOAT,
LILV_VALUE_BOOL,
- LILV_VALUE_BLANK
+ LILV_VALUE_BLANK,
+ LILV_VALUE_BLOB
} LilvNodeType;
struct LilvNodeImpl {
@@ -179,6 +181,10 @@ struct LilvNodeImpl {
float float_val;
bool bool_val;
SordNode* uri_val;
+ struct {
+ void* buf;
+ size_t size;
+ } blob_val;
} val;
LilvNodeType type;
};
diff --git a/src/node.c b/src/node.c
index 3dc4ace..debf1be 100644
--- a/src/node.c
+++ b/src/node.c
@@ -21,7 +21,7 @@
#include "lilv_internal.h"
static void
-lilv_node_set_numerics_from_string(LilvNode* val)
+lilv_node_set_numerics_from_string(LilvNode* val, size_t len)
{
char* endptr;
@@ -39,6 +39,9 @@ lilv_node_set_numerics_from_string(LilvNode* val)
case LILV_VALUE_BOOL:
val->val.bool_val = (!strcmp(val->str_val, "true"));
break;
+ case LILV_VALUE_BLOB:
+ val->val.blob_val.buf = serd_base64_decode(
+ (const uint8_t*)val->str_val, len, &val->val.blob_val.size);
}
}
@@ -66,6 +69,7 @@ lilv_node_new(LilvWorld* world, LilvNodeType type, const char* str)
case LILV_VALUE_INT:
case LILV_VALUE_FLOAT:
case LILV_VALUE_BOOL:
+ case LILV_VALUE_BLOB:
val->str_val = lilv_strdup(str);
break;
}
@@ -80,6 +84,7 @@ lilv_node_new_from_node(LilvWorld* world, const SordNode* node)
LilvNode* result = NULL;
SordNode* datatype_uri = NULL;
LilvNodeType type = LILV_VALUE_STRING;
+ size_t len = 0;
switch (sord_node_get_type(node)) {
case SORD_URI:
@@ -106,19 +111,15 @@ lilv_node_new_from_node(LilvWorld* world, const SordNode* node)
type = LILV_VALUE_FLOAT;
else if (sord_node_equals(datatype_uri, world->xsd_integer_node))
type = LILV_VALUE_INT;
+ else if (sord_node_equals(datatype_uri, world->xsd_base64Binary_node))
+ type = LILV_VALUE_BLOB;
else
LILV_ERRORF("Unknown datatype `%s'\n",
sord_node_get_string(datatype_uri));
}
- result = lilv_node_new(world, type, (const char*)sord_node_get_string(node));
- switch (result->type) {
- case LILV_VALUE_INT:
- case LILV_VALUE_FLOAT:
- case LILV_VALUE_BOOL:
- lilv_node_set_numerics_from_string(result);
- default:
- break;
- }
+ result = lilv_node_new(
+ world, type, (const char*)sord_node_get_string_counted(node, &len));
+ lilv_node_set_numerics_from_string(result, len);
break;
default:
assert(false);
@@ -237,6 +238,10 @@ lilv_node_equals(const LilvNode* value, const LilvNode* other)
return (value->val.float_val == other->val.float_val);
case LILV_VALUE_BOOL:
return (value->val.bool_val == other->val.bool_val);
+ case LILV_VALUE_BLOB:
+ return (value->val.blob_val.size == other->val.blob_val.size)
+ && !memcmp(value->val.blob_val.buf, other->val.blob_val.buf,
+ value->val.blob_val.size);
}
return false; /* shouldn't get here */
@@ -263,6 +268,7 @@ lilv_node_get_turtle_token(const LilvNode* value)
break;
case LILV_VALUE_STRING:
case LILV_VALUE_BOOL:
+ case LILV_VALUE_BLOB:
result = lilv_strdup(value->str_val);
break;
case LILV_VALUE_INT:
diff --git a/src/state.c b/src/state.c
index aae3fab..f50557f 100644
--- a/src/state.c
+++ b/src/state.c
@@ -73,6 +73,14 @@ property_cmp(const void* a, const void* b)
return pa->key - pb->key;
}
+static int
+value_cmp(const void* a, const void* b)
+{
+ const PortValue* pa = (const PortValue*)a;
+ const PortValue* pb = (const PortValue*)b;
+ return strcmp(pa->symbol, pb->symbol);
+}
+
LILV_API
const LilvNode*
lilv_state_get_plugin_uri(const LilvState* state)
@@ -131,7 +139,7 @@ store_callback(void* handle,
state->props = realloc(state->props,
(++state->num_props) * sizeof(Property));
Property* const prop = &state->props[state->num_props - 1];
-
+
prop->value = malloc(size);
memcpy(prop->value, value, size);
@@ -183,10 +191,12 @@ lilv_state_new_from_instance(const LilvPlugin* plugin,
#endif // HAVE_LV2_STATE
qsort(state->props, state->num_props, sizeof(Property), property_cmp);
+ qsort(state->values, state->num_values, sizeof(PortValue), value_cmp);
return state;
}
+#ifdef HAVE_LV2_STATE
static const void*
retrieve_callback(void* handle,
uint32_t key,
@@ -208,6 +218,7 @@ retrieve_callback(void* handle,
}
return NULL;
}
+#endif // HAVE_LV2_STATE
LILV_API
void
@@ -261,9 +272,6 @@ property_from_node(LilvWorld* world,
prop->type = map->map(map->handle, NS_ATOM "URID");
prop->size = sizeof(uint32_t);
break;
- case LILV_VALUE_BLANK:
- // TODO: Hmm...
- break;
case LILV_VALUE_STRING:
prop->size = strlen(str) + 1;
prop->value = malloc(prop->size);
@@ -288,8 +296,11 @@ property_from_node(LilvWorld* world,
prop->type = map->map(map->handle, NS_ATOM "Float");
prop->size = sizeof(float);
break;
+ case LILV_VALUE_BLANK:
+ case LILV_VALUE_BLOB:
+ // TODO: Blank nodes in state
+ break;
}
- prop->flags = LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE;
}
static LilvState*
@@ -302,9 +313,9 @@ new_state_from_model(LilvWorld* world,
memset(state, '\0', sizeof(LilvState));
// Get the plugin URI this state applies to
- const SordQuad pat1 = {
+ const SordQuad upat = {
node, world->lv2_appliesTo_node, NULL, NULL };
- SordIter* i = sord_find(model, pat1);
+ SordIter* i = sord_find(model, upat);
if (i) {
state->plugin_uri = lilv_node_new_from_node(
world, lilv_match_object(i));
@@ -314,9 +325,19 @@ new_state_from_model(LilvWorld* world,
sord_node_get_string(node));
}
+ // Get the state label
+ const SordQuad lpat = {
+ node, world->rdfs_label_node, NULL, NULL };
+ i = sord_find(model, lpat);
+ if (i) {
+ state->label = lilv_strdup(
+ (const char*)sord_node_get_string(lilv_match_object(i)));
+ sord_iter_free(i);
+ }
+
// Get port values
- const SordQuad pat2 = { node, world->lv2_port_node, NULL, NULL };
- SordIter* ports = sord_find(model, pat2);
+ const SordQuad ppat = { node, world->lv2_port_node, NULL, NULL };
+ SordIter* ports = sord_find(model, ppat);
FOREACH_MATCH(ports) {
const SordNode* port = lilv_match_object(ports);
const SordNode* label = get_one(model, port, world->rdfs_label_node);
@@ -346,31 +367,53 @@ new_state_from_model(LilvWorld* world,
SordNode* statep = sord_new_uri(world->world, USTR(NS_STATE "state"));
const SordNode* state_node = get_one(model, node, statep);
if (state_node) {
- const SordQuad pat3 = { state_node, NULL, NULL };
- SordIter* props = sord_find(model, pat3);
+ const SordQuad spat = { state_node, NULL, NULL };
+ SordIter* props = sord_find(model, spat);
FOREACH_MATCH(props) {
- const SordNode* p = lilv_match_predicate(props);
- const SordNode* o = lilv_match_object(props);
- LilvNode* onode = lilv_node_new_from_node(world, o);
-
- Property prop = { NULL, 0, 0, 0, 0 };
+ const SordNode* p = lilv_match_predicate(props);
+ const SordNode* o = lilv_match_object(props);
+
+ uint32_t flags = 0;
+#ifdef HAVE_LV2_STATE
+ flags = LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE;
+#endif
+ Property prop = { NULL, 0, 0, 0, flags };
prop.key = map->map(map->handle,
(const char*)sord_node_get_string(p));
- property_from_node(world, map, onode, &prop);
+
+ if (sord_node_get_type(o) == SORD_BLANK) {
+ const SordNode* type = get_one(model, o, world->rdf_a_node);
+ const SordNode* value = get_one(model, o, world->rdf_value_node);
+ if (type && value) {
+ size_t len;
+ const uint8_t* b64 = sord_node_get_string_counted(value, &len);
+ prop.value = serd_base64_decode(b64, len, &prop.size);
+ prop.type = map->map(map->handle,
+ (const char*)sord_node_get_string(type));
+ } else {
+ LILV_ERRORF("Unable to parse blank node property <%p>\n",
+ sord_node_get_string(p));
+ }
+ } else {
+ LilvNode* onode = lilv_node_new_from_node(world, o);
+ property_from_node(world, map, onode, &prop);
+ lilv_node_free(onode);
+ }
+
if (prop.value) {
state->props = realloc(
state->props, (++state->num_props) * sizeof(Property));
state->props[state->num_props - 1] = prop;
}
- lilv_node_free(onode);
}
sord_iter_free(props);
}
sord_node_free(world->world, statep);
qsort(state->props, state->num_props, sizeof(Property), property_cmp);
-
+ qsort(state->values, state->num_values, sizeof(PortValue), value_cmp);
+
return state;
}
@@ -425,10 +468,14 @@ lilv_state_new_from_file(LilvWorld* world,
}
static LilvNode*
-node_from_property(LilvWorld* world, const char* type, void* value, size_t size)
+node_from_property(LilvWorld* world, LV2_URID_Unmap* unmap,
+ const char* type, void* value, size_t size)
{
if (!strcmp(type, NS_ATOM "String")) {
return lilv_new_string(world, (const char*)value);
+ } else if (!strcmp(type, NS_ATOM "URID")) {
+ const char* str = unmap->unmap(unmap->handle, *(uint32_t*)value);
+ return lilv_new_uri(world, str);
} else if (!strcmp(type, NS_ATOM "Int32")) {
if (size == sizeof(int32_t)) {
return lilv_new_int(world, *(int32_t*)value);
@@ -491,8 +538,8 @@ add_state_to_manifest(const LilvNode* plugin_uri,
{
FILE* fd = fopen((char*)manifest_path, "a");
if (!fd) {
- fprintf(stderr, "error: Failed to open %s (%s)\n",
- manifest_path, strerror(errno));
+ LILV_ERRORF("Failed to open %s (%s)\n",
+ manifest_path, strerror(errno));
return 4;
}
@@ -508,6 +555,7 @@ add_state_to_manifest(const LilvNode* plugin_uri,
SerdEnv* env = serd_env_new(NULL);
serd_env_set_prefix_from_strings(env, USTR("lv2"), USTR(LILV_NS_LV2));
serd_env_set_prefix_from_strings(env, USTR("pset"), USTR(NS_PSET));
+ serd_env_set_prefix_from_strings(env, USTR("rdf"), USTR(LILV_NS_RDF));
serd_env_set_prefix_from_strings(env, USTR("rdfs"), USTR(LILV_NS_RDFS));
#if defined(HAVE_LOCKF) && defined(HAVE_FILENO)
@@ -607,15 +655,14 @@ lilv_state_save(LilvWorld* world,
const char* const home = getenv("HOME");
if (!home) {
- fprintf(stderr, "error: $HOME is undefined\n");
+ LILV_ERROR("$HOME is undefined\n");
return 2;
}
// Create ~/.lv2/
char* const lv2dir = lilv_strjoin(home, "/.lv2/", NULL);
if (mkdir(lv2dir, 0755) && errno != EEXIST) {
- fprintf(stderr, "error: Unable to create %s (%s)\n",
- lv2dir, strerror(errno));
+ LILV_ERRORF("Unable to create %s (%s)\n", lv2dir, strerror(errno));
free(lv2dir);
return 3;
}
@@ -623,13 +670,12 @@ lilv_state_save(LilvWorld* world,
// Create ~/.lv2/presets.lv2/
char* const bundle = lilv_strjoin(lv2dir, "presets.lv2/", NULL);
if (mkdir(bundle, 0755) && errno != EEXIST) {
- fprintf(stderr, "error: Unable to create %s (%s)\n",
- lv2dir, strerror(errno));
+ LILV_ERRORF("Unable to create %s (%s)\n", lv2dir, strerror(errno));
free(lv2dir);
free(bundle);
return 4;
}
-
+
char* const filename = pathify(state->label);
default_path = lilv_strjoin(bundle, filename, ".ttl", NULL);
default_manifest_path = lilv_strjoin(bundle, "manifest.ttl", NULL);
@@ -648,8 +694,7 @@ lilv_state_save(LilvWorld* world,
FILE* fd = fopen(path, "w");
if (!fd) {
- fprintf(stderr, "error: Failed to open %s (%s)\n",
- path, strerror(errno));
+ LILV_ERRORF("Failed to open %s (%s)\n", path, strerror(errno));
free(default_path);
free(default_manifest_path);
return 4;
@@ -658,6 +703,7 @@ lilv_state_save(LilvWorld* world,
SerdEnv* env = serd_env_new(NULL);
serd_env_set_prefix_from_strings(env, USTR("lv2"), USTR(LILV_NS_LV2));
serd_env_set_prefix_from_strings(env, USTR("pset"), USTR(NS_PSET));
+ serd_env_set_prefix_from_strings(env, USTR("rdf"), USTR(LILV_NS_RDF));
serd_env_set_prefix_from_strings(env, USTR("rdfs"), USTR(LILV_NS_RDFS));
serd_env_set_prefix_from_strings(env, USTR("state"), USTR(NS_STATE));
@@ -744,20 +790,50 @@ lilv_state_save(LilvWorld* world,
LILV_WARNF("Failed to unmap property key `%d'\n", prop->key);
} else if (!type) {
LILV_WARNF("Failed to unmap property type `%d'\n", prop->type);
- } else if (!(prop->flags & LV2_STATE_IS_PORTABLE)) {
+#ifdef HAVE_LV2_STATE
+ } else if (!(prop->flags & LV2_STATE_IS_PORTABLE)
+ || !(prop->flags & LV2_STATE_IS_POD)) {
LILV_WARNF("Unable to save non-portable property <%s>\n", type);
+#endif
} else {
+ SerdNode t;
+ p = serd_node_from_string(SERD_URI, USTR(key));
LilvNode* const node = node_from_property(
- world, type, prop->value, prop->size);
+ world, unmap, type, prop->value, prop->size);
if (node) {
- p = serd_node_from_string(SERD_URI, USTR(key));
- SerdNode t;
node_to_serd(node, &o, &t);
serd_writer_write_statement(
writer, SERD_ANON_CONT, NULL,
&state_node, &p, &o, &t, NULL);
} else {
- LILV_WARNF("Unable to save property type <%s>\n", type);
+ char name[16];
+ snprintf(name, sizeof(name), "b%u", i);
+ const SerdNode blank = serd_node_from_string(
+ SERD_BLANK, (const uint8_t*)name);
+
+ // <state> <key> [
+ serd_writer_write_statement(
+ writer, SERD_ANON_O_BEGIN, NULL,
+ &state_node, &p, &blank, NULL, NULL);
+
+ // rdf:type <type>
+ p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "type"));
+ o = serd_node_from_string(SERD_URI, USTR(type));
+ serd_writer_write_statement(
+ writer, SERD_ANON_CONT, NULL,
+ &blank, &p, &o, NULL, NULL);
+
+ // rdf:value "string"^^<xsd:base64Binary>
+ SerdNode blob = serd_node_new_blob(prop->value, prop->size, true);
+ p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "value"));
+ t = serd_node_from_string(SERD_URI,
+ USTR(LILV_NS_XSD "base64Binary"));
+ serd_writer_write_statement(
+ writer, SERD_ANON_CONT, NULL,
+ &blank, &p, &blob, &t, NULL);
+ serd_node_free(&blob);
+
+ serd_writer_end_anon(writer, &blank); // ]
}
}
}
@@ -781,6 +857,44 @@ lilv_state_save(LilvWorld* world,
}
LILV_API
+bool
+lilv_state_equals(const LilvState* a, const LilvState* b)
+{
+ if (!lilv_node_equals(a->plugin_uri, b->plugin_uri)
+ || (a->label && !b->label)
+ || (b->label && !a->label)
+ || (a->label && b->label && strcmp(a->label, b->label))
+ || a->num_props != b->num_props
+ || a->num_values != b->num_values) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < a->num_values; ++i) {
+ PortValue* const av = &a->values[i];
+ PortValue* const bv = &b->values[i];
+ if (strcmp(av->symbol, bv->symbol)) {
+ return false;
+ } else if (!lilv_node_equals(av->value, bv->value)) {
+ return false;
+ }
+ }
+
+ for (uint32_t i = 0; i < a->num_props; ++i) {
+ Property* const ap = &a->props[i];
+ Property* const bp = &b->props[i];
+ if (ap->size != bp->size
+ || ap->key != bp->key
+ || ap->type != bp->type
+ || ap->flags != bp->flags
+ || memcmp(ap->value, bp->value, ap->size)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+LILV_API
void
lilv_state_free(LilvState* state)
{
diff --git a/src/world.c b/src/world.c
index af20bc3..2029136 100644
--- a/src/world.c
+++ b/src/world.c
@@ -77,6 +77,7 @@ lilv_world_new(void)
world->rdfs_label_node = NEW_URI(LILV_NS_RDFS "label");
world->rdfs_seealso_node = NEW_URI(LILV_NS_RDFS "seeAlso");
world->rdfs_subclassof_node = NEW_URI(LILV_NS_RDFS "subClassOf");
+ world->xsd_base64Binary_node = NEW_URI(LILV_NS_XSD "base64Binary");
world->xsd_boolean_node = NEW_URI(LILV_NS_XSD "boolean");
world->xsd_decimal_node = NEW_URI(LILV_NS_XSD "decimal");
world->xsd_double_node = NEW_URI(LILV_NS_XSD "double");
@@ -135,6 +136,7 @@ lilv_world_free(LilvWorld* world)
sord_node_free(world->world, world->rdfs_label_node);
sord_node_free(world->world, world->rdfs_seealso_node);
sord_node_free(world->world, world->rdfs_subclassof_node);
+ sord_node_free(world->world, world->xsd_base64Binary_node);
sord_node_free(world->world, world->xsd_boolean_node);
sord_node_free(world->world, world->xsd_decimal_node);
sord_node_free(world->world, world->xsd_double_node);
diff --git a/test/lilv_test.c b/test/lilv_test.c
index b5c5302..1994832 100644
--- a/test/lilv_test.c
+++ b/test/lilv_test.c
@@ -28,6 +28,9 @@
#include <math.h>
#include "lilv/lilv.h"
+#include "../src/lilv_internal.h"
+
+#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
#define TEST_PATH_MAX 1024
@@ -972,6 +975,156 @@ test_ui(void)
/*****************************************************************************/
+float in = 1.0;
+float out = 42.0;
+
+LilvNode*
+get_port_value(const char* port_symbol, void* user_data)
+{
+ if (!strcmp(port_symbol, "input")) {
+ return lilv_new_float((LilvWorld*)user_data, in);
+ } else if (!strcmp(port_symbol, "output")) {
+ return lilv_new_float((LilvWorld*)user_data, out);
+ } else {
+ fprintf(stderr, "error: get_port_value for nonexistent port `%s'\n",
+ port_symbol);
+ return NULL;
+ }
+}
+
+void
+set_port_value(const char* port_symbol,
+ const LilvNode* value,
+ void* user_data)
+{
+ if (!strcmp(port_symbol, "input")) {
+ in = lilv_node_as_float(value);
+ } else if (!strcmp(port_symbol, "output")) {
+ out = lilv_node_as_float(value);
+ } else {
+ fprintf(stderr, "error: set_port_value for nonexistent port `%s'\n",
+ port_symbol);
+ }
+}
+
+char** uris = NULL;
+size_t n_uris = 0;
+
+LV2_URID
+map_uri(LV2_URID_Map_Handle handle,
+ const char* uri)
+{
+ for (size_t i = 0; i < n_uris; ++i) {
+ if (!strcmp(uris[i], uri)) {
+ return i + 1;
+ }
+ }
+
+ uris = realloc(uris, ++n_uris * sizeof(char*));
+ uris[n_uris - 1] = lilv_strdup(uri);
+ return n_uris;
+}
+
+const char*
+unmap_uri(LV2_URID_Map_Handle handle,
+ LV2_URID urid)
+{
+ if (urid > 0 && urid <= n_uris) {
+ return uris[urid - 1];
+ }
+ return NULL;
+}
+
+int
+test_state(void)
+{
+ LilvWorld* world = lilv_world_new();
+ LilvNode* bundle_uri = lilv_new_uri(world, LILV_TEST_BUNDLE);
+ LilvNode* plugin_uri = lilv_new_uri(world,
+ "http://example.org/lilv-test-plugin");
+ lilv_world_load_bundle(world, bundle_uri);
+
+ const LilvPlugins* plugins = lilv_world_get_all_plugins(world);
+ const LilvPlugin* plugin = lilv_plugins_get_by_uri(plugins, plugin_uri);
+ TEST_ASSERT(plugin);
+
+ LV2_URID_Map map = { NULL, map_uri };
+ LV2_Feature map_feature = { LV2_URID_MAP_URI, &map };
+ LV2_URID_Unmap unmap = { NULL, unmap_uri };
+ LV2_Feature unmap_feature = { LV2_URID_UNMAP_URI, &unmap };
+ const LV2_Feature* features[] = { &map_feature, &unmap_feature, NULL };
+
+ LilvInstance* instance = lilv_plugin_instantiate(plugin, 48000.0, features);
+ TEST_ASSERT(instance);
+
+ lilv_instance_connect_port(instance, 0, &in);
+ lilv_instance_connect_port(instance, 1, &out);
+
+ lilv_instance_run(instance, 1);
+ TEST_ASSERT(in == 1.0);
+ TEST_ASSERT(out == 1.0);
+
+ // Get instance state state
+ LilvState* state = lilv_state_new_from_instance(
+ plugin, instance, get_port_value, world, 0, NULL);
+
+ // Get another instance state
+ LilvState* state2 = lilv_state_new_from_instance(
+ plugin, instance, get_port_value, world, 0, NULL);
+
+ // Ensure they are equal
+ TEST_ASSERT(lilv_state_equals(state, state2));
+
+ const LilvNode* state_plugin_uri = lilv_state_get_plugin_uri(state);
+ TEST_ASSERT(lilv_node_equals(state_plugin_uri, plugin_uri));
+
+ // Tinker with the label of the first state
+ TEST_ASSERT(lilv_state_get_label(state) == NULL);
+ lilv_state_set_label(state, "Test State Old Label");
+ TEST_ASSERT(!strcmp(lilv_state_get_label(state), "Test State Old Label"));
+ lilv_state_set_label(state, "Test State");
+ TEST_ASSERT(!strcmp(lilv_state_get_label(state), "Test State"));
+
+ TEST_ASSERT(!lilv_state_equals(state, state2)); // Label changed
+
+ // Run and get a new instance state (which should now differ)
+ lilv_instance_run(instance, 1);
+ LilvState* state3 = lilv_state_new_from_instance(
+ plugin, instance, get_port_value, world, 0, NULL);
+ TEST_ASSERT(!lilv_state_equals(state2, state3)); // num_runs changed
+
+ // Restore instance state to original state
+ lilv_state_restore(state2, instance, set_port_value, NULL, 0, NULL);
+
+ // Take a new snapshot and ensure it matches the set state
+ LilvState* state4 = lilv_state_new_from_instance(
+ plugin, instance, get_port_value, world, 0, NULL);
+ TEST_ASSERT(lilv_state_equals(state2, state4));
+
+ // Save state to a file
+ int ret = lilv_state_save(world, &unmap, state, NULL,
+ "state.ttl", "manifest.ttl");
+ TEST_ASSERT(!ret);
+
+ // Load state from file
+ LilvState* state5 = lilv_state_new_from_file(world, &map, NULL, "state.ttl");
+ TEST_ASSERT(lilv_state_equals(state, state5)); // Round trip accuracy
+
+ lilv_state_free(state);
+ lilv_state_free(state2);
+ lilv_state_free(state3);
+ lilv_state_free(state4);
+ lilv_state_free(state5);
+
+ lilv_instance_free(instance);
+
+ lilv_node_free(bundle_uri);
+ lilv_world_free(world);
+ return 1;
+}
+
+/*****************************************************************************/
+
/* add tests here */
static struct TestCase tests[] = {
TEST_CASE(utils),
@@ -983,6 +1136,7 @@ static struct TestCase tests[] = {
TEST_CASE(plugin),
TEST_CASE(port),
TEST_CASE(ui),
+ TEST_CASE(state),
{ NULL, NULL }
};
diff --git a/test/manifest.ttl b/test/manifest.ttl
new file mode 100644
index 0000000..5af7e88
--- /dev/null
+++ b/test/manifest.ttl
@@ -0,0 +1,7 @@
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+<http://example.org/lilv-test-plugin>
+ a lv2:Plugin ;
+ lv2:binary <test_plugin.so> ;
+ rdfs:seeAlso <test_plugin.ttl> .
diff --git a/test/test_plugin.c b/test/test_plugin.c
new file mode 100644
index 0000000..0ef76f2
--- /dev/null
+++ b/test/test_plugin.c
@@ -0,0 +1,235 @@
+/*
+ Lilv Test Plugin
+ Copyright 2011 David Robillard <d@drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lv2/lv2plug.in/ns/ext/state/state.h"
+#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
+#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
+
+#define TEST_URI "http://example.org/lilv-test-plugin"
+
+#define NS_ATOM "http://lv2plug.in/ns/ext/atom#"
+
+enum {
+ TEST_INPUT = 0,
+ TEST_OUTPUT = 1
+};
+
+typedef struct {
+ LV2_URID_Map* map;
+
+ struct {
+ LV2_URID atom_Float;
+ } uris;
+
+ float* input;
+ float* output;
+ unsigned num_runs;
+} Test;
+
+static void
+cleanup(LV2_Handle instance)
+{
+ free(instance);
+}
+
+static void
+connect_port(LV2_Handle instance,
+ uint32_t port,
+ void* data)
+{
+ Test* test = (Test*)instance;
+ switch (port) {
+ case TEST_INPUT:
+ test->input = (float*)data;
+ break;
+ case TEST_OUTPUT:
+ test->output = (float*)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor,
+ double rate,
+ const char* path,
+ const LV2_Feature* const* features)
+{
+ Test* test = (Test*)malloc(sizeof(Test));
+ if (!test) {
+ return NULL;
+ }
+
+ test->map = NULL;
+ test->input = NULL;
+ test->output = NULL;
+ test->num_runs = 0;
+
+ /* Scan host features for URID map */
+ for (int i = 0; features[i]; ++i) {
+ if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) {
+ test->map = (LV2_URID_Map*)features[i]->data;
+ test->uris.atom_Float = test->map->map(
+ test->map->handle, NS_ATOM "Float");
+ }
+ }
+
+ if (!test->map) {
+ fprintf(stderr, "Host does not support urid:map.\n");
+ free(test);
+ return NULL;
+ }
+
+ return (LV2_Handle)test;
+}
+
+static void
+run(LV2_Handle instance,
+ uint32_t sample_count)
+{
+ Test* test = (Test*)instance;
+ *test->output = *test->input;
+ ++test->num_runs;
+}
+
+static uint32_t
+map_uri(Test* plugin, const char* uri)
+{
+ return plugin->map->map(plugin->map->handle, uri);
+}
+
+static void
+save(LV2_Handle instance,
+ LV2_State_Store_Function store,
+ void* callback_data,
+ uint32_t flags,
+ const LV2_Feature* const* features)
+{
+ Test* plugin = (Test*)instance;
+
+ store(callback_data,
+ map_uri(plugin, "http://example.org/greeting"),
+ "hello",
+ strlen("hello") + 1,
+ map_uri(plugin, NS_ATOM "String"),
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ const uint32_t urid = map_uri(plugin, "http://example.org/urivalue");
+ store(callback_data,
+ map_uri(plugin, "http://example.org/uri"),
+ &urid,
+ sizeof(uint32_t),
+ map_uri(plugin, NS_ATOM "URID"),
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ store(callback_data,
+ map_uri(plugin, "http://example.org/num-runs"),
+ &plugin->num_runs,
+ sizeof(plugin->num_runs),
+ map_uri(plugin, NS_ATOM "Int32"),
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ const float two = 2.0f;
+ store(callback_data,
+ map_uri(plugin, "http://example.org/two"),
+ &two,
+ sizeof(two),
+ map_uri(plugin, NS_ATOM "Float"),
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ const uint32_t affirmative = 1;
+ store(callback_data,
+ map_uri(plugin, "http://example.org/true"),
+ &affirmative,
+ sizeof(affirmative),
+ map_uri(plugin, NS_ATOM "Bool"),
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ const uint32_t negative = 0;
+ store(callback_data,
+ map_uri(plugin, "http://example.org/false"),
+ &negative,
+ sizeof(negative),
+ map_uri(plugin, NS_ATOM "Bool"),
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ const uint8_t blob[] = "This is a bunch of data that happens to be text"
+ " but could really be anything at all, lest you feel like cramming"
+ " all sorts of ridiculous binary stuff in Turtle";
+ store(callback_data,
+ map_uri(plugin, "http://example.org/blob"),
+ blob,
+ sizeof(blob),
+ map_uri(plugin, "http://example.org/SomeUnknownType"),
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+}
+
+static void
+restore(LV2_Handle instance,
+ LV2_State_Retrieve_Function retrieve,
+ void* callback_data,
+ uint32_t flags,
+ const LV2_Feature* const* features)
+{
+ Test* plugin = (Test*)instance;
+
+ size_t size;
+ uint32_t type;
+ uint32_t valflags;
+
+ plugin->num_runs = *(int32_t*)retrieve(
+ callback_data,
+ map_uri(plugin, "http://example.org/num-runs"),
+ &size, &type, &valflags);
+}
+
+const void*
+extension_data(const char* uri)
+{
+ static const LV2_State_Interface state = { save, restore };
+ if (!strcmp(uri, LV2_STATE_INTERFACE_URI)) {
+ return &state;
+ }
+ return NULL;
+}
+
+static const LV2_Descriptor descriptor = {
+ TEST_URI,
+ instantiate,
+ connect_port,
+ NULL, // activate,
+ run,
+ NULL, // deactivate,
+ cleanup,
+ extension_data
+};
+
+LV2_SYMBOL_EXPORT
+const LV2_Descriptor* lv2_descriptor(uint32_t index)
+{
+ switch (index) {
+ case 0:
+ return &descriptor;
+ default:
+ return NULL;
+ }
+}
diff --git a/test/test_plugin.ttl b/test/test_plugin.ttl
new file mode 100644
index 0000000..ec16d00
--- /dev/null
+++ b/test/test_plugin.ttl
@@ -0,0 +1,40 @@
+# Lilv Test Plugin
+# Copyright 2011 David Robillard <d@drobilla.net>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
+
+<http://example.org/lilv-test-plugin>
+ a lv2:Plugin ;
+ doap:name "Lilv Test" ;
+ doap:license <http://opensource.org/licenses/isc> ;
+ lv2:requiredFeature <http://lv2plug.in/ns/ext/urid#Mapper> ;
+ lv2:optionalFeature lv2:hardRtCapable ;
+ lv2:extensionData <http://lv2plug.in/ns/ext/state#Interface> ;
+ lv2:port [
+ a lv2:InputPort ,
+ lv2:ControlPort ;
+ lv2:index 0 ;
+ lv2:symbol "input" ;
+ lv2:name "Input"
+ ] , [
+ a lv2:OutputPort ,
+ lv2:ControlPort ;
+ lv2:index 1 ;
+ lv2:symbol "output" ;
+ lv2:name "Output"
+ ] .
diff --git a/wscript b/wscript
index 13a382f..a774f0f 100644
--- a/wscript
+++ b/wscript
@@ -69,11 +69,13 @@ def configure(conf):
autowaf.check_pkg(conf, 'lv2core', uselib_store='LV2CORE', mandatory=True)
autowaf.check_pkg(conf, 'serd-0', uselib_store='SERD',
- atleast_version='0.7.0', mandatory=True)
+ atleast_version='0.9.0', mandatory=True)
autowaf.check_pkg(conf, 'sord-0', uselib_store='SORD',
atleast_version='0.5.0', mandatory=True)
+ autowaf.check_pkg(conf, 'lv2-lv2plug.in-ns-ext-urid',
+ uselib_store='LV2_URID', mandatory=True)
autowaf.check_pkg(conf, 'lv2-lv2plug.in-ns-ext-state',
- uselib_store='LV2_STATE', mandatory=True)
+ uselib_store='LV2_STATE', mandatory=False)
conf.check_cc(function_name='wordexp',
header_name='wordexp.h',
@@ -204,7 +206,7 @@ def build(bld):
cflags = libflags + [ '-DLILV_SHARED',
'-DLILV_INTERNAL' ],
lib = lib)
- autowaf.use_lib(bld, obj, 'SORD LV2CORE')
+ autowaf.use_lib(bld, obj, 'SORD LV2CORE LV2_STATE LV2_URID')
# Static library
if bld.env['BUILD_STATIC']:
@@ -217,9 +219,30 @@ def build(bld):
vnum = LILV_LIB_VERSION,
install_path = '${LIBDIR}',
cflags = [ '-DLILV_INTERNAL' ])
- autowaf.use_lib(bld, obj, 'SORD LV2CORE')
+ autowaf.use_lib(bld, obj, 'SORD LV2CORE LV2_STATE LV2_URID')
if bld.env['BUILD_TESTS']:
+ # Test plugin library
+ penv = bld.env.derive()
+ shlib_pattern = penv['cshlib_PATTERN']
+ if shlib_pattern.startswith('lib'):
+ shlib_pattern = shlib_pattern[3:]
+ penv['cshlib_PATTERN'] = shlib_pattern
+ obj = bld(features = 'c cshlib',
+ env = penv,
+ source = 'test/test_plugin.c',
+ name = 'test_plugin',
+ target = 'test/test_plugin.lv2/test_plugin',
+ install_path = None,
+ uselib = 'LV2CORE LV2_STATE LV2_URID')
+
+ # Test plugin data files
+ for i in [ 'manifest.ttl', 'test_plugin.ttl' ]:
+ bld(rule = 'cp ${SRC} ${TGT}',
+ source = 'test/' + i,
+ target = 'test/test_plugin.lv2/' + i,
+ install_path = None)
+
# Static profiled library (for unit test code coverage)
obj = bld(features = 'c cstlib',
source = lib_source,
@@ -230,7 +253,7 @@ def build(bld):
cflags = [ '-fprofile-arcs', '-ftest-coverage',
'-DLILV_INTERNAL' ],
lib = lib + ['gcov'])
- autowaf.use_lib(bld, obj, 'SORD LV2CORE')
+ autowaf.use_lib(bld, obj, 'SORD LV2CORE LV2_STATE LV2_URID')
# Unit test program
obj = bld(features = 'c cprogram',
@@ -241,8 +264,10 @@ def build(bld):
lib = lib + ['gcov'],
target = 'test/lilv_test',
install_path = '',
+ defines = ['LILV_TEST_BUNDLE=\"%s/\"' % os.path.abspath(
+ os.path.join(out, 'test', 'test_plugin.lv2'))],
cflags = [ '-fprofile-arcs', '-ftest-coverage' ])
- autowaf.use_lib(bld, obj, 'SORD LV2CORE')
+ autowaf.use_lib(bld, obj, 'SORD LV2CORE LV2_STATE LV2_URID')
# Utilities
if bld.env['BUILD_UTILS']: