diff options
-rw-r--r-- | lilv/lilv.h | 7 | ||||
-rw-r--r-- | src/instance.c | 2 | ||||
-rw-r--r-- | src/lilv_internal.h | 8 | ||||
-rw-r--r-- | src/node.c | 26 | ||||
-rw-r--r-- | src/state.c | 184 | ||||
-rw-r--r-- | src/world.c | 2 | ||||
-rw-r--r-- | test/lilv_test.c | 154 | ||||
-rw-r--r-- | test/manifest.ttl | 7 | ||||
-rw-r--r-- | test/test_plugin.c | 235 | ||||
-rw-r--r-- | test/test_plugin.ttl | 40 | ||||
-rw-r--r-- | wscript | 37 |
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; }; @@ -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" + ] . @@ -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']: |