summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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']: