[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