summaryrefslogtreecommitdiffstats
path: root/src/plugin.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugin.c')
-rw-r--r--src/plugin.c1149
1 files changed, 1149 insertions, 0 deletions
diff --git a/src/plugin.c b/src/plugin.c
new file mode 100644
index 0000000..b2648dd
--- /dev/null
+++ b/src/plugin.c
@@ -0,0 +1,1149 @@
+/*
+ Copyright 2007-2019 David Robillard <http://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.
+*/
+
+#define __STDC_LIMIT_MACROS
+
+#include "lilv_config.h"
+#include "lilv_internal.h"
+
+#include "lilv/lilv.h"
+#include "serd/serd.h"
+#include "sord/sord.h"
+#include "zix/tree.h"
+
+#include "lv2/core/lv2.h"
+#include "lv2/ui/ui.h"
+
+#ifdef LILV_DYN_MANIFEST
+# include "lv2/dynmanifest/dynmanifest.h"
+# include <dlfcn.h>
+#endif
+
+#include <math.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define NS_DOAP (const uint8_t*)"http://usefulinc.com/ns/doap#"
+#define NS_FOAF (const uint8_t*)"http://xmlns.com/foaf/0.1/"
+
+static void
+lilv_plugin_init(LilvPlugin* plugin, LilvNode* bundle_uri)
+{
+ plugin->bundle_uri = bundle_uri;
+ plugin->binary_uri = NULL;
+#ifdef LILV_DYN_MANIFEST
+ plugin->dynmanifest = NULL;
+#endif
+ plugin->plugin_class = NULL;
+ plugin->data_uris = lilv_nodes_new();
+ plugin->ports = NULL;
+ plugin->num_ports = 0;
+ plugin->loaded = false;
+ plugin->parse_errors = false;
+ plugin->replaced = false;
+}
+
+/** Ownership of `uri` and `bundle` is taken */
+LilvPlugin*
+lilv_plugin_new(LilvWorld* world, LilvNode* uri, LilvNode* bundle_uri)
+{
+ LilvPlugin* plugin = (LilvPlugin*)malloc(sizeof(LilvPlugin));
+
+ plugin->world = world;
+ plugin->plugin_uri = uri;
+
+ lilv_plugin_init(plugin, bundle_uri);
+ return plugin;
+}
+
+void
+lilv_plugin_clear(LilvPlugin* plugin, LilvNode* bundle_uri)
+{
+ lilv_node_free(plugin->bundle_uri);
+ lilv_node_free(plugin->binary_uri);
+ lilv_nodes_free(plugin->data_uris);
+ lilv_plugin_init(plugin, bundle_uri);
+}
+
+static void
+lilv_plugin_free_ports(LilvPlugin* plugin)
+{
+ if (plugin->ports) {
+ for (uint32_t i = 0; i < plugin->num_ports; ++i) {
+ lilv_port_free(plugin, plugin->ports[i]);
+ }
+ free(plugin->ports);
+ plugin->num_ports = 0;
+ plugin->ports = NULL;
+ }
+}
+
+void
+lilv_plugin_free(LilvPlugin* plugin)
+{
+#ifdef LILV_DYN_MANIFEST
+ if (plugin->dynmanifest && --plugin->dynmanifest->refs == 0) {
+ typedef int (*CloseFunc)(LV2_Dyn_Manifest_Handle);
+ CloseFunc close_func = (CloseFunc)lilv_dlfunc(plugin->dynmanifest->lib,
+ "lv2_dyn_manifest_close");
+ if (close_func) {
+ close_func(plugin->dynmanifest->handle);
+ }
+
+ dlclose(plugin->dynmanifest->lib);
+ lilv_node_free(plugin->dynmanifest->bundle);
+ free(plugin->dynmanifest);
+ }
+#endif
+
+ lilv_node_free(plugin->plugin_uri);
+ plugin->plugin_uri = NULL;
+
+ lilv_node_free(plugin->bundle_uri);
+ plugin->bundle_uri = NULL;
+
+ lilv_node_free(plugin->binary_uri);
+ plugin->binary_uri = NULL;
+
+ lilv_plugin_free_ports(plugin);
+
+ lilv_nodes_free(plugin->data_uris);
+ plugin->data_uris = NULL;
+
+ free(plugin);
+}
+
+static LilvNode*
+lilv_plugin_get_one(const LilvPlugin* plugin,
+ const SordNode* subject,
+ const SordNode* predicate)
+{
+ LilvNode* ret = NULL;
+ SordIter* stream = lilv_world_query_internal(
+ plugin->world, subject, predicate, NULL);
+ if (!sord_iter_end(stream)) {
+ ret = lilv_node_new_from_node(plugin->world,
+ sord_iter_get_node(stream, SORD_OBJECT));
+ }
+ sord_iter_free(stream);
+ return ret;
+}
+
+LilvNode*
+lilv_plugin_get_unique(const LilvPlugin* plugin,
+ const SordNode* subject,
+ const SordNode* predicate)
+{
+ LilvNode* ret = lilv_plugin_get_one(plugin, subject, predicate);
+ if (!ret) {
+ LILV_ERRORF("No value found for (%s %s ...) property\n",
+ sord_node_get_string(subject),
+ sord_node_get_string(predicate));
+ }
+ return ret;
+}
+
+static void
+lilv_plugin_load(LilvPlugin* plugin)
+{
+ SordNode* bundle_uri_node = plugin->bundle_uri->node;
+ const SerdNode* bundle_uri_snode = sord_node_to_serd_node(bundle_uri_node);
+
+ SerdEnv* env = serd_env_new(bundle_uri_snode);
+ SerdReader* reader = sord_new_reader(plugin->world->model, env, SERD_TURTLE,
+ bundle_uri_node);
+
+ SordModel* prots = lilv_world_filter_model(
+ plugin->world,
+ plugin->world->model,
+ plugin->plugin_uri->node,
+ plugin->world->uris.lv2_prototype,
+ NULL, NULL);
+ SordModel* skel = sord_new(plugin->world->world, SORD_SPO, false);
+ SordIter* iter = sord_begin(prots);
+ for (; !sord_iter_end(iter); sord_iter_next(iter)) {
+ const SordNode* t = sord_iter_get_node(iter, SORD_OBJECT);
+ LilvNode* prototype = lilv_node_new_from_node(plugin->world, t);
+
+ lilv_world_load_resource(plugin->world, prototype);
+
+ SordIter* statements = sord_search(
+ plugin->world->model, prototype->node, NULL, NULL, NULL);
+ FOREACH_MATCH(statements) {
+ SordQuad quad;
+ sord_iter_get(statements, quad);
+ quad[0] = plugin->plugin_uri->node;
+ sord_add(skel, quad);
+ }
+
+ sord_iter_free(statements);
+ lilv_node_free(prototype);
+ }
+ sord_iter_free(iter);
+
+ for (iter = sord_begin(skel); !sord_iter_end(iter); sord_iter_next(iter)) {
+ SordQuad quad;
+ sord_iter_get(iter, quad);
+ sord_add(plugin->world->model, quad);
+ }
+ sord_iter_free(iter);
+ sord_free(skel);
+ sord_free(prots);
+
+ // Parse all the plugin's data files into RDF model
+ SerdStatus st = SERD_SUCCESS;
+ LILV_FOREACH(nodes, i, plugin->data_uris) {
+ const LilvNode* data_uri = lilv_nodes_get(plugin->data_uris, i);
+
+ serd_env_set_base_uri(env, sord_node_to_serd_node(data_uri->node));
+ st = lilv_world_load_file(plugin->world, reader, data_uri);
+ if (st > SERD_FAILURE) {
+ break;
+ }
+ }
+
+ if (st > SERD_FAILURE) {
+ plugin->loaded = true;
+ plugin->parse_errors = true;
+ serd_reader_free(reader);
+ serd_env_free(env);
+ return;
+ }
+
+#ifdef LILV_DYN_MANIFEST
+ // Load and parse dynamic manifest data, if this is a library
+ if (plugin->dynmanifest) {
+ typedef int (*GetDataFunc)(LV2_Dyn_Manifest_Handle handle,
+ FILE* fp,
+ const char* uri);
+ GetDataFunc get_data_func = (GetDataFunc)lilv_dlfunc(
+ plugin->dynmanifest->lib, "lv2_dyn_manifest_get_data");
+ if (get_data_func) {
+ const SordNode* bundle = plugin->dynmanifest->bundle->node;
+ serd_env_set_base_uri(env, sord_node_to_serd_node(bundle));
+ FILE* fd = tmpfile();
+ get_data_func(plugin->dynmanifest->handle, fd,
+ lilv_node_as_string(plugin->plugin_uri));
+ rewind(fd);
+ serd_reader_add_blank_prefix(
+ reader, lilv_world_blank_node_prefix(plugin->world));
+ serd_reader_read_file_handle(
+ reader, fd, (const uint8_t*)"(dyn-manifest)");
+ fclose(fd);
+ }
+ }
+#endif
+ serd_reader_free(reader);
+ serd_env_free(env);
+
+ plugin->loaded = true;
+}
+
+static bool
+is_symbol(const char* str)
+{
+ for (const char* s = str; *s; ++s) {
+ if (!((*s >= 'a' && *s <= 'z') ||
+ (*s >= 'A' && *s <= 'Z') ||
+ (s > str && *s >= '0' && *s <= '9') ||
+ *s == '_')) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void
+lilv_plugin_load_ports_if_necessary(const LilvPlugin* const_plugin)
+{
+ LilvPlugin* plugin = (LilvPlugin*)const_plugin;
+
+ lilv_plugin_load_if_necessary(plugin);
+
+ if (!plugin->ports) {
+ plugin->ports = (LilvPort**)malloc(sizeof(LilvPort*));
+ plugin->ports[0] = NULL;
+
+ SordIter* ports = lilv_world_query_internal(
+ plugin->world,
+ plugin->plugin_uri->node,
+ plugin->world->uris.lv2_port,
+ NULL);
+
+ FOREACH_MATCH(ports) {
+ const SordNode* port = sord_iter_get_node(ports, SORD_OBJECT);
+ LilvNode* index = lilv_plugin_get_unique(
+ plugin, port, plugin->world->uris.lv2_index);
+ LilvNode* symbol = lilv_plugin_get_unique(
+ plugin, port, plugin->world->uris.lv2_symbol);
+
+ if (!lilv_node_is_string(symbol) ||
+ !is_symbol((const char*)sord_node_get_string(symbol->node))) {
+ LILV_ERRORF("Plugin <%s> port symbol `%s' is invalid\n",
+ lilv_node_as_uri(plugin->plugin_uri),
+ lilv_node_as_string(symbol));
+ lilv_node_free(symbol);
+ lilv_node_free(index);
+ lilv_plugin_free_ports(plugin);
+ break;
+ }
+
+ if (!lilv_node_is_int(index)) {
+ LILV_ERRORF("Plugin <%s> port index is not an integer\n",
+ lilv_node_as_uri(plugin->plugin_uri));
+ lilv_node_free(symbol);
+ lilv_node_free(index);
+ lilv_plugin_free_ports(plugin);
+ break;
+ }
+
+ uint32_t this_index = lilv_node_as_int(index);
+ LilvPort* this_port = NULL;
+ if (plugin->num_ports > this_index) {
+ this_port = plugin->ports[this_index];
+ } else {
+ plugin->ports = (LilvPort**)realloc(
+ plugin->ports, (this_index + 1) * sizeof(LilvPort*));
+ memset(plugin->ports + plugin->num_ports, '\0',
+ (this_index - plugin->num_ports) * sizeof(LilvPort*));
+ plugin->num_ports = this_index + 1;
+ }
+
+ // Havn't seen this port yet, add it to array
+ if (!this_port) {
+ this_port = lilv_port_new(plugin->world,
+ port,
+ this_index,
+ lilv_node_as_string(symbol));
+ plugin->ports[this_index] = this_port;
+ }
+
+ SordIter* types = lilv_world_query_internal(
+ plugin->world, port, plugin->world->uris.rdf_a, NULL);
+ FOREACH_MATCH(types) {
+ const SordNode* type = sord_iter_get_node(types, SORD_OBJECT);
+ if (sord_node_get_type(type) == SORD_URI) {
+ zix_tree_insert(
+ (ZixTree*)this_port->classes,
+ lilv_node_new_from_node(plugin->world, type), NULL);
+ } else {
+ LILV_WARNF("Plugin <%s> port type is not a URI\n",
+ lilv_node_as_uri(plugin->plugin_uri));
+ }
+ }
+ sord_iter_free(types);
+
+ lilv_node_free(symbol);
+ lilv_node_free(index);
+ }
+ sord_iter_free(ports);
+
+ // Check sanity
+ for (uint32_t i = 0; i < plugin->num_ports; ++i) {
+ if (!plugin->ports[i]) {
+ LILV_ERRORF("Plugin <%s> is missing port %d/%d\n",
+ lilv_node_as_uri(plugin->plugin_uri), i, plugin->num_ports);
+ lilv_plugin_free_ports(plugin);
+ break;
+ }
+ }
+ }
+}
+
+void
+lilv_plugin_load_if_necessary(const LilvPlugin* plugin)
+{
+ if (!plugin->loaded) {
+ lilv_plugin_load((LilvPlugin*)plugin);
+ }
+}
+
+LILV_API const LilvNode*
+lilv_plugin_get_uri(const LilvPlugin* plugin)
+{
+ return plugin->plugin_uri;
+}
+
+LILV_API const LilvNode*
+lilv_plugin_get_bundle_uri(const LilvPlugin* plugin)
+{
+ return plugin->bundle_uri;
+}
+
+LILV_API const LilvNode*
+lilv_plugin_get_library_uri(const LilvPlugin* plugin)
+{
+ lilv_plugin_load_if_necessary((LilvPlugin*)plugin);
+ if (!plugin->binary_uri) {
+ // <plugin> lv2:binary ?binary
+ SordIter* i = lilv_world_query_internal(plugin->world,
+ plugin->plugin_uri->node,
+ plugin->world->uris.lv2_binary,
+ NULL);
+ FOREACH_MATCH(i) {
+ const SordNode* binary_node = sord_iter_get_node(i, SORD_OBJECT);
+ if (sord_node_get_type(binary_node) == SORD_URI) {
+ ((LilvPlugin*)plugin)->binary_uri =
+ lilv_node_new_from_node(plugin->world, binary_node);
+ break;
+ }
+ }
+ sord_iter_free(i);
+ }
+ if (!plugin->binary_uri) {
+ LILV_WARNF("Plugin <%s> has no lv2:binary\n",
+ lilv_node_as_uri(lilv_plugin_get_uri(plugin)));
+ }
+ return plugin->binary_uri;
+}
+
+LILV_API const LilvNodes*
+lilv_plugin_get_data_uris(const LilvPlugin* plugin)
+{
+ return plugin->data_uris;
+}
+
+LILV_API const LilvPluginClass*
+lilv_plugin_get_class(const LilvPlugin* plugin)
+{
+ lilv_plugin_load_if_necessary((LilvPlugin*)plugin);
+ if (!plugin->plugin_class) {
+ // <plugin> a ?class
+ SordIter* c = lilv_world_query_internal(plugin->world,
+ plugin->plugin_uri->node,
+ plugin->world->uris.rdf_a,
+ NULL);
+ FOREACH_MATCH(c) {
+ const SordNode* class_node = sord_iter_get_node(c, SORD_OBJECT);
+ if (sord_node_get_type(class_node) != SORD_URI) {
+ continue;
+ }
+
+ LilvNode* klass = lilv_node_new_from_node(plugin->world, class_node);
+ if (!lilv_node_equals(klass, plugin->world->lv2_plugin_class->uri)) {
+ const LilvPluginClass* pclass = lilv_plugin_classes_get_by_uri(
+ plugin->world->plugin_classes, klass);
+
+ if (pclass) {
+ ((LilvPlugin*)plugin)->plugin_class = pclass;
+ lilv_node_free(klass);
+ break;
+ }
+ }
+
+ lilv_node_free(klass);
+ }
+ sord_iter_free(c);
+
+ if (plugin->plugin_class == NULL) {
+ ((LilvPlugin*)plugin)->plugin_class =
+ plugin->world->lv2_plugin_class;
+ }
+ }
+ return plugin->plugin_class;
+}
+
+static LilvNodes*
+lilv_plugin_get_value_internal(const LilvPlugin* plugin,
+ const SordNode* predicate)
+{
+ lilv_plugin_load_if_necessary(plugin);
+ return lilv_world_find_nodes_internal(
+ plugin->world, plugin->plugin_uri->node, predicate, NULL);
+}
+
+LILV_API bool
+lilv_plugin_verify(const LilvPlugin* plugin)
+{
+ lilv_plugin_load_if_necessary(plugin);
+ if (plugin->parse_errors) {
+ return false;
+ }
+
+ LilvNode* rdf_type = lilv_new_uri(plugin->world, LILV_NS_RDF "type");
+ LilvNodes* results = lilv_plugin_get_value(plugin, rdf_type);
+ lilv_node_free(rdf_type);
+ if (!results) {
+ return false;
+ }
+
+ lilv_nodes_free(results);
+ results = lilv_plugin_get_value_internal(plugin,
+ plugin->world->uris.doap_name);
+ if (!results) {
+ return false;
+ }
+
+ lilv_nodes_free(results);
+ LilvNode* lv2_port = lilv_new_uri(plugin->world, LV2_CORE__port);
+ results = lilv_plugin_get_value(plugin, lv2_port);
+ lilv_node_free(lv2_port);
+ if (!results) {
+ return false;
+ }
+
+ lilv_nodes_free(results);
+ return true;
+}
+
+LILV_API LilvNode*
+lilv_plugin_get_name(const LilvPlugin* plugin)
+{
+ LilvNodes* results = lilv_plugin_get_value_internal(
+ plugin, plugin->world->uris.doap_name);
+
+ LilvNode* ret = NULL;
+ if (results) {
+ LilvNode* val = lilv_nodes_get_first(results);
+ if (lilv_node_is_string(val)) {
+ ret = lilv_node_duplicate(val);
+ }
+ lilv_nodes_free(results);
+ }
+
+ if (!ret) {
+ LILV_WARNF("Plugin <%s> has no (mandatory) doap:name\n",
+ lilv_node_as_string(lilv_plugin_get_uri(plugin)));
+ }
+
+ return ret;
+}
+
+LILV_API LilvNodes*
+lilv_plugin_get_value(const LilvPlugin* plugin,
+ const LilvNode* predicate)
+{
+ lilv_plugin_load_if_necessary(plugin);
+ return lilv_world_find_nodes(plugin->world, plugin->plugin_uri, predicate, NULL);
+}
+
+LILV_API uint32_t
+lilv_plugin_get_num_ports(const LilvPlugin* plugin)
+{
+ lilv_plugin_load_ports_if_necessary(plugin);
+ return plugin->num_ports;
+}
+
+LILV_API void
+lilv_plugin_get_port_ranges_float(const LilvPlugin* plugin,
+ float* min_values,
+ float* max_values,
+ float* def_values)
+{
+ lilv_plugin_load_ports_if_necessary(plugin);
+ LilvNode* min = NULL;
+ LilvNode* max = NULL;
+ LilvNode* def = NULL;
+ LilvNode** minptr = min_values ? &min : NULL;
+ LilvNode** maxptr = max_values ? &max : NULL;
+ LilvNode** defptr = def_values ? &def : NULL;
+
+ for (uint32_t i = 0; i < plugin->num_ports; ++i) {
+ lilv_port_get_range(plugin, plugin->ports[i], defptr, minptr, maxptr);
+
+ if (min_values) {
+ if (lilv_node_is_float(min) || lilv_node_is_int(min)) {
+ min_values[i] = lilv_node_as_float(min);
+ } else {
+ min_values[i] = NAN;
+ }
+ }
+
+ if (max_values) {
+ if (lilv_node_is_float(max) || lilv_node_is_int(max)) {
+ max_values[i] = lilv_node_as_float(max);
+ } else {
+ max_values[i] = NAN;
+ }
+ }
+
+ if (def_values) {
+ if (lilv_node_is_float(def) || lilv_node_is_int(def)) {
+ def_values[i] = lilv_node_as_float(def);
+ } else {
+ def_values[i] = NAN;
+ }
+ }
+
+ lilv_node_free(def);
+ lilv_node_free(min);
+ lilv_node_free(max);
+ }
+}
+
+LILV_API uint32_t
+lilv_plugin_get_num_ports_of_class_va(const LilvPlugin* plugin,
+ const LilvNode* class_1,
+ va_list args)
+{
+ lilv_plugin_load_ports_if_necessary(plugin);
+
+ uint32_t count = 0;
+
+ // Build array of classes from args so we can walk it several times
+ size_t n_classes = 0;
+ const LilvNode** classes = NULL;
+ for (LilvNode* c = NULL; (c = va_arg(args, LilvNode*)); ) {
+ classes = (const LilvNode**)realloc(
+ classes, ++n_classes * sizeof(LilvNode*));
+ classes[n_classes - 1] = c;
+ }
+
+ // Check each port against every type
+ for (unsigned i = 0; i < plugin->num_ports; ++i) {
+ LilvPort* port = plugin->ports[i];
+ if (port && lilv_port_is_a(plugin, port, class_1)) {
+ bool matches = true;
+ for (size_t j = 0; j < n_classes; ++j) {
+ if (!lilv_port_is_a(plugin, port, classes[j])) {
+ matches = false;
+ break;
+ }
+ }
+
+ if (matches) {
+ ++count;
+ }
+ }
+ }
+
+ free(classes);
+ return count;
+}
+
+LILV_API uint32_t
+lilv_plugin_get_num_ports_of_class(const LilvPlugin* plugin,
+ const LilvNode* class_1, ...)
+{
+ va_list args;
+ va_start(args, class_1);
+
+ uint32_t count = lilv_plugin_get_num_ports_of_class_va(plugin, class_1, args);
+
+ va_end(args);
+ return count;
+}
+
+LILV_API bool
+lilv_plugin_has_latency(const LilvPlugin* plugin)
+{
+ lilv_plugin_load_if_necessary(plugin);
+ SordIter* ports = lilv_world_query_internal(
+ plugin->world,
+ plugin->plugin_uri->node,
+ plugin->world->uris.lv2_port,
+ NULL);
+
+ bool ret = false;
+ FOREACH_MATCH(ports) {
+ const SordNode* port = sord_iter_get_node(ports, SORD_OBJECT);
+ SordIter* prop = lilv_world_query_internal(
+ plugin->world,
+ port,
+ plugin->world->uris.lv2_portProperty,
+ plugin->world->uris.lv2_reportsLatency);
+ SordIter* des = lilv_world_query_internal(
+ plugin->world,
+ port,
+ plugin->world->uris.lv2_designation,
+ plugin->world->uris.lv2_latency);
+ const bool latent = !sord_iter_end(prop) || !sord_iter_end(des);
+ sord_iter_free(prop);
+ sord_iter_free(des);
+ if (latent) {
+ ret = true;
+ break;
+ }
+ }
+ sord_iter_free(ports);
+
+ return ret;
+}
+
+static const LilvPort*
+lilv_plugin_get_port_by_property(const LilvPlugin* plugin,
+ const SordNode* port_property)
+{
+ lilv_plugin_load_ports_if_necessary(plugin);
+ for (uint32_t i = 0; i < plugin->num_ports; ++i) {
+ LilvPort* port = plugin->ports[i];
+ SordIter* iter = lilv_world_query_internal(
+ plugin->world,
+ port->node->node,
+ plugin->world->uris.lv2_portProperty,
+ port_property);
+
+ const bool found = !sord_iter_end(iter);
+ sord_iter_free(iter);
+
+ if (found) {
+ return port;
+ }
+ }
+
+ return NULL;
+}
+
+LILV_API const LilvPort*
+lilv_plugin_get_port_by_designation(const LilvPlugin* plugin,
+ const LilvNode* port_class,
+ const LilvNode* designation)
+{
+ LilvWorld* world = plugin->world;
+ lilv_plugin_load_ports_if_necessary(plugin);
+ for (uint32_t i = 0; i < plugin->num_ports; ++i) {
+ LilvPort* port = plugin->ports[i];
+ SordIter* iter = lilv_world_query_internal(
+ world,
+ port->node->node,
+ world->uris.lv2_designation,
+ designation->node);
+
+ const bool found = !sord_iter_end(iter) &&
+ (!port_class || lilv_port_is_a(plugin, port, port_class));
+ sord_iter_free(iter);
+
+ if (found) {
+ return port;
+ }
+ }
+
+ return NULL;
+}
+
+LILV_API uint32_t
+lilv_plugin_get_latency_port_index(const LilvPlugin* plugin)
+{
+ const LilvPort* prop_port = lilv_plugin_get_port_by_property(
+ plugin, plugin->world->uris.lv2_reportsLatency);
+ const LilvPort* des_port = lilv_plugin_get_port_by_property(
+ plugin, plugin->world->uris.lv2_latency);
+ if (prop_port) {
+ return prop_port->index;
+ } else if (des_port) {
+ return des_port->index;
+ } else {
+ return (uint32_t)-1;
+ }
+}
+
+LILV_API bool
+lilv_plugin_has_feature(const LilvPlugin* plugin,
+ const LilvNode* feature)
+{
+ lilv_plugin_load_if_necessary(plugin);
+ const SordNode* predicates[] = { plugin->world->uris.lv2_requiredFeature,
+ plugin->world->uris.lv2_optionalFeature,
+ NULL };
+
+ for (const SordNode** pred = predicates; *pred; ++pred) {
+ if (lilv_world_ask_internal(
+ plugin->world, plugin->plugin_uri->node, *pred, feature->node)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+LILV_API LilvNodes*
+lilv_plugin_get_supported_features(const LilvPlugin* plugin)
+{
+ LilvNodes* optional = lilv_plugin_get_optional_features(plugin);
+ LilvNodes* required = lilv_plugin_get_required_features(plugin);
+ LilvNodes* result = lilv_nodes_merge(optional, required);
+ lilv_nodes_free(optional);
+ lilv_nodes_free(required);
+ return result;
+}
+
+LILV_API LilvNodes*
+lilv_plugin_get_optional_features(const LilvPlugin* plugin)
+{
+ lilv_plugin_load_if_necessary(plugin);
+ return lilv_world_find_nodes_internal(plugin->world,
+ plugin->plugin_uri->node,
+ plugin->world->uris.lv2_optionalFeature,
+ NULL);
+}
+
+LILV_API LilvNodes*
+lilv_plugin_get_required_features(const LilvPlugin* plugin)
+{
+ lilv_plugin_load_if_necessary(plugin);
+ return lilv_world_find_nodes_internal(plugin->world,
+ plugin->plugin_uri->node,
+ plugin->world->uris.lv2_requiredFeature,
+ NULL);
+}
+
+LILV_API bool
+lilv_plugin_has_extension_data(const LilvPlugin* plugin,
+ const LilvNode* uri)
+{
+ if (!lilv_node_is_uri(uri)) {
+ LILV_ERRORF("Extension data `%s' is not a URI\n",
+ sord_node_get_string(uri->node));
+ return false;
+ }
+
+ lilv_plugin_load_if_necessary(plugin);
+ return lilv_world_ask_internal(
+ plugin->world,
+ plugin->plugin_uri->node,
+ plugin->world->uris.lv2_extensionData,
+ uri->node);
+}
+
+LILV_API LilvNodes*
+lilv_plugin_get_extension_data(const LilvPlugin* plugin)
+{
+ return lilv_plugin_get_value_internal(plugin, plugin->world->uris.lv2_extensionData);
+}
+
+LILV_API const LilvPort*
+lilv_plugin_get_port_by_index(const LilvPlugin* plugin,
+ uint32_t index)
+{
+ lilv_plugin_load_ports_if_necessary(plugin);
+ if (index < plugin->num_ports) {
+ return plugin->ports[index];
+ } else {
+ return NULL;
+ }
+}
+
+LILV_API const LilvPort*
+lilv_plugin_get_port_by_symbol(const LilvPlugin* plugin,
+ const LilvNode* symbol)
+{
+ lilv_plugin_load_ports_if_necessary(plugin);
+ for (uint32_t i = 0; i < plugin->num_ports; ++i) {
+ LilvPort* port = plugin->ports[i];
+ if (lilv_node_equals(port->symbol, symbol)) {
+ return port;
+ }
+ }
+
+ return NULL;
+}
+
+LILV_API LilvNode*
+lilv_plugin_get_project(const LilvPlugin* plugin)
+{
+ lilv_plugin_load_if_necessary(plugin);
+
+ SordNode* lv2_project = sord_new_uri(plugin->world->world,
+ (const uint8_t*)LV2_CORE__project);
+
+ SordIter* projects = lilv_world_query_internal(plugin->world,
+ plugin->plugin_uri->node,
+ lv2_project,
+ NULL);
+
+ sord_node_free(plugin->world->world, lv2_project);
+
+ if (sord_iter_end(projects)) {
+ sord_iter_free(projects);
+ return NULL;
+ }
+
+ const SordNode* project = sord_iter_get_node(projects, SORD_OBJECT);
+
+ sord_iter_free(projects);
+ return lilv_node_new_from_node(plugin->world, project);
+}
+
+static const SordNode*
+lilv_plugin_get_author(const LilvPlugin* plugin)
+{
+ lilv_plugin_load_if_necessary(plugin);
+
+ SordNode* doap_maintainer = sord_new_uri(
+ plugin->world->world, NS_DOAP "maintainer");
+
+ SordIter* maintainers = lilv_world_query_internal(
+ plugin->world,
+ plugin->plugin_uri->node,
+ doap_maintainer,
+ NULL);
+
+ if (sord_iter_end(maintainers)) {
+ sord_iter_free(maintainers);
+
+ LilvNode* project = lilv_plugin_get_project(plugin);
+ if (!project) {
+ sord_node_free(plugin->world->world, doap_maintainer);
+ return NULL;
+ }
+
+ maintainers = lilv_world_query_internal(
+ plugin->world,
+ project->node,
+ doap_maintainer,
+ NULL);
+
+ lilv_node_free(project);
+ }
+
+ sord_node_free(plugin->world->world, doap_maintainer);
+
+ if (sord_iter_end(maintainers)) {
+ sord_iter_free(maintainers);
+ return NULL;
+ }
+
+ const SordNode* author = sord_iter_get_node(maintainers, SORD_OBJECT);
+
+ sord_iter_free(maintainers);
+ return author;
+}
+
+static LilvNode*
+lilv_plugin_get_author_property(const LilvPlugin* plugin, const uint8_t* uri)
+{
+ const SordNode* author = lilv_plugin_get_author(plugin);
+ if (author) {
+ SordWorld* sworld = plugin->world->world;
+ SordNode* pred = sord_new_uri(sworld, uri);
+ LilvNode* ret = lilv_plugin_get_one(plugin, author, pred);
+ sord_node_free(sworld, pred);
+ return ret;
+ }
+ return NULL;
+}
+
+LILV_API LilvNode*
+lilv_plugin_get_author_name(const LilvPlugin* plugin)
+{
+ return lilv_plugin_get_author_property(plugin, NS_FOAF "name");
+}
+
+LILV_API LilvNode*
+lilv_plugin_get_author_email(const LilvPlugin* plugin)
+{
+ return lilv_plugin_get_author_property(plugin, NS_FOAF "mbox");
+}
+
+LILV_API LilvNode*
+lilv_plugin_get_author_homepage(const LilvPlugin* plugin)
+{
+ return lilv_plugin_get_author_property(plugin, NS_FOAF "homepage");
+}
+
+LILV_API bool
+lilv_plugin_is_replaced(const LilvPlugin* plugin)
+{
+ return plugin->replaced;
+}
+
+LILV_API LilvUIs*
+lilv_plugin_get_uis(const LilvPlugin* plugin)
+{
+ lilv_plugin_load_if_necessary(plugin);
+
+ SordNode* ui_ui_node = sord_new_uri(plugin->world->world,
+ (const uint8_t*)LV2_UI__ui);
+ SordNode* ui_binary_node = sord_new_uri(plugin->world->world,
+ (const uint8_t*)LV2_UI__binary);
+
+ LilvUIs* result = lilv_uis_new();
+ SordIter* uis = lilv_world_query_internal(plugin->world,
+ plugin->plugin_uri->node,
+ ui_ui_node,
+ NULL);
+
+ FOREACH_MATCH(uis) {
+ const SordNode* ui = sord_iter_get_node(uis, SORD_OBJECT);
+
+ LilvNode* type = lilv_plugin_get_unique(plugin, ui, plugin->world->uris.rdf_a);
+ LilvNode* binary = lilv_plugin_get_one(plugin, ui, plugin->world->uris.lv2_binary);
+ if (!binary) {
+ binary = lilv_plugin_get_unique(plugin, ui, ui_binary_node);
+ }
+
+ if (sord_node_get_type(ui) != SORD_URI
+ || !lilv_node_is_uri(type)
+ || !lilv_node_is_uri(binary)) {
+ lilv_node_free(binary);
+ lilv_node_free(type);
+ LILV_ERRORF("Corrupt UI <%s>\n", sord_node_get_string(ui));
+ continue;
+ }
+
+ LilvUI* lilv_ui = lilv_ui_new(
+ plugin->world,
+ lilv_node_new_from_node(plugin->world, ui),
+ type,
+ binary);
+
+ zix_tree_insert((ZixTree*)result, lilv_ui, NULL);
+ }
+ sord_iter_free(uis);
+
+ sord_node_free(plugin->world->world, ui_binary_node);
+ sord_node_free(plugin->world->world, ui_ui_node);
+
+ if (lilv_uis_size(result) > 0) {
+ return result;
+ } else {
+ lilv_uis_free(result);
+ return NULL;
+ }
+}
+
+LILV_API LilvNodes*
+lilv_plugin_get_related(const LilvPlugin* plugin, const LilvNode* type)
+{
+ lilv_plugin_load_if_necessary(plugin);
+
+ LilvWorld* const world = plugin->world;
+ LilvNodes* const related = lilv_world_find_nodes_internal(
+ world,
+ NULL,
+ world->uris.lv2_appliesTo,
+ lilv_plugin_get_uri(plugin)->node);
+
+ if (!type) {
+ return related;
+ }
+
+ LilvNodes* matches = lilv_nodes_new();
+ LILV_FOREACH(nodes, i, related) {
+ LilvNode* node = (LilvNode*)lilv_collection_get((ZixTree*)related, i);
+ if (lilv_world_ask_internal(
+ world, node->node, world->uris.rdf_a, type->node)) {
+ zix_tree_insert((ZixTree*)matches,
+ lilv_node_new_from_node(world, node->node),
+ NULL);
+ }
+ }
+
+ lilv_nodes_free(related);
+ return matches;
+}
+
+static SerdEnv*
+new_lv2_env(const SerdNode* base)
+{
+ SerdEnv* env = serd_env_new(base);
+
+#define USTR(s) ((const uint8_t*)(s))
+ serd_env_set_prefix_from_strings(env, USTR("doap"), USTR(LILV_NS_DOAP));
+ serd_env_set_prefix_from_strings(env, USTR("foaf"), USTR(LILV_NS_FOAF));
+ serd_env_set_prefix_from_strings(env, USTR("lv2"), USTR(LILV_NS_LV2));
+ serd_env_set_prefix_from_strings(env, USTR("owl"), USTR(LILV_NS_OWL));
+ 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("xsd"), USTR(LILV_NS_XSD));
+
+ return env;
+}
+
+static void
+maybe_write_prefixes(SerdWriter* writer, SerdEnv* env, FILE* file)
+{
+ fseek(file, 0, SEEK_END);
+ if (ftell(file) == 0) {
+ serd_env_foreach(
+ env, (SerdPrefixSink)serd_writer_set_prefix, writer);
+ } else {
+ fprintf(file, "\n");
+ }
+}
+
+LILV_API void
+lilv_plugin_write_description(LilvWorld* world,
+ const LilvPlugin* plugin,
+ const LilvNode* base_uri,
+ FILE* plugin_file)
+{
+ const LilvNode* subject = lilv_plugin_get_uri(plugin);
+ const uint32_t num_ports = lilv_plugin_get_num_ports(plugin);
+ const SerdNode* base = sord_node_to_serd_node(base_uri->node);
+ SerdEnv* env = new_lv2_env(base);
+
+ SerdWriter* writer = serd_writer_new(
+ SERD_TURTLE,
+ (SerdStyle)(SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED),
+ env,
+ NULL,
+ serd_file_sink,
+ plugin_file);
+
+ // Write prefixes if this is a new file
+ maybe_write_prefixes(writer, env, plugin_file);
+
+ // Write plugin description
+ SordIter* plug_iter = lilv_world_query_internal(
+ world, subject->node, NULL, NULL);
+ sord_write_iter(plug_iter, writer);
+
+ // Write port descriptions
+ for (uint32_t i = 0; i < num_ports; ++i) {
+ const LilvPort* port = plugin->ports[i];
+ SordIter* port_iter = lilv_world_query_internal(
+ world, port->node->node, NULL, NULL);
+ sord_write_iter(port_iter, writer);
+ }
+
+ serd_writer_free(writer);
+ serd_env_free(env);
+}
+
+LILV_API void
+lilv_plugin_write_manifest_entry(LilvWorld* world,
+ const LilvPlugin* plugin,
+ const LilvNode* base_uri,
+ FILE* manifest_file,
+ const char* plugin_file_path)
+{
+ const LilvNode* subject = lilv_plugin_get_uri(plugin);
+ const SerdNode* base = sord_node_to_serd_node(base_uri->node);
+ SerdEnv* env = new_lv2_env(base);
+
+ SerdWriter* writer = serd_writer_new(
+ SERD_TURTLE,
+ (SerdStyle)(SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED),
+ env,
+ NULL,
+ serd_file_sink,
+ manifest_file);
+
+ // Write prefixes if this is a new file
+ maybe_write_prefixes(writer, env, manifest_file);
+
+ // Write manifest entry
+ serd_writer_write_statement(
+ writer, 0, NULL,
+ sord_node_to_serd_node(subject->node),
+ sord_node_to_serd_node(plugin->world->uris.rdf_a),
+ sord_node_to_serd_node(plugin->world->uris.lv2_Plugin), 0, 0);
+
+ const SerdNode file_node = serd_node_from_string(
+ SERD_URI, (const uint8_t*)plugin_file_path);
+ serd_writer_write_statement(
+ writer, 0, NULL,
+ sord_node_to_serd_node(subject->node),
+ sord_node_to_serd_node(plugin->world->uris.rdfs_seeAlso),
+ &file_node, 0, 0);
+
+ serd_writer_free(writer);
+ serd_env_free(env);
+}