From 12bfee6829a2605fcad66235f6ffdb93bf4fd2cb Mon Sep 17 00:00:00 2001 From: David Robillard Date: Fri, 4 Dec 2015 12:13:51 -0500 Subject: Add parameter example plugin --- plugins/eg-params.lv2/README.txt | 21 ++ plugins/eg-params.lv2/manifest.ttl.in | 7 + plugins/eg-params.lv2/params.c | 564 ++++++++++++++++++++++++++++++++++ plugins/eg-params.lv2/params.ttl | 124 ++++++++ plugins/eg-params.lv2/wscript | 64 ++++ plugins/wscript | 3 +- 6 files changed, 782 insertions(+), 1 deletion(-) create mode 100644 plugins/eg-params.lv2/README.txt create mode 100644 plugins/eg-params.lv2/manifest.ttl.in create mode 100644 plugins/eg-params.lv2/params.c create mode 100644 plugins/eg-params.lv2/params.ttl create mode 100644 plugins/eg-params.lv2/wscript 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: . +@prefix rdfs: . + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . 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 + + 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 +#include +#include +#include +#ifndef __cplusplus +# include +#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: . +@prefix doap: . +@prefix lv2: . +@prefix param: . +@prefix patch: . +@prefix plug: . +@prefix rdfs: . +@prefix state: . +@prefix urid: . +@prefix xsd: . + +# An existing parameter or RDF property can be used as a parameter. The LV2 +# parameters extension 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. + + a lv2:Plugin , + lv2:UtilityPlugin ; + doap:name "Example Parameters" ; + doap:license ; + lv2:project ; + 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 ; + 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) -- cgit v1.2.1