diff options
Diffstat (limited to 'plugins/eg-params.lv2')
-rw-r--r-- | plugins/eg-params.lv2/README.txt | 21 | ||||
-rw-r--r-- | plugins/eg-params.lv2/manifest.ttl.in | 7 | ||||
-rw-r--r-- | plugins/eg-params.lv2/params.c | 526 | ||||
-rw-r--r-- | plugins/eg-params.lv2/params.ttl | 126 | ||||
-rw-r--r-- | plugins/eg-params.lv2/state_map.h | 110 | ||||
-rw-r--r-- | plugins/eg-params.lv2/wscript | 65 |
6 files changed, 855 insertions, 0 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..3df6652 --- /dev/null +++ b/plugins/eg-params.lv2/params.c @@ -0,0 +1,526 @@ +/* + LV2 Parameter Example Plugin + Copyright 2014-2016 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/log/logger.h" +#include "lv2/lv2plug.in/ns/ext/midi/midi.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/lv2core/lv2_util.h" + +#include "state_map.h" + +#define MAX_STRING 1024 + +#define EG_PARAMS_URI "http://lv2plug.in/plugins/eg-params" + +#define N_PROPS 9 + +typedef struct { + LV2_URID plugin; + LV2_URID atom_Path; + LV2_URID atom_Sequence; + LV2_URID atom_URID; + LV2_URID atom_eventTransfer; + LV2_URID eg_spring; + LV2_URID midi_Event; + LV2_URID patch_Get; + LV2_URID patch_Set; + LV2_URID patch_Put; + LV2_URID patch_body; + LV2_URID patch_subject; + 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->plugin = map->map(map->handle, EG_PARAMS_URI); + + 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_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_body = map->map(map->handle, LV2_PATCH__body); + uris->patch_subject = map->map(map->handle, LV2_PATCH__subject); + 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_Logger log; + + // Forge for creating atoms + LV2_Atom_Forge forge; + + // Ports + const LV2_Atom_Sequence* in_port; + LV2_Atom_Sequence* out_port; + + // URIs + URIs uris; + + // Plugin state + StateMapItem props[N_PROPS]; + State state; + + // Buffer for making strings from URIDs if unmap is not provided + char urid_buf[12]; +} 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 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 + const char* missing = lv2_features_query( + features, + LV2_LOG__log, &self->log.log, false, + LV2_URID__map, &self->map, true, + LV2_URID__unmap, &self->unmap, false, + NULL); + lv2_log_logger_set_map(&self->log, self->map); + if (missing) { + lv2_log_error(&self->log, "Missing feature <%s>\n", missing); + free(self); + return NULL; + } + + // Map URIs and initialise forge + map_uris(self->map, &self->uris); + lv2_atom_forge_init(&self->forge, self->map); + + // Initialise state dictionary + State* state = &self->state; + state_map_init( + self->props, self->map, self->map->handle, + EG_PARAMS_URI "#int", STATE_MAP_INIT(Int, &state->aint), + EG_PARAMS_URI "#long", STATE_MAP_INIT(Long, &state->along), + EG_PARAMS_URI "#float", STATE_MAP_INIT(Float, &state->afloat), + EG_PARAMS_URI "#double", STATE_MAP_INIT(Double, &state->adouble), + EG_PARAMS_URI "#bool", STATE_MAP_INIT(Bool, &state->abool), + EG_PARAMS_URI "#string", STATE_MAP_INIT(String, &state->astring), + EG_PARAMS_URI "#path", STATE_MAP_INIT(Path, &state->apath), + EG_PARAMS_URI "#lfo", STATE_MAP_INIT(Float, &state->lfo), + EG_PARAMS_URI "#spring", STATE_MAP_INIT(Float, &state->spring), + NULL); + + return (LV2_Handle)self; +} + +static void +cleanup(LV2_Handle instance) +{ + free(instance); +} + +/** Helper function to unmap a URID if possible. */ +static const char* +unmap(Params* self, LV2_URID urid) +{ + if (self->unmap) { + return self->unmap->unmap(self->unmap->handle, urid); + } else { + snprintf(self->urid_buf, sizeof(self->urid_buf), "%u", urid); + return self->urid_buf; + } +} + +static LV2_State_Status +check_type(Params* self, + LV2_URID key, + LV2_URID type, + LV2_URID required_type) +{ + if (type != required_type) { + lv2_log_trace( + &self->log, "Bad type <%s> for <%s> (needs <%s>)\n", + unmap(self, type), + unmap(self, key), + unmap(self, required_type)); + return LV2_STATE_ERR_BAD_TYPE; + } + return LV2_STATE_SUCCESS; +} + +static LV2_State_Status +set_parameter(Params* self, + LV2_URID key, + uint32_t size, + LV2_URID type, + const void* body, + bool from_state) +{ + // Look up property in state dictionary + const StateMapItem* entry = state_map_find(self->props, N_PROPS, key); + if (!entry) { + lv2_log_trace(&self->log, "Unknown parameter <%s>\n", unmap(self, key)); + return LV2_STATE_ERR_NO_PROPERTY; + } + + // Ensure given type matches property's type + if (check_type(self, key, type, entry->value->type)) { + return LV2_STATE_ERR_BAD_TYPE; + } + + // Set property value in state dictionary + lv2_log_trace(&self->log, "Set <%s>\n", entry->uri); + memcpy(entry->value + 1, body, size); + entry->value->size = size; + return LV2_STATE_SUCCESS; +} + +static const LV2_Atom* +get_parameter(Params* self, LV2_URID key) +{ + const StateMapItem* entry = state_map_find(self->props, N_PROPS, key); + if (entry) { + lv2_log_trace(&self->log, "Get <%s>\n", entry->uri); + return entry->value; + } + + lv2_log_trace(&self->log, "Unknown parameter <%s>\n", unmap(self, key)); + return NULL; +} + +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 = (LV2_Atom_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_prop(Params* self, + LV2_State_Map_Path* map_path, + LV2_State_Status* save_status, + LV2_State_Store_Function store, + LV2_State_Handle handle, + LV2_URID key, + const LV2_Atom* value) +{ + LV2_State_Status st; + if (map_path && value->type == self->uris.atom_Path) { + // Map path to abstract path for portable storage + const char* path = (const char*)(value + 1); + char* apath = map_path->abstract_path(map_path->handle, path); + st = store(handle, + key, + apath, + strlen(apath) + 1, + self->uris.atom_Path, + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + free(apath); + } else { + // Store simple property + st = store(handle, + key, + value + 1, + value->size, + value->type, + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + } + + if (save_status && !*save_status) { + *save_status = st; + } +} + +/** + State save method. + + This is used in the usual way when called by the host to save plugin state, + but also internally for writing messages in the audio thread by passing a + "store" function which actually writes the description to the forge. +*/ +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 = (LV2_State_Map_Path*)lv2_features_data( + features, LV2_STATE__mapPath); + + LV2_State_Status st = LV2_STATE_SUCCESS; + for (unsigned i = 0; i < N_PROPS; ++i) { + StateMapItem* prop = &self->props[i]; + store_prop(self, map_path, &st, store, handle, prop->urid, prop->value); + } + + return st; +} + +static void +retrieve_prop(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 + } +} + +/** State restore method. */ +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; + + for (unsigned i = 0; i < N_PROPS; ++i) { + retrieve_prop(self, &st, retrieve, handle, self->props[i].urid); + } + + return st; +} + +static inline bool +subject_is_plugin(Params* self, const LV2_Atom_URID* subject) +{ + // This simple plugin only supports one subject: itself + return (!subject || (subject->atom.type == self->uris.atom_URID && + subject->body == self->uris.plugin)); +} + +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_URID* subject = NULL; + const LV2_Atom_URID* property = NULL; + const LV2_Atom* value = NULL; + lv2_atom_object_get(obj, + uris->patch_subject, (const LV2_Atom**)&subject, + uris->patch_property, (const LV2_Atom**)&property, + uris->patch_value, &value, + 0); + if (!subject_is_plugin(self, subject)) { + lv2_log_error(&self->log, "Set for unknown subject\n"); + } else if (!property) { + lv2_log_error(&self->log, "Set with no property\n"); + } else if (property->atom.type != uris->atom_URID) { + lv2_log_error(&self->log, "Set property is not a URID\n"); + } else { + // Set property to the given value + const LV2_URID key = property->body; + set_parameter(self, key, value->size, value->type, value + 1, false); + } + } else if (obj->body.otype == uris->patch_Get) { + // Get the property of the get message + const LV2_Atom_URID* subject = NULL; + const LV2_Atom_URID* property = NULL; + lv2_atom_object_get(obj, + uris->patch_subject, (const LV2_Atom**)&subject, + uris->patch_property, (const LV2_Atom**)&property, + 0); + if (!subject_is_plugin(self, subject)) { + lv2_log_error(&self->log, "Get with unknown subject\n"); + } else if (!property) { + // Get with no property, emit complete state + lv2_atom_forge_frame_time(&self->forge, ev->time.frames); + LV2_Atom_Forge_Frame pframe; + lv2_atom_forge_object(&self->forge, &pframe, 0, uris->patch_Put); + lv2_atom_forge_key(&self->forge, uris->patch_body); + + LV2_Atom_Forge_Frame bframe; + lv2_atom_forge_object(&self->forge, &bframe, 0, 0); + save(self, write_param_to_forge, &self->forge, 0, NULL); + + lv2_atom_forge_pop(&self->forge, &bframe); + lv2_atom_forge_pop(&self->forge, &pframe); + } else if (property->atom.type != uris->atom_URID) { + lv2_log_error(&self->log, "Get property is not a URID\n"); + } else { + // Get for a specific property + const LV2_URID key = property->body; + const LV2_Atom* value = get_parameter(self, key); + if (value) { + 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_Set); + lv2_atom_forge_key(&self->forge, uris->patch_property); + lv2_atom_forge_urid(&self->forge, property->body); + store_prop(self, NULL, NULL, write_param_to_forge, &self->forge, + uris->patch_value, value); + lv2_atom_forge_pop(&self->forge, &frame); + } + } + } else { + lv2_log_trace(&self->log, "Unknown object type <%s>\n", + unmap(self, obj->body.otype)); + } + } + + if (self->state.spring.body > 0.0f) { + const float spring = self->state.spring.body; + self->state.spring.body = (spring >= 0.001) ? spring - 0.001 : 0.0; + 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->state.spring.body); + + 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..dbcf6aa --- /dev/null +++ b/plugins/eg-params.lv2/params.ttl @@ -0,0 +1,126 @@ +@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:designation lv2:control ; + lv2:index 0 ; + lv2:symbol "in" ; + lv2:name "In" + ] , [ + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message ; + lv2:designation lv2:control ; + 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 "0e0"^^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/state_map.h b/plugins/eg-params.lv2/state_map.h new file mode 100644 index 0000000..cd97a80 --- /dev/null +++ b/plugins/eg-params.lv2/state_map.h @@ -0,0 +1,110 @@ +/* + LV2 State Map + Copyright 2016 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 <stdlib.h> + +/** Entry in an array that serves as a dictionary of properties. */ +typedef struct { + const char* uri; + LV2_URID urid; + LV2_Atom* value; +} StateMapItem; + +/** Comparator for StateMapItems sorted by URID. */ +static int +state_map_cmp(const void* a, const void* b) +{ + const StateMapItem* ka = (const StateMapItem*)a; + const StateMapItem* kb = (const StateMapItem*)b; + if (ka->urid < kb->urid) { + return -1; + } else if (kb->urid < ka->urid) { + return 1; + } + return 0; +} + +/** Helper macro for terse state map initialisation. */ +#define STATE_MAP_INIT(type, ptr) \ + (LV2_ATOM__ ## type), \ + (sizeof(*ptr) - sizeof(LV2_Atom)), \ + (ptr) + +/** + Initialise a state map. + + The variable parameters list must be NULL terminated, and is a sequence of + const char* uri, const char* type, uint32_t size, LV2_Atom* value. The + value must point to a valid atom that resides elsewhere, the state map is + only an index and does not contain actual state values. The macro + STATE_MAP_INIT can be used to make simpler code when state is composed of + standard atom types, for example: + + struct Plugin { + LV2_URID_Map* map; + StateMapItem props[3]; + // ... + }; + + state_map_init( + self->props, self->map, self->map->handle, + PLUG_URI "#gain", STATE_MAP_INIT(Float, &state->gain), + PLUG_URI "#offset", STATE_MAP_INIT(Int, &state->offset), + PLUG_URI "#file", STATE_MAP_INIT(Path, &state->file), + NULL); +*/ +static void +state_map_init(StateMapItem dict[], + LV2_URID_Map* map, + LV2_URID_Map_Handle handle, + /* const char* uri, const char* type, uint32_t size, LV2_Atom* value */ ...) +{ + // Set dict entries from parameters + unsigned i = 0; + va_list args; + va_start(args, handle); + for (const char* uri; (uri = va_arg(args, const char*)); ++i) { + const char* type = va_arg(args, const char*); + const uint32_t size = va_arg(args, uint32_t); + LV2_Atom* const value = va_arg(args, LV2_Atom*); + dict[i].uri = uri; + dict[i].urid = map->map(map->handle, uri); + dict[i].value = value; + dict[i].value->size = size; + dict[i].value->type = map->map(map->handle, type); + } + va_end(args); + + // Sort for fast lookup by URID by state_map_find() + qsort(dict, i, sizeof(StateMapItem), state_map_cmp); +} + +/** + Retrieve an item from a state map by URID. + + This takes O(lg(n)) time, and is useful for implementing generic property + access with little code, for example to respond to patch:Get messages for a + specific property. +*/ +static StateMapItem* +state_map_find(StateMapItem dict[], uint32_t n_entries, LV2_URID urid) +{ + const StateMapItem key = { NULL, urid, NULL }; + return (StateMapItem*)bsearch( + &key, dict, n_entries, sizeof(StateMapItem), state_map_cmp); +} + diff --git a/plugins/eg-params.lv2/wscript b/plugins/eg-params.lv2/wscript new file mode 100644 index 0000000..60dcee4 --- /dev/null +++ b/plugins/eg-params.lv2/wscript @@ -0,0 +1,65 @@ +#!/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') + opt.load('lv2') + autowaf.set_options(opt) + +def configure(conf): + autowaf.display_header('Params Configuration') + conf.load('compiler_c', cache=True) + conf.load('lv2', cache=True) + conf.load('autowaf', cache=True) + + 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 |