[LV2] [PATCH] lv2apply: Add options and URID map features
Alexandros Theodotou
alex at zrythm.org
Sat Apr 17 12:53:53 PDT 2021
---
utils/lv2apply.c | 152 +++++++++++++++++++++++++++---
utils/symap.c | 235 +++++++++++++++++++++++++++++++++++++++++++++++
utils/symap.h | 66 +++++++++++++
wscript | 5 +-
4 files changed, 443 insertions(+), 15 deletions(-)
create mode 100644 utils/symap.c
create mode 100644 utils/symap.h
diff --git a/utils/lv2apply.c b/utils/lv2apply.c
index e042999..3006a22 100644
--- a/utils/lv2apply.c
+++ b/utils/lv2apply.c
@@ -1,5 +1,6 @@
/*
Copyright 2007-2019 David Robillard <d at drobilla.net>
+ Copyright 2021 Alexandros Theodotou <alex at zrythm.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -16,7 +17,13 @@
#include "lilv/lilv.h"
+#include "lv2/atom/atom.h"
+#include "lv2/buf-size/buf-size.h"
#include "lv2/core/lv2.h"
+#include "lv2/options/options.h"
+#include "lv2/parameters/parameters.h"
+#include "lv2/urid/urid.h"
+#include "symap.h"
#include <math.h>
#include <sndfile.h>
@@ -33,6 +40,10 @@
# define LILV_LOG_FUNC(fmt, arg1)
#endif
+#ifndef ARRAY_SIZE
+# define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
+#endif
+
/** Control port value set from the command line */
typedef struct Param {
const char* sym; ///< Port symbol
@@ -52,23 +63,66 @@ typedef struct {
bool optional; ///< True iff connection optional
} Port;
+/** Features */
+typedef struct {
+ LV2_Feature map_feature;
+ LV2_Feature unmap_feature;
+ LV2_Options_Option options[4];
+ LV2_Feature options_feature;
+} LV2ApplyFeatures;
+
+/** URIDs */
+typedef struct {
+ LV2_URID atom_Float;
+ LV2_URID atom_Int;
+ LV2_URID bufsz_maxBlockLength;
+ LV2_URID bufsz_minBlockLength;
+ LV2_URID param_sampleRate;
+} LV2ApplyURIDs;
+
/** Application state */
typedef struct {
- LilvWorld* world;
- const LilvPlugin* plugin;
- LilvInstance* instance;
- const char* in_path;
- const char* out_path;
- SNDFILE* in_file;
- SNDFILE* out_file;
- unsigned n_params;
- Param* params;
- unsigned n_ports;
- unsigned n_audio_in;
- unsigned n_audio_out;
- Port* ports;
+ LilvWorld* world;
+ const LilvPlugin* plugin;
+ LilvInstance* instance;
+ const char* in_path;
+ const char* out_path;
+ SNDFILE* in_file;
+ SNDFILE* out_file;
+ unsigned n_params;
+ Param* params;
+ unsigned n_ports;
+ unsigned n_audio_in;
+ unsigned n_audio_out;
+ Port* ports;
+ LV2ApplyFeatures features;
+ const LV2_Feature** feature_list;
+ Symap* symap; ///< URI map
+ LV2_URID_Map map; ///< URI => Int map
+ LV2_URID_Unmap unmap; ///< Int => URI map
+ LV2ApplyURIDs urids; ///< URIDs
+ int block_length;
+ float sample_rate; ///< Sample rate
} LV2Apply;
+static LV2_URID
+map_uri(LV2_URID_Map_Handle handle,
+ const char* uri)
+{
+ LV2Apply* lv2apply = (LV2Apply*)handle;
+ const LV2_URID id = symap_map(lv2apply->symap, uri);
+ return id;
+}
+
+static const char*
+unmap_uri(LV2_URID_Unmap_Handle handle,
+ LV2_URID urid)
+{
+ LV2Apply* lv2apply = (LV2Apply*)handle;
+ const char* uri = symap_unmap(lv2apply->symap, urid);
+ return uri;
+}
+
static int
fatal(LV2Apply* self, int status, const char* fmt, ...);
@@ -121,6 +175,8 @@ cleanup(int status, LV2Apply* self)
lilv_world_free(self->world);
free(self->ports);
free(self->params);
+ symap_free(self->symap);
+ free(self->feature_list);
return status;
}
@@ -227,6 +283,63 @@ print_usage(int status)
return status;
}
+static void
+init_urids(LV2Apply* self)
+{
+ self->symap = symap_new();
+ self->urids.atom_Float = symap_map(self->symap, LV2_ATOM__Float);
+ self->urids.atom_Int = symap_map(self->symap, LV2_ATOM__Int);
+ self->urids.bufsz_maxBlockLength = symap_map(self->symap, LV2_BUF_SIZE__maxBlockLength);
+ self->urids.bufsz_minBlockLength = symap_map(self->symap, LV2_BUF_SIZE__minBlockLength);
+ self->urids.param_sampleRate = symap_map(self->symap, LV2_PARAMETERS__sampleRate);
+}
+
+static void
+init_feature(LV2_Feature* const dest, const char* const URI, void* data)
+{
+ dest->URI = URI;
+ dest->data = data;
+}
+
+static void
+init_features(LV2Apply* self)
+{
+ /* Build options array to pass to plugin */
+ const LV2_Options_Option options[ARRAY_SIZE(self->features.options)] = {
+ { LV2_OPTIONS_INSTANCE, 0, self->urids.param_sampleRate,
+ sizeof(float), self->urids.atom_Float, &self->sample_rate },
+ { LV2_OPTIONS_INSTANCE, 0, self->urids.bufsz_minBlockLength,
+ sizeof(int32_t), self->urids.atom_Int, &self->block_length },
+ { LV2_OPTIONS_INSTANCE, 0, self->urids.bufsz_maxBlockLength,
+ sizeof(int32_t), self->urids.atom_Int, &self->block_length },
+ { LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL }
+ };
+ memcpy(self->features.options, options, sizeof(self->features.options));
+
+ init_feature(&self->features.options_feature, LV2_OPTIONS__options, self->features.options);
+
+ self->map.handle = self;
+ self->map.map = map_uri;
+ init_feature(&self->features.map_feature, LV2_URID__map, &self->map);
+
+ self->unmap.handle = self;
+ self->unmap.unmap = unmap_uri;
+ init_feature(&self->features.unmap_feature, LV2_URID__unmap, &self->unmap);
+
+ /* Build feature list for passing to plugins */
+ const LV2_Feature* const features[] = {
+ &self->features.map_feature,
+ &self->features.unmap_feature,
+ &self->features.options_feature,
+ NULL
+ };
+ self->feature_list = calloc(1, sizeof(features));
+ if (!self->feature_list) {
+ fatal (self, 10, "Failed to allocate feature list\n");
+ }
+ memcpy(self->feature_list, features, sizeof(features));
+}
+
int
main(int argc, char** argv)
{
@@ -310,6 +423,14 @@ main(int argc, char** argv)
in_fmt.channels,
self.n_audio_in);
}
+ self.block_length = 1;
+ self.sample_rate = in_fmt.samplerate;
+
+ /* Init URIDs */
+ init_urids(&self);
+
+ /* Init features */
+ init_features(&self);
/* Set control values */
for (unsigned i = 0; i < self.n_params; ++i) {
@@ -336,7 +457,10 @@ main(int argc, char** argv)
const uint32_t n_ports = lilv_plugin_get_num_ports(plugin);
float in_buf[self.n_audio_in > 0 ? self.n_audio_in : 1];
float out_buf[self.n_audio_out > 0 ? self.n_audio_out : 1];
- self.instance = lilv_plugin_instantiate(self.plugin, in_fmt.samplerate, NULL);
+ self.instance = lilv_plugin_instantiate(self.plugin, in_fmt.samplerate, self.feature_list);
+ if (!self.instance) {
+ return fatal (&self, 11, "Failed to instantiate plugin\n");
+ }
for (uint32_t p = 0, i = 0, o = 0; p < n_ports; ++p) {
if (self.ports[p].type == TYPE_CONTROL) {
lilv_instance_connect_port(self.instance, p, &self.ports[p].value);
diff --git a/utils/symap.c b/utils/symap.c
new file mode 100644
index 0000000..71a46f0
--- /dev/null
+++ b/utils/symap.c
@@ -0,0 +1,235 @@
+/*
+ Copyright 2011-2014 David Robillard <d at 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 "symap.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ @file symap.c Implementation of Symap, a basic symbol map (string interner).
+
+ This implementation is primitive, but has some desirable qualities: good
+ (O(lg(n)) lookup performance for already-mapped symbols, minimal space
+ overhead, extremely fast (O(1)) reverse mapping (ID to string), simple code,
+ no dependencies.
+
+ The tradeoff is that mapping new symbols may be quite slow. In other words,
+ this implementation is ideal for use cases with a relatively limited set of
+ symbols, or where most symbols are mapped early. It will not fare so well
+ with very dynamic sets of symbols. For that, you're better off with a
+ tree-based implementation (and the associated space cost, especially if you
+ need reverse mapping).
+*/
+
+struct SymapImpl {
+ /**
+ Unsorted array of strings, such that the symbol for ID i is found
+ at symbols[i - 1].
+ */
+ char** symbols;
+
+ /**
+ Array of IDs, sorted by corresponding string in `symbols`.
+ */
+ uint32_t* index;
+
+ /**
+ Number of symbols (number of items in `symbols` and `index`).
+ */
+ uint32_t size;
+};
+
+Symap*
+symap_new(void)
+{
+ Symap* map = (Symap*)malloc(sizeof(Symap));
+ map->symbols = NULL;
+ map->index = NULL;
+ map->size = 0;
+ return map;
+}
+
+void
+symap_free(Symap* map)
+{
+ if (!map) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < map->size; ++i) {
+ free(map->symbols[i]);
+ }
+
+ free(map->symbols);
+ free(map->index);
+ free(map);
+}
+
+static char*
+symap_strdup(const char* str)
+{
+ const size_t len = strlen(str);
+ char* copy = (char*)malloc(len + 1);
+ memcpy(copy, str, len + 1);
+ return copy;
+}
+
+/**
+ Return the index into map->index (not the ID) corresponding to `sym`,
+ or the index where a new entry for `sym` should be inserted.
+*/
+static uint32_t
+symap_search(const Symap* map, const char* sym, bool* exact)
+{
+ *exact = false;
+ if (map->size == 0) {
+ return 0; // Empty map, insert at 0
+ } else if (strcmp(map->symbols[map->index[map->size - 1] - 1], sym) < 0) {
+ return map->size; // Greater than last element, append
+ }
+
+ uint32_t lower = 0;
+ uint32_t upper = map->size - 1;
+ uint32_t i = upper;
+ int cmp = 0;
+
+ while (upper >= lower) {
+ i = lower + ((upper - lower) / 2);
+ cmp = strcmp(map->symbols[map->index[i] - 1], sym);
+
+ if (cmp == 0) {
+ *exact = true;
+ return i;
+ } else if (cmp > 0) {
+ if (i == 0) {
+ break; // Avoid underflow
+ }
+ upper = i - 1;
+ } else {
+ lower = ++i;
+ }
+ }
+
+ assert(!*exact || strcmp(map->symbols[map->index[i] - 1], sym) > 0);
+ return i;
+}
+
+uint32_t
+symap_try_map(Symap* map, const char* sym)
+{
+ bool exact = false;
+ const uint32_t index = symap_search(map, sym, &exact);
+ if (exact) {
+ assert(!strcmp(map->symbols[map->index[index]], sym));
+ return map->index[index];
+ }
+
+ return 0;
+}
+
+uint32_t
+symap_map(Symap* map, const char* sym)
+{
+ bool exact = false;
+ const uint32_t index = symap_search(map, sym, &exact);
+ if (exact) {
+ assert(!strcmp(map->symbols[map->index[index] - 1], sym));
+ return map->index[index];
+ }
+
+ const uint32_t id = ++map->size;
+ char* const str = symap_strdup(sym);
+
+ /* Append new symbol to symbols array */
+ map->symbols = (char**)realloc(map->symbols, map->size * sizeof(str));
+ map->symbols[id - 1] = str;
+
+ /* Insert new index element into sorted index */
+ map->index = (uint32_t*)realloc(map->index, map->size * sizeof(uint32_t));
+ if (index < map->size - 1) {
+ memmove(map->index + index + 1,
+ map->index + index,
+ (map->size - index - 1) * sizeof(uint32_t));
+ }
+
+ map->index[index] = id;
+
+ return id;
+}
+
+const char*
+symap_unmap(Symap* map, uint32_t id)
+{
+ if (id == 0) {
+ return NULL;
+ } else if (id <= map->size) {
+ return map->symbols[id - 1];
+ }
+ return NULL;
+}
+
+#ifdef STANDALONE
+
+#include <stdio.h>
+
+static void
+symap_dump(Symap* map)
+{
+ fprintf(stderr, "{\n");
+ for (uint32_t i = 0; i < map->size; ++i) {
+ fprintf(stderr, "\t%u = %s\n",
+ map->index[i], map->symbols[map->index[i] - 1]);
+ }
+ fprintf(stderr, "}\n");
+}
+
+int
+main()
+{
+ #define N_SYMS 5
+ char* syms[N_SYMS] = {
+ "hello", "bonjour", "goodbye", "aloha", "salut"
+ };
+
+ Symap* map = symap_new();
+ for (int i = 0; i < N_SYMS; ++i) {
+ if (symap_try_map(map, syms[i])) {
+ fprintf(stderr, "error: Symbol already mapped\n");
+ return 1;
+ }
+
+ const uint32_t id = symap_map(map, syms[i]);
+ if (strcmp(map->symbols[id - 1], syms[i])) {
+ fprintf(stderr, "error: Corrupt symbol table\n");
+ return 1;
+ }
+
+ if (symap_map(map, syms[i]) != id) {
+ fprintf(stderr, "error: Remapped symbol to a different ID\n");
+ return 1;
+ }
+
+ symap_dump(map);
+ }
+
+ symap_free(map);
+ return 0;
+}
+
+#endif /* STANDALONE */
diff --git a/utils/symap.h b/utils/symap.h
new file mode 100644
index 0000000..25b12cd
--- /dev/null
+++ b/utils/symap.h
@@ -0,0 +1,66 @@
+/*
+ Copyright 2011-2014 David Robillard <d at 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.
+*/
+
+/**
+ @file symap.h API for Symap, a basic symbol map (string interner).
+
+ Particularly useful for implementing LV2 URI mapping.
+
+ @see <a href="http://lv2plug.in/ns/ext/urid">LV2 URID</a>
+*/
+
+#ifndef SYMAP_H
+#define SYMAP_H
+
+#include <stdint.h>
+
+typedef struct SymapImpl Symap;
+
+/**
+ Create a new symbol map.
+*/
+Symap*
+symap_new(void);
+
+/**
+ Free a symbol map.
+*/
+void
+symap_free(Symap* map);
+
+/**
+ Map a string to a symbol ID if it is already mapped, otherwise return 0.
+*/
+uint32_t
+symap_try_map(Symap* map, const char* sym);
+
+/**
+ Map a string to a symbol ID.
+
+ Note that 0 is never a valid symbol ID.
+*/
+uint32_t
+symap_map(Symap* map, const char* sym);
+
+/**
+ Unmap a symbol ID back to a symbol, or NULL if no such ID exists.
+
+ Note that 0 is never a valid symbol ID.
+*/
+const char*
+symap_unmap(Symap* map, uint32_t id);
+
+#endif /* SYMAP_H */
diff --git a/wscript b/wscript
index 9810fed..f337db4 100644
--- a/wscript
+++ b/wscript
@@ -289,8 +289,11 @@ def configure(conf):
def build_util(bld, name, defines, libs=''):
+ util_src = [ name + '.c' ]
+ if name == 'utils/lv2apply':
+ util_src += [ 'utils/symap.c' ]
obj = bld(features = 'c cprogram',
- source = name + '.c',
+ source = util_src,
includes = ['.', 'include', './src', './utils'],
use = 'liblilv',
uselib = 'SERD SORD SRATOM LV2 ' + libs,
--
2.31.1
More information about the Devel
mailing list