aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2015-12-04 12:13:51 -0500
committerDavid Robillard <d@drobilla.net>2015-12-04 12:13:51 -0500
commit12bfee6829a2605fcad66235f6ffdb93bf4fd2cb (patch)
treeb095c11df3023005e55aceabb8c4e97c9331bebe
parentc70109bccac680e32395f29df63510187b68b96d (diff)
downloadlv2-12bfee6829a2605fcad66235f6ffdb93bf4fd2cb.tar.xz
Add parameter example plugin
-rw-r--r--plugins/eg-params.lv2/README.txt21
-rw-r--r--plugins/eg-params.lv2/manifest.ttl.in7
-rw-r--r--plugins/eg-params.lv2/params.c564
-rw-r--r--plugins/eg-params.lv2/params.ttl124
-rw-r--r--plugins/eg-params.lv2/wscript64
-rw-r--r--plugins/wscript3
6 files changed, 782 insertions, 1 deletions
diff --git a/plugins/eg-params.lv2/README.txt b/plugins/eg-params.lv2/README.txt
new file mode 100644
index 0000000..acf90c1
--- /dev/null
+++ b/plugins/eg-params.lv2/README.txt
@@ -0,0 +1,21 @@
+== Params ==
+
+The basic LV2 mechanism for controls is
+http://lv2plug.in/ns/lv2core#ControlPort[lv2:ControlPort], inherited from
+LADSPA. Control ports are problematic because they are not sample accurate,
+support only one type (`float`), and require that plugins poll to know when a
+control has changed.
+
+Parameters can be used instead to address these issues. Parameters can be
+thought of as properties of a plugin instance; they are identified by URI and
+have a value of any type. This deliberately meshes with the concept of plugin
+state defined by the http://lv2plug.in/ns/ext/state[LV2 state extension].
+The state extension allows plugins to save and restore their parameters (along
+with other internal state information, if necessary).
+
+Parameters are accessed and manipulated using messages sent via a sequence
+port. The http://lv2plug.in/ns/ext/patch[LV2 patch extension] defines the
+standard messages for working with parameters. Typically, only two are used
+for simple plugins: http://lv2plug.in/ns/ext/patch#Set[patch:Set] sets a
+parameter to some value, and http://lv2plug.in/ns/ext/patch#Get[patch:Get]
+requests that the plugin send a description of its parameters.
diff --git a/plugins/eg-params.lv2/manifest.ttl.in b/plugins/eg-params.lv2/manifest.ttl.in
new file mode 100644
index 0000000..913de7c
--- /dev/null
+++ b/plugins/eg-params.lv2/manifest.ttl.in
@@ -0,0 +1,7 @@
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+<http://lv2plug.in/plugins/eg-params>
+ a lv2:Plugin ;
+ lv2:binary <params@LIB_EXT@> ;
+ rdfs:seeAlso <params.ttl> .
diff --git a/plugins/eg-params.lv2/params.c b/plugins/eg-params.lv2/params.c
new file mode 100644
index 0000000..186c858
--- /dev/null
+++ b/plugins/eg-params.lv2/params.c
@@ -0,0 +1,564 @@
+/*
+ LV2 Parameter Example Plugin
+ Copyright 2014-2015 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 <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef __cplusplus
+# include <stdbool.h>
+#endif
+
+#include "lv2/lv2plug.in/ns/ext/atom/forge.h"
+#include "lv2/lv2plug.in/ns/ext/atom/util.h"
+#include "lv2/lv2plug.in/ns/ext/patch/patch.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"
+
+#include "lv2/lv2plug.in/ns/ext/log/logger.h"
+#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
+#include "lv2/lv2plug.in/ns/ext/state/state.h"
+
+#define MAX_STRING 1024
+
+#define EG_PARAMS_URI "http://lv2plug.in/plugins/eg-params"
+#define EG_PARAMS__float EG_PARAMS_URI "#float"
+
+typedef struct {
+ LV2_URID atom_Path;
+ LV2_URID atom_Sequence;
+ LV2_URID atom_URID;
+ LV2_URID atom_eventTransfer;
+ LV2_URID eg_int;
+ LV2_URID eg_long;
+ LV2_URID eg_float;
+ LV2_URID eg_double;
+ LV2_URID eg_bool;
+ LV2_URID eg_string;
+ LV2_URID eg_path;
+ LV2_URID eg_lfo;
+ LV2_URID eg_spring;
+ LV2_URID midi_Event;
+ LV2_URID patch_Get;
+ LV2_URID patch_Set;
+ LV2_URID patch_Put;
+ LV2_URID patch_property;
+ LV2_URID patch_value;
+} URIs;
+
+typedef struct {
+ LV2_Atom_Int aint;
+ LV2_Atom_Long along;
+ LV2_Atom_Float afloat;
+ LV2_Atom_Double adouble;
+ LV2_Atom_Bool abool;
+ LV2_Atom astring;
+ char string[MAX_STRING];
+ LV2_Atom apath;
+ char path[MAX_STRING];
+ LV2_Atom_Float lfo;
+ LV2_Atom_Float spring;
+} State;
+
+static inline void
+map_uris(LV2_URID_Map* map, URIs* uris)
+{
+ uris->atom_Path = map->map(map->handle, LV2_ATOM__Path);
+ uris->atom_Sequence = map->map(map->handle, LV2_ATOM__Sequence);
+ uris->atom_URID = map->map(map->handle, LV2_ATOM__URID);
+ uris->atom_eventTransfer = map->map(map->handle, LV2_ATOM__eventTransfer);
+ uris->eg_int = map->map(map->handle, EG_PARAMS_URI "#int");
+ uris->eg_long = map->map(map->handle, EG_PARAMS_URI "#long");
+ uris->eg_float = map->map(map->handle, EG_PARAMS_URI "#float");
+ uris->eg_double = map->map(map->handle, EG_PARAMS_URI "#double");
+ uris->eg_bool = map->map(map->handle, EG_PARAMS_URI "#bool");
+ uris->eg_string = map->map(map->handle, EG_PARAMS_URI "#string");
+ uris->eg_path = map->map(map->handle, EG_PARAMS_URI "#path");
+ uris->eg_lfo = map->map(map->handle, EG_PARAMS_URI "#lfo");
+ uris->eg_spring = map->map(map->handle, EG_PARAMS_URI "#spring");
+ uris->midi_Event = map->map(map->handle, LV2_MIDI__MidiEvent);
+ uris->patch_Get = map->map(map->handle, LV2_PATCH__Get);
+ uris->patch_Set = map->map(map->handle, LV2_PATCH__Set);
+ uris->patch_Put = map->map(map->handle, LV2_PATCH__Put);
+ uris->patch_property = map->map(map->handle, LV2_PATCH__property);
+ uris->patch_value = map->map(map->handle, LV2_PATCH__value);
+}
+
+enum {
+ PARAMS_IN = 0,
+ PARAMS_OUT = 1
+};
+
+typedef struct {
+ // Features
+ LV2_URID_Map* map;
+ LV2_URID_Unmap* unmap;
+ LV2_Log_Log* log;
+
+ // Forge for creating atoms
+ LV2_Atom_Forge forge;
+
+ // Logger convenience API
+ LV2_Log_Logger logger;
+
+ // Ports
+ const LV2_Atom_Sequence* in_port;
+ LV2_Atom_Sequence* out_port;
+
+ // URIs
+ URIs uris;
+
+ // Plugin state
+ State state;
+
+ float spring;
+ float lfo;
+} Params;
+
+static void
+connect_port(LV2_Handle instance,
+ uint32_t port,
+ void* data)
+{
+ Params* self = (Params*)instance;
+ switch (port) {
+ case PARAMS_IN:
+ self->in_port = (const LV2_Atom_Sequence*)data;
+ break;
+ case PARAMS_OUT:
+ self->out_port = (LV2_Atom_Sequence*)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static inline int
+get_features(const LV2_Feature* const* features, ...)
+{
+ va_list args;
+ va_start(args, features);
+
+ const char* uri = NULL;
+ while ((uri = va_arg(args, const char*))) {
+ void** data = va_arg(args, void**);
+ bool required = va_arg(args, int);
+ bool found = false;
+
+ for (int i = 0; features[i]; ++i) {
+ if (!strcmp(features[i]->URI, uri)) {
+ *data = features[i]->data;
+ found = true;
+ break;
+ }
+ }
+
+ if (required && !found) {
+ fprintf(stderr, "Missing required feature <%s>\n", uri);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+#define INIT_PARAM(atype, param) { \
+ (param)->atom.type = (atype); \
+ (param)->atom.size = sizeof((param)->body); \
+}
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor,
+ double rate,
+ const char* path,
+ const LV2_Feature* const* features)
+{
+ // Allocate instance
+ Params* self = (Params*)calloc(1, sizeof(Params));
+ if (!self) {
+ return NULL;
+ }
+
+ // Get host features
+ if (get_features(features,
+ LV2_URID__map, &self->map, true,
+ LV2_URID__unmap, &self->unmap, false,
+ LV2_LOG__log, &self->log, false,
+ NULL)) {
+ free(self);
+ return NULL;
+ }
+
+ // Map URIs and initialise forge/logger
+ map_uris(self->map, &self->uris);
+ lv2_atom_forge_init(&self->forge, self->map);
+ lv2_log_logger_init(&self->logger, self->map, self->log);
+
+ // Initialize state
+ INIT_PARAM(self->forge.Int, &self->state.aint);
+ INIT_PARAM(self->forge.Long, &self->state.along);
+ INIT_PARAM(self->forge.Float, &self->state.afloat);
+ INIT_PARAM(self->forge.Double, &self->state.adouble);
+ INIT_PARAM(self->forge.Bool, &self->state.abool);
+ INIT_PARAM(self->forge.Float, &self->state.spring);
+ INIT_PARAM(self->forge.Float, &self->state.lfo);
+ self->state.astring.type = self->forge.String;
+ self->state.astring.size = 0;
+ self->state.apath.type = self->forge.Path;
+ self->state.apath.size = 0;
+
+ return (LV2_Handle)self;
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ free(instance);
+}
+
+static LV2_State_Status
+check_param(Params* self,
+ LV2_URID key,
+ LV2_URID required_key,
+ LV2_URID type,
+ LV2_URID required_type)
+{
+ if (key == required_key) {
+ if (type == required_type) {
+ return LV2_STATE_SUCCESS;
+ } else if (self->unmap) {
+ lv2_log_trace(
+ &self->logger, "Bad type <%s> for <%s> (needs <%s>)\n",
+ self->unmap->unmap(self->unmap->handle, type),
+ self->unmap->unmap(self->unmap->handle, key),
+ self->unmap->unmap(self->unmap->handle, required_type));
+ } else {
+ lv2_log_trace(&self->logger, "Bad type for parameter %d\n", key);
+ }
+ return LV2_STATE_ERR_BAD_TYPE;
+ }
+
+ return LV2_STATE_ERR_NO_PROPERTY;
+}
+
+static LV2_State_Status
+set_parameter(Params* self,
+ LV2_URID key,
+ uint32_t size,
+ LV2_URID type,
+ const void* body,
+ bool from_state)
+{
+ const URIs* uris = &self->uris;
+ const LV2_Atom_Forge* forge = &self->forge;
+ LV2_State_Status st = LV2_STATE_SUCCESS;
+
+ if (!(st = check_param(self, key, uris->eg_int, type, forge->Int))) {
+ self->state.aint.body = *(const int32_t*)body;
+ lv2_log_trace(&self->logger, "Set int %d\n", self->state.aint.body);
+ } else if (!(st = check_param(self, key, uris->eg_long, type, forge->Long))) {
+ self->state.along.body = *(const int64_t*)body;
+ lv2_log_trace(&self->logger, "Set long %ld\n", self->state.along.body);
+ } else if (!(st = check_param(self, key, uris->eg_float, type, forge->Float))) {
+ self->state.afloat.body = *(const float*)body;
+ lv2_log_trace(&self->logger, "Set float %f\n", self->state.afloat.body);
+ } else if (!(st = check_param(self, key, uris->eg_double, type, forge->Double))) {
+ self->state.adouble.body = *(const double*)body;
+ lv2_log_trace(&self->logger, "Set double %f\n", self->state.adouble.body);
+ } else if (!(st = check_param(self, key, uris->eg_bool, type, forge->Bool))) {
+ self->state.abool.body = *(const int32_t*)body;
+ lv2_log_trace(&self->logger, "Set bool %d\n", self->state.abool.body);
+ } else if (!(st = check_param(self, key, uris->eg_string, type, forge->String))) {
+ if (size <= MAX_STRING) {
+ memcpy(self->state.string, body, size);
+ self->state.astring.size = size;
+ lv2_log_trace(&self->logger, "Set string %s\n", self->state.string);
+ } else {
+ lv2_log_error(&self->logger, "Insufficient space for string\n");
+ return LV2_STATE_ERR_NO_SPACE;
+ }
+ } else if (!(st = check_param(self, key, uris->eg_path, type, forge->Path))) {
+ if (size <= MAX_STRING) {
+ memcpy(self->state.path, body, size);
+ self->state.apath.size = size;
+ lv2_log_trace(&self->logger, "Set path %s\n", self->state.path);
+ } else {
+ lv2_log_error(&self->logger, "Insufficient space for path\n");
+ return LV2_STATE_ERR_NO_SPACE;
+ }
+ } else if (!(st = check_param(self, key, uris->eg_spring, type, forge->Float))) {
+ self->spring = *(const float*)body;
+ lv2_log_trace(&self->logger, "Set spring %f\n", *(const float*)body);
+ } else if (key == uris->eg_lfo) {
+ if (!from_state) {
+ st = LV2_STATE_ERR_UNKNOWN;
+ lv2_log_error(&self->logger, "Attempt to set non-writable LFO\n");
+ } else {
+ self->state.lfo.body = *(const float*)body;
+ lv2_log_trace(&self->logger, "Set LFO %f\n", self->state.lfo.body);
+ }
+ } else if (self->unmap) {
+ st = LV2_STATE_ERR_NO_PROPERTY;
+ lv2_log_trace(&self->logger, "Unknown parameter <%s>\n",
+ self->unmap->unmap(self->unmap->handle, key));
+ } else {
+ st = LV2_STATE_ERR_NO_PROPERTY;
+ lv2_log_trace(&self->logger, "Unknown parameter %d\n", key);
+ }
+
+ return st;
+}
+
+static LV2_State_Status
+write_param_to_forge(LV2_State_Handle handle,
+ uint32_t key,
+ const void* value,
+ size_t size,
+ uint32_t type,
+ uint32_t flags)
+{
+ LV2_Atom_Forge* forge = handle;
+
+ if (!lv2_atom_forge_key(forge, key) ||
+ !lv2_atom_forge_atom(forge, size, type) ||
+ !lv2_atom_forge_write(forge, value, size)) {
+ return LV2_STATE_ERR_UNKNOWN;
+ }
+
+ return LV2_STATE_SUCCESS;
+}
+
+static void
+store_param(Params* self,
+ LV2_State_Status* save_status,
+ LV2_State_Store_Function store,
+ LV2_State_Handle handle,
+ LV2_URID key,
+ const LV2_Atom* value)
+{
+ const LV2_State_Status st = store(handle,
+ key,
+ value + 1,
+ value->size,
+ value->type,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ if (!*save_status) {
+ *save_status = st;
+ }
+}
+
+static LV2_State_Status
+store_state(Params* self,
+ LV2_State_Store_Function store,
+ LV2_State_Handle handle,
+ uint32_t flags,
+ LV2_State_Map_Path* map_path)
+{
+ const URIs* uris = &self->uris;
+ const State* state = &self->state;
+ LV2_State_Status st = LV2_STATE_SUCCESS;
+
+ // Store simple properties
+ store_param(self, &st, store, handle, uris->eg_int, &state->aint.atom);
+ store_param(self, &st, store, handle, uris->eg_long, &state->along.atom);
+ store_param(self, &st, store, handle, uris->eg_float, &state->afloat.atom);
+ store_param(self, &st, store, handle, uris->eg_double, &state->adouble.atom);
+ store_param(self, &st, store, handle, uris->eg_bool, &state->abool.atom);
+ store_param(self, &st, store, handle, uris->eg_string, &state->astring);
+ store_param(self, &st, store, handle, uris->eg_spring, &state->spring.atom);
+ store_param(self, &st, store, handle, uris->eg_lfo, &state->lfo.atom);
+
+ if (map_path) {
+ // Map path to abstract path for portable storage
+ char* apath = map_path->abstract_path(map_path->handle, state->path);
+ st = store(handle,
+ self->uris.eg_path,
+ apath,
+ strlen(apath) + 1,
+ self->uris.atom_Path,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+ free(apath);
+ } else {
+ store_param(self, &st, store, handle, uris->eg_path, &state->apath);
+ }
+
+ return st;
+}
+
+static LV2_State_Status
+save(LV2_Handle instance,
+ LV2_State_Store_Function store,
+ LV2_State_Handle handle,
+ uint32_t flags,
+ const LV2_Feature* const* features)
+{
+ Params* self = (Params*)instance;
+ LV2_State_Map_Path* map_path = NULL;
+ get_features(features, LV2_STATE__mapPath, &map_path, true, NULL);
+ return store_state(self, store, handle, flags, map_path);
+}
+
+static void
+retrieve_param(Params* self,
+ LV2_State_Status* restore_status,
+ LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle handle,
+ LV2_URID key)
+{
+ // Retrieve value from saved state
+ size_t vsize;
+ uint32_t vtype;
+ uint32_t vflags;
+ const void* value = retrieve(handle, key, &vsize, &vtype, &vflags);
+
+ // Set plugin instance state
+ const LV2_State_Status st = value
+ ? set_parameter(self, key, vsize, vtype, value, true)
+ : LV2_STATE_ERR_NO_PROPERTY;
+
+ if (!*restore_status) {
+ *restore_status = st; // Set status if there has been no error yet
+ }
+}
+
+static LV2_State_Status
+restore(LV2_Handle instance,
+ LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle handle,
+ uint32_t flags,
+ const LV2_Feature* const* features)
+{
+ Params* self = (Params*)instance;
+ LV2_State_Status st = LV2_STATE_SUCCESS;
+
+ // Retrieve simple properties
+ retrieve_param(self, &st, retrieve, handle, self->uris.eg_int);
+ retrieve_param(self, &st, retrieve, handle, self->uris.eg_long);
+ retrieve_param(self, &st, retrieve, handle, self->uris.eg_float);
+ retrieve_param(self, &st, retrieve, handle, self->uris.eg_double);
+ retrieve_param(self, &st, retrieve, handle, self->uris.eg_bool);
+ retrieve_param(self, &st, retrieve, handle, self->uris.eg_string);
+ retrieve_param(self, &st, retrieve, handle, self->uris.eg_path);
+ retrieve_param(self, &st, retrieve, handle, self->uris.eg_spring);
+ retrieve_param(self, &st, retrieve, handle, self->uris.eg_lfo);
+
+ return st;
+}
+
+static void
+run(LV2_Handle instance, uint32_t sample_count)
+{
+ Params* self = (Params*)instance;
+ URIs* uris = &self->uris;
+
+ // Initially, self->out_port contains a Chunk with size set to capacity
+ // Set up forge to write directly to output port
+ const uint32_t out_capacity = self->out_port->atom.size;
+ lv2_atom_forge_set_buffer(&self->forge,
+ (uint8_t*)self->out_port,
+ out_capacity);
+
+ // Start a sequence in the output port.
+ LV2_Atom_Forge_Frame out_frame;
+ lv2_atom_forge_sequence_head(&self->forge, &out_frame, 0);
+
+ // Read incoming events
+ LV2_ATOM_SEQUENCE_FOREACH(self->in_port, ev) {
+ const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body;
+ if (obj->body.otype == uris->patch_Set) {
+ // Get the property and value of the set message
+ const LV2_Atom* property = NULL;
+ const LV2_Atom* value = NULL;
+ lv2_atom_object_get(obj,
+ uris->patch_property, &property,
+ uris->patch_value, &value,
+ 0);
+ if (!property) {
+ lv2_log_error(&self->logger,
+ "patch:Set message with no property\n");
+ } else if (property->type != uris->atom_URID) {
+ lv2_log_error(&self->logger,
+ "patch:Set property is not a URID\n");
+ } else {
+ const LV2_URID key = ((const LV2_Atom_URID*)property)->body;
+ set_parameter(self, key, value->size, value->type, value + 1, false);
+ }
+ } else if (obj->body.otype == uris->patch_Get) {
+ // Received a get message, emit our state (probably to UI)
+ lv2_atom_forge_frame_time(&self->forge, ev->time.frames);
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_object(&self->forge, &frame, 0, uris->patch_Put);
+ store_state(self, write_param_to_forge, &self->forge, 0, NULL);
+ lv2_atom_forge_pop(&self->forge, &frame);
+ } else {
+ if (self->unmap) {
+ lv2_log_trace(&self->logger,
+ "Unknown object type <%s>\n",
+ self->unmap->unmap(self->unmap->handle, obj->body.otype));
+ } else {
+ lv2_log_trace(&self->logger,
+ "Unknown object type %d\n", obj->body.otype);
+ }
+ }
+ }
+
+ if (self->spring > 0.01f) {
+ self->spring -= 0.001;
+ lv2_atom_forge_frame_time(&self->forge, 0);
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_object(&self->forge, &frame, 0, uris->patch_Set);
+
+ lv2_atom_forge_key(&self->forge, uris->patch_property);
+ lv2_atom_forge_urid(&self->forge, uris->eg_spring);
+ lv2_atom_forge_key(&self->forge, uris->patch_value);
+ lv2_atom_forge_float(&self->forge, self->spring);
+
+ lv2_atom_forge_pop(&self->forge, &frame);
+ }
+
+ lv2_atom_forge_pop(&self->forge, &out_frame);
+}
+
+static const void*
+extension_data(const char* uri)
+{
+ static const LV2_State_Interface state = { save, restore };
+ if (!strcmp(uri, LV2_STATE__interface)) {
+ return &state;
+ }
+ return NULL;
+}
+
+static const LV2_Descriptor descriptor = {
+ EG_PARAMS_URI,
+ instantiate,
+ connect_port,
+ NULL, // activate,
+ run,
+ NULL, // deactivate,
+ cleanup,
+ extension_data
+};
+
+LV2_SYMBOL_EXPORT const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ return (index == 0) ? &descriptor : NULL;
+}
diff --git a/plugins/eg-params.lv2/params.ttl b/plugins/eg-params.lv2/params.ttl
new file mode 100644
index 0000000..9777382
--- /dev/null
+++ b/plugins/eg-params.lv2/params.ttl
@@ -0,0 +1,124 @@
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix param: <http://lv2plug.in/ns/ext/parameters#> .
+@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
+@prefix plug: <http://lv2plug.in/plugins/eg-params#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix state: <http://lv2plug.in/ns/ext/state#> .
+@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+# An existing parameter or RDF property can be used as a parameter. The LV2
+# parameters extension <http://lv2plug.in/ns/ext/parameters> defines many
+# common audio parameters. Where possible, existing parameters should be used
+# so hosts can intelligently control plugins.
+
+# If no suitable parameter exists, one can be defined for the plugin like so:
+
+plug:int
+ a lv2:Parameter ;
+ rdfs:label "int" ;
+ rdfs:range atom:Int .
+
+plug:long
+ a lv2:Parameter ;
+ rdfs:label "long" ;
+ rdfs:range atom:Long .
+
+plug:float
+ a lv2:Parameter ;
+ rdfs:label "float" ;
+ rdfs:range atom:Float .
+
+plug:double
+ a lv2:Parameter ;
+ rdfs:label "double" ;
+ rdfs:range atom:Double .
+
+plug:bool
+ a lv2:Parameter ;
+ rdfs:label "bool" ;
+ rdfs:range atom:Bool .
+
+plug:string
+ a lv2:Parameter ;
+ rdfs:label "string" ;
+ rdfs:range atom:String .
+
+plug:path
+ a lv2:Parameter ;
+ rdfs:label "path" ;
+ rdfs:range atom:Path .
+
+plug:lfo
+ a lv2:Parameter ;
+ rdfs:label "LFO" ;
+ rdfs:range atom:Float ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 .
+
+plug:spring
+ a lv2:Parameter ;
+ rdfs:label "spring" ;
+ rdfs:range atom:Float .
+
+# Most of the plugin description is similar to the others we have seen, but
+# this plugin has only two ports, for receiving and sending messages used to
+# manipulate and access parameters.
+<http://lv2plug.in/plugins/eg-params>
+ a lv2:Plugin ,
+ lv2:UtilityPlugin ;
+ doap:name "Example Parameters" ;
+ doap:license <http://opensource.org/licenses/isc> ;
+ lv2:project <http://lv2plug.in/ns/lv2> ;
+ lv2:requiredFeature urid:map ;
+ lv2:optionalFeature lv2:hardRTCapable ,
+ state:loadDefaultState ;
+ lv2:extensionData state:interface ;
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "in" ;
+ lv2:name "In"
+ ] , [
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "out" ;
+ lv2:name "Out"
+ ] ;
+# The plugin must list all parameters that can be written (e.g. changed by the
+# user) as patch:writable:
+ patch:writable plug:int ,
+ plug:long ,
+ plug:float ,
+ plug:double ,
+ plug:bool ,
+ plug:string ,
+ plug:path ,
+ plug:spring ;
+# Similarly, parameters that may change internally must be listed as patch:readable,
+# meaning to host should watch for changes to the parameter's value:
+ patch:readable plug:lfo ,
+ plug:spring ;
+# Parameters map directly to properties of the plugin's state. So, we can
+# specify initial parameter values with the state:state property. The
+# state:loadDefaultState feature (required above) requires that the host loads
+# the default state after instantiation but before running the plugin.
+ state:state [
+ plug:int 0 ;
+ plug:long "0"^^xsd:long ;
+ plug:float 0.1234 ;
+ plug:double "0.0"^^xsd:double ;
+ plug:bool false ;
+ plug:string "Hello, world" ;
+ plug:path <params.ttl> ;
+ plug:spring 0.0 ;
+ plug:lfo 0.0
+ ] .
diff --git a/plugins/eg-params.lv2/wscript b/plugins/eg-params.lv2/wscript
new file mode 100644
index 0000000..a26c856
--- /dev/null
+++ b/plugins/eg-params.lv2/wscript
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+from waflib.extras import autowaf as autowaf
+import re
+
+# Variables for 'waf dist'
+APPNAME = 'eg-params.lv2'
+VERSION = '1.0.0'
+
+# Mandatory variables
+top = '.'
+out = 'build'
+
+def options(opt):
+ opt.load('compiler_c')
+ autowaf.set_options(opt)
+
+def configure(conf):
+ conf.load('compiler_c')
+ autowaf.configure(conf)
+ autowaf.set_c99_mode(conf)
+ autowaf.display_header('Params Configuration')
+
+ if not autowaf.is_child():
+ autowaf.check_pkg(conf, 'lv2', atleast_version='1.12.1', uselib_store='LV2')
+
+ autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR)
+ print('')
+
+def build(bld):
+ bundle = 'eg-params.lv2'
+
+ # Make a pattern for shared objects without the 'lib' prefix
+ module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN)
+ module_ext = module_pat[module_pat.rfind('.'):]
+
+ # Build manifest.ttl by substitution (for portable lib extension)
+ bld(features = 'subst',
+ source = 'manifest.ttl.in',
+ target = '%s/%s' % (bundle, 'manifest.ttl'),
+ install_path = '${LV2DIR}/%s' % bundle,
+ LIB_EXT = module_ext)
+
+ # Copy other data files to build bundle (build/eg-params.lv2)
+ for i in ['params.ttl']:
+ bld(features = 'subst',
+ is_copy = True,
+ source = i,
+ target = '%s/%s' % (bundle, i),
+ install_path = '${LV2DIR}/%s' % bundle)
+
+ # Use LV2 headers from parent directory if building as a sub-project
+ includes = ['.']
+ if autowaf.is_child:
+ includes += ['../..']
+
+ # Build plugin library
+ obj = bld(features = 'c cshlib',
+ source = 'params.c',
+ name = 'params',
+ target = '%s/params' % bundle,
+ install_path = '${LV2DIR}/%s' % bundle,
+ use = 'LV2',
+ includes = includes)
+ obj.env.cshlib_PATTERN = module_pat
diff --git a/plugins/wscript b/plugins/wscript
index db641b4..0cfd387 100644
--- a/plugins/wscript
+++ b/plugins/wscript
@@ -23,7 +23,8 @@ def build(bld):
'eg-fifths.lv2',
'eg-metro.lv2',
'eg-sampler.lv2',
- 'eg-scope.lv2']:
+ 'eg-scope.lv2',
+ 'eg-params.lv2']:
files += bld.path.ant_glob('%s/*.txt' % i)
files += bld.path.ant_glob('%s/manifest.ttl*' % i)
files += bld.path.ant_glob('%s/*.ttl' % i)