= Programming LV2 Plugins =
David Robillard <d@drobilla.net>
:Author Initials: DER
:toc:
:website: http://lv2plug.in/
:doctype: book

== Introduction ==

This is a series of well-documented example plugins that demonstrate the various features of LV2.
Starting with the most basic plugin possible,
each adds new functionality and explains the features used from a high level perspective.

API and vocabulary reference documentation explains details,
but not the ``big picture''.
This book is intended to complement the reference documentation by providing good reference implementations of plugins,
while also conveying a higher-level understanding of LV2.

The chapters/plugins are arranged so that each builds incrementally on its predecessor.
Reading this book front to back is a good way to become familiar with modern LV2 programming.
The reader is expected to be familiar with C, but otherwise no special knowledge is required;
the first plugin describes the basics in detail.

This book is compiled from plugin source code into a single document for pleasant reading and ease of reference.
Each chapter corresponds to executable plugin code which can be found in the +plugins+ directory of the LV2 distribution.
If you prefer to read actual source code, all the content here is also available in the source code as comments.

== Simple Amplifier ==

This plugin is a simple example of a basic LV2 plugin with no additional features.
It has audio ports which contain an array of `float`,
and a control port which contains a single `float`.

LV2 plugins are defined in two parts: code and data.
The code is written in C, or any C compatible language such as C++.
Static data is described separately in the human and machine friendly http://www.w3.org/TeamSubmission/turtle/[Turtle] syntax.

Generally, the goal is to keep code minimal,
and describe as much as possible in the static data.
There are several advantages to this approach:

 * Hosts can discover and inspect plugins without loading or executing any plugin code.
 * Plugin data can be used from a wide range of generic tools like scripting languages and command line utilities.
 * The standard data model allows the use of existing vocabularies to describe plugins and related information.
 * The language is extensible, so authors may describe any data without requiring changes to the LV2 specification.
 * Labels and documentation are translatable, and available to hosts for display in user interfaces.

=== manifest.ttl.in ===


LV2 plugins are installed in a ``bundle'', a directory with a standard 
structure.  Each bundle has a Turtle file named `manifest.ttl` which lists 
the contents of the bundle. 

Hosts typically read the manifest of every installed bundle to discover 
plugins on start-up, so it should be as small as possible for performance 
reasons.  Details that are only useful if the host chooses to load the plugin 
are stored in other files and linked to from `manifest.ttl`. 

==== URIs ==== 

LV2 makes use of URIs as globally-unique identifiers for resources.  For 
example, the ID of the plugin described here is 
`<http://lv2plug.in/plugins/eg-amp>`.  Note that URIs are only used as 
identifiers and don't necessarily imply that something can be accessed at 
that address on the web (though that may be the case). 

==== Namespace Prefixes ==== 

Turtle files contain many URIs, but prefixes can be defined to improve 
readability.  For example, with the `lv2:` prefix below, `lv2:Plugin` can be 
written instead of `<http://lv2plug.in/ns/lv2core#Plugin>`.

[source,turtle]
---------------
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
---------------


==== Describing a Plugin ====




Turtle files contain a set of ``statements'' which describe resources. 
This file contains 3 statements: 
[options="header"] 
|================================================================ 
| Subject                             | Predicate    | Object 
| <http://lv2plug.in/plugins/eg-amp>  | a            | lv2:Plugin 
| <http://lv2plug.in/plugins/eg-amp>  | lv2:binary   | <amp.so> 
| <http://lv2plug.in/plugins/eg-amp>  | rdfs:seeAlso | <amp.ttl> 
|================================================================




Firstly, `<http://lv2plug.in/plugins/eg-amp>` is an LV2 plugin:

[source,turtle]
---------------
<http://lv2plug.in/plugins/eg-amp> a lv2:Plugin .
---------------


The predicate ```a`'' is a Turtle shorthand for `rdf:type`.




The binary of that plugin can be found at `<amp.ext>`:

[source,turtle]
---------------
<http://lv2plug.in/plugins/eg-amp> lv2:binary <amp@LIB_EXT@> .
---------------


This file is a template; the token `@LIB_EXT@` is replaced by the build 
system with the appropriate extension for the current platform before 
installation.  For example, in the output `manifest.ttl`, the binary would be 
listed as `<amp.so>`.  Relative URIs in manifests are relative to the bundle 
directory, so this refers to a binary with the given name in the same 
directory as this manifest.




Finally, more information about this plugin can be found in `<amp.ttl>`:

[source,turtle]
---------------
<http://lv2plug.in/plugins/eg-amp> rdfs:seeAlso <amp.ttl> .
---------------


==== Abbreviation ==== 

This file shows these statements individually for instructive purposes, but 
the subject `<http://lv2plug.in/plugins/eg-amp>` is repetitive.  Turtle 
allows the semicolon to be used as a delimiter that repeats the previous 
subject.  For example, this manifest would more realistically be written like 
so:

[source,turtle]
---------------
<http://lv2plug.in/plugins/eg-amp>
	a lv2:Plugin ;
	lv2:binary <amp@LIB_EXT@>  ;
	rdfs:seeAlso <amp.ttl> .
---------------
=== amp.ttl ===


The full description of the plugin is in this file, which is linked to from 
`manifest.ttl`.  This is done so the host only needs to scan the relatively 
small `manifest.ttl` files to quickly discover all plugins.

[source,turtle]
---------------
@prefix doap:  <http://usefulinc.com/ns/doap#> .
@prefix lv2:   <http://lv2plug.in/ns/lv2core#> .
@prefix rdf:   <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs:  <http://www.w3.org/2000/01/rdf-schema#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
---------------


First the type of the plugin is described.  All plugins must explicitly list 
`lv2:Plugin` as a type.  A more specific type should also be given, where 
applicable, so hosts can present a nicer UI for loading plugins.  Note that 
this URI is the identifier of the plugin, so if it does not match the one in 
`manifest.ttl`, the host will not discover the plugin data at all.

[source,turtle]
---------------
<http://lv2plug.in/plugins/eg-amp>
	a lv2:Plugin ,
		lv2:AmplifierPlugin ;
---------------


Plugins are associated with a project, where common information like 
developers, home page, and so on are described.  This plugin is part of the 
LV2 project, which has URI <http://lv2plug.in/ns/lv2>, and is described 
elsewhere.  Typical plugin collections will describe the project in 
manifest.ttl

[source,turtle]
---------------
	lv2:project <http://lv2plug.in/ns/lv2> ;
---------------


Every plugin must have a name, described with the doap:name property. 
Translations to various languages can be added by putting a language tag 
after strings as shown.

[source,turtle]
---------------
	doap:name "Simple Amplifier" ,
		"简单放大器"@zh ,
		"Einfacher Verstärker"@de ,
		"Simple Amplifier"@en-gb ,
		"Amplificador Simple"@es ,
		"Amplificateur de Base"@fr ,
		"Amplificatore Semplice"@it ,
		"簡単なアンプ"@jp ,
		"Просто Усилитель"@ru ;
	doap:license <http://opensource.org/licenses/isc> ;
	lv2:optionalFeature lv2:hardRTCapable ;
	lv2:port [
---------------


Every port must have at least two types, one that specifies direction 
(lv2:InputPort or lv2:OutputPort), and another to describe the data type. 
This port is a lv2:ControlPort, which means it contains a single float.

[source,turtle]
---------------
		a lv2:InputPort ,
			lv2:ControlPort ;
		lv2:index 0 ;
		lv2:symbol "gain" ;
		lv2:name "Gain" ,
			"收益"@zh ,
			"Verstärkung"@de ,
			"Gain"@en-gb ,
			"Aumento"@es ,
			"Gain"@fr ,
			"Guadagno"@it ,
			"利益"@jp ,
			"Увеличение"@ru ;
---------------


An lv2:ControlPort should always describe its default value, and usually a 
minimum and maximum value.  Defining a range is not strictly required, but 
should be done wherever possible to aid host support, particularly for UIs.

[source,turtle]
---------------
		lv2:default 0.0 ;
		lv2:minimum -90.0 ;
		lv2:maximum 24.0 ;
---------------


Ports can describe units and control detents to allow better UI generation 
and host automation.

[source,turtle]
---------------
		units:unit units:db ;
		lv2:scalePoint [
			rdfs:label "+5" ;
			rdf:value 5.0
		] , [
			rdfs:label "0" ;
			rdf:value 0.0
		] , [
			rdfs:label "-5" ;
			rdf:value -5.0
		] , [
			rdfs:label "-10" ;
			rdf:value -10.0
		]
	] , [
		a lv2:AudioPort ,
			lv2:InputPort ;
		lv2:index 1 ;
		lv2:symbol "in" ;
		lv2:name "In"
	] , [
		a lv2:AudioPort ,
			lv2:OutputPort ;
		lv2:index 2 ;
		lv2:symbol "out" ;
		lv2:name "Out"
	] .
---------------
=== amp.c ===




LV2 headers are based on the URI of the specification they come from, so a
consistent convention can be used even for unofficial extensions.  The URI
of the core LV2 specification is <http://lv2plug.in/ns/lv2core>, by
replacing `http:/` with `lv2` any header in the specification bundle can be
included, in this case `lv2.h`.

[source,c]
----------
#include "lv2/core/lv2.h"
----------


Include standard C headers

[source,c]
----------
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
----------


The URI is the identifier for a plugin, and how the host associates this
implementation in code with its description in data.  In this plugin it is
only used once in the code, but defining the plugin URI at the top of the
file is a good convention to follow.  If this URI does not match that used
in the data files, the host will fail to load the plugin.

[source,c]
----------
#define AMP_URI "http://lv2plug.in/plugins/eg-amp"
----------


In code, ports are referred to by index.  An enumeration of port indices
should be defined for readability.

[source,c]
----------
typedef enum {
	AMP_GAIN   = 0,
	AMP_INPUT  = 1,
	AMP_OUTPUT = 2
} PortIndex;
----------


Every plugin defines a private structure for the plugin instance.  All data
associated with a plugin instance is stored here, and is available to
every instance method.  In this simple plugin, only port buffers need to be
stored, since there is no additional instance data.

[source,c]
----------
typedef struct {
	// Port buffers
	const float* gain;
	const float* input;
	float*       output;
} Amp;
----------


The `instantiate()` function is called by the host to create a new plugin
instance.  The host passes the plugin descriptor, sample rate, and bundle
path for plugins that need to load additional resources (e.g. waveforms).
The features parameter contains host-provided features defined in LV2
extensions, but this simple plugin does not use any.

This function is in the ``instantiation'' threading class, so no other
methods on this instance will be called concurrently with it.

[source,c]
----------
static LV2_Handle
instantiate(const LV2_Descriptor*     descriptor,
            double                    rate,
            const char*               bundle_path,
            const LV2_Feature* const* features)
{
	Amp* amp = (Amp*)calloc(1, sizeof(Amp));

	return (LV2_Handle)amp;
}
----------


The `connect_port()` method is called by the host to connect a particular
port to a buffer.  The plugin must store the data location, but data may not
be accessed except in run().

This method is in the ``audio'' threading class, and is called in the same
context as run().

[source,c]
----------
static void
connect_port(LV2_Handle instance,
             uint32_t   port,
             void*      data)
{
	Amp* amp = (Amp*)instance;

	switch ((PortIndex)port) {
	case AMP_GAIN:
		amp->gain = (const float*)data;
		break;
	case AMP_INPUT:
		amp->input = (const float*)data;
		break;
	case AMP_OUTPUT:
		amp->output = (float*)data;
		break;
	}
}
----------


The `activate()` method is called by the host to initialise and prepare the
plugin instance for running.  The plugin must reset all internal state
except for buffer locations set by `connect_port()`.  Since this plugin has
no other internal state, this method does nothing.

This method is in the ``instantiation'' threading class, so no other
methods on this instance will be called concurrently with it.

[source,c]
----------
static void
activate(LV2_Handle instance)
{
}
----------


Define a macro for converting a gain in dB to a coefficient.

[source,c]
----------
#define DB_CO(g) ((g) > -90.0f ? powf(10.0f, (g) * 0.05f) : 0.0f)
----------


The `run()` method is the main process function of the plugin.  It processes
a block of audio in the audio context.  Since this plugin is
`lv2:hardRTCapable`, `run()` must be real-time safe, so blocking (e.g. with
a mutex) or memory allocation are not allowed.

[source,c]
----------
static void
run(LV2_Handle instance, uint32_t n_samples)
{
	const Amp* amp = (const Amp*)instance;

	const float        gain   = *(amp->gain);
	const float* const input  = amp->input;
	float* const       output = amp->output;

	const float coef = DB_CO(gain);

	for (uint32_t pos = 0; pos < n_samples; pos++) {
		output[pos] = input[pos] * coef;
	}
}
----------


The `deactivate()` method is the counterpart to `activate()`, and is called by
the host after running the plugin.  It indicates that the host will not call
`run()` again until another call to `activate()` and is mainly useful for more
advanced plugins with ``live'' characteristics such as those with auxiliary
processing threads.  As with `activate()`, this plugin has no use for this
information so this method does nothing.

This method is in the ``instantiation'' threading class, so no other
methods on this instance will be called concurrently with it.

[source,c]
----------
static void
deactivate(LV2_Handle instance)
{
}
----------


Destroy a plugin instance (counterpart to `instantiate()`).

This method is in the ``instantiation'' threading class, so no other
methods on this instance will be called concurrently with it.

[source,c]
----------
static void
cleanup(LV2_Handle instance)
{
	free(instance);
}
----------


The `extension_data()` function returns any extension data supported by the
plugin.  Note that this is not an instance method, but a function on the
plugin descriptor.  It is usually used by plugins to implement additional
interfaces.  This plugin does not have any extension data, so this function
returns NULL.

This method is in the ``discovery'' threading class, so no other functions
or methods in this plugin library will be called concurrently with it.

[source,c]
----------
static const void*
extension_data(const char* uri)
{
	return NULL;
}
----------


Every plugin must define an `LV2_Descriptor`.  It is best to define
descriptors statically to avoid leaking memory and non-portable shared
library constructors and destructors to clean up properly.

[source,c]
----------
static const LV2_Descriptor descriptor = {
	AMP_URI,
	instantiate,
	connect_port,
	activate,
	run,
	deactivate,
	cleanup,
	extension_data
};
----------


The `lv2_descriptor()` function is the entry point to the plugin library.  The
host will load the library and call this function repeatedly with increasing
indices to find all the plugins defined in the library.  The index is not an
indentifier, the URI of the returned descriptor is used to determine the
identify of the plugin.

This method is in the ``discovery'' threading class, so no other functions
or methods in this plugin library will be called concurrently with it.

[source,c]
----------
LV2_SYMBOL_EXPORT
const LV2_Descriptor*
lv2_descriptor(uint32_t index)
{
	switch (index) {
	case 0:  return &descriptor;
	default: return NULL;
	}
}
----------
== MIDI Gate ==

This plugin demonstrates:

 * Receiving MIDI input

 * Processing audio based on MIDI events with sample accuracy

 * Supporting MIDI programs which the host can control/automate, or present a
   user interface for with human readable labels

=== manifest.ttl.in ===


The manifest.ttl file follows the same template as the previous example.

[source,turtle]
---------------
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix ui:   <http://lv2plug.in/ns/extensions/ui#> .

<http://lv2plug.in/plugins/eg-midigate>
	a lv2:Plugin ;
	lv2:binary <midigate@LIB_EXT@> ;
	rdfs:seeAlso <midigate.ttl> .
---------------
=== midigate.ttl ===


The same set of namespace prefixes with two additions for LV2 extensions this 
plugin uses: atom and urid.

[source,turtle]
---------------
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .

<http://lv2plug.in/plugins/eg-midigate>
	a lv2:Plugin ;
	doap:name "Example MIDI Gate" ;
	doap:license <http://opensource.org/licenses/isc> ;
	lv2:project <http://lv2plug.in/ns/lv2> ;
	lv2:requiredFeature urid:map ;
	lv2:optionalFeature lv2:hardRTCapable ;
---------------


This plugin has three ports.  There is an audio input and output as before, 
as well as a new AtomPort.  An AtomPort buffer contains an Atom, which is a 
generic container for any type of data.  In this case, we want to receive 
MIDI events, so the (mandatory) +atom:bufferType+ is atom:Sequence, which is 
a series of events with time stamps. 

Events themselves are also generic and can contain any type of data, but in 
this case we are only interested in MIDI events.  The (optional) 
+atom:supports+ property describes which event types are supported.  Though 
not required, this information should always be given so the host knows what 
types of event it can expect the plugin to understand. 

The (optional) +lv2:designation+ of this port is +lv2:control+, which 
indicates that this is the "main" control port where the host should send 
events it expects to configure the plugin, in this case changing the MIDI 
program.  This is necessary since it is possible to have several MIDI input 
ports, though typically it is best to have one.

[source,turtle]
---------------
	lv2:port [
		a lv2:InputPort ,
			atom:AtomPort ;
		atom:bufferType atom:Sequence ;
		atom:supports midi:MidiEvent ;
		lv2:designation lv2:control ;
		lv2:index 0 ;
		lv2:symbol "control" ;
		lv2:name "Control"
	] , [
		a lv2:AudioPort ,
			lv2:InputPort ;
		lv2:index 1 ;
		lv2:symbol "in" ;
		lv2:name "In"
	] , [
		a lv2:AudioPort ,
			lv2:OutputPort ;
		lv2:index 2 ;
		lv2:symbol "out" ;
		lv2:name "Out"
	] .
---------------
=== midigate.c ===
[source,c]
----------
#include "lv2/atom/atom.h"
#include "lv2/atom/util.h"
#include "lv2/core/lv2.h"
#include "lv2/core/lv2_util.h"
#include "lv2/log/log.h"
#include "lv2/log/logger.h"
#include "lv2/midi/midi.h"
#include "lv2/urid/urid.h"

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MIDIGATE_URI "http://lv2plug.in/plugins/eg-midigate"

typedef enum {
	MIDIGATE_CONTROL = 0,
	MIDIGATE_IN      = 1,
	MIDIGATE_OUT     = 2
} PortIndex;

typedef struct {
	// Port buffers
	const LV2_Atom_Sequence* control;
	const float*             in;
	float*                   out;

	// Features
	LV2_URID_Map*  map;
	LV2_Log_Logger logger;

	struct {
		LV2_URID midi_MidiEvent;
	} uris;

	unsigned n_active_notes;
	unsigned program;  // 0 = normal, 1 = inverted
} Midigate;

static LV2_Handle
instantiate(const LV2_Descriptor*     descriptor,
            double                    rate,
            const char*               bundle_path,
            const LV2_Feature* const* features)
{
	Midigate* self = (Midigate*)calloc(1, sizeof(Midigate));
	if (!self) {
		return NULL;
	}

	// Scan host features for URID map
	const char* missing = lv2_features_query(
		features,
		LV2_LOG__log,  &self->logger.log, false,
		LV2_URID__map, &self->map,        true,
		NULL);
	lv2_log_logger_set_map(&self->logger, self->map);
	if (missing) {
		lv2_log_error(&self->logger, "Missing feature <%s>\n", missing);
		free(self);
		return NULL;
	}

	self->uris.midi_MidiEvent = self->map->map(
		self->map->handle, LV2_MIDI__MidiEvent);

	return (LV2_Handle)self;
}

static void
connect_port(LV2_Handle instance,
             uint32_t   port,
             void*      data)
{
	Midigate* self = (Midigate*)instance;

	switch ((PortIndex)port) {
	case MIDIGATE_CONTROL:
		self->control = (const LV2_Atom_Sequence*)data;
		break;
	case MIDIGATE_IN:
		self->in = (const float*)data;
		break;
	case MIDIGATE_OUT:
		self->out = (float*)data;
		break;
	}
}

static void
activate(LV2_Handle instance)
{
	Midigate* self = (Midigate*)instance;
	self->n_active_notes = 0;
	self->program        = 0;
}
----------


A function to write a chunk of output, to be called from run().  If the gate
is high, then the input will be passed through for this chunk, otherwise
silence is written.

[source,c]
----------
static void
write_output(Midigate* self, uint32_t offset, uint32_t len)
{
	const bool active = (self->program == 0)
		? (self->n_active_notes > 0)
		: (self->n_active_notes == 0);
	if (active) {
		memcpy(self->out + offset, self->in + offset, len * sizeof(float));
	} else {
		memset(self->out + offset, 0, len * sizeof(float));
	}
}
----------


This plugin works through the cycle in chunks starting at offset zero.  The
+offset+ represents the current time within this this cycle, so
the output from 0 to +offset+ has already been written.

MIDI events are read in a loop.  In each iteration, the number of active
notes (on note on and note off) or the program (on program change) is
updated, then the output is written up until the current event time.  Then
+offset+ is updated and the next event is processed.  After the loop the
final chunk from the last event to the end of the cycle is emitted.

There is currently no standard way to describe MIDI programs in LV2, so the
host has no way of knowing that these programs exist and should be presented
to the user.  A future version of LV2 will address this shortcoming.

This pattern of iterating over input events and writing output along the way
is a common idiom for writing sample accurate output based on event input.

Note that this simple example simply writes input or zero for each sample
based on the gate.  A serious implementation would need to envelope the
transition to avoid aliasing.

[source,c]
----------
static void
run(LV2_Handle instance, uint32_t sample_count)
{
	Midigate* self   = (Midigate*)instance;
	uint32_t  offset = 0;

	LV2_ATOM_SEQUENCE_FOREACH(self->control, ev) {
		if (ev->body.type == self->uris.midi_MidiEvent) {
			const uint8_t* const msg = (const uint8_t*)LV2_ATOM_BODY_CONST(ev);
			switch (lv2_midi_message_type(msg)) {
			case LV2_MIDI_MSG_NOTE_ON:
				++self->n_active_notes;
				break;
			case LV2_MIDI_MSG_NOTE_OFF:
				if (self->n_active_notes > 0) {
					--self->n_active_notes;
				}
				break;
			case LV2_MIDI_MSG_CONTROLLER:
				if (msg[1] == LV2_MIDI_CTL_ALL_NOTES_OFF) {
					self->n_active_notes = 0;
				}
				break;
			case LV2_MIDI_MSG_PGM_CHANGE:
				if (msg[1] == 0 || msg[1] == 1) {
					self->program = msg[1];
				}
				break;
			default: break;
			}
		}

		write_output(self, offset, ev->time.frames - offset);
		offset = (uint32_t)ev->time.frames;
	}

	write_output(self, offset, sample_count - offset);
}
----------


We have no resources to free on deactivation.
Note that the next call to activate will re-initialise the state, namely
self->n_active_notes, so there is no need to do so here.

[source,c]
----------
static void
deactivate(LV2_Handle instance)
{}

static void
cleanup(LV2_Handle instance)
{
	free(instance);
}
----------


This plugin also has no extension data to return.

[source,c]
----------
static const void*
extension_data(const char* uri)
{
	return NULL;
}

static const LV2_Descriptor descriptor = {
	MIDIGATE_URI,
	instantiate,
	connect_port,
	activate,
	run,
	deactivate,
	cleanup,
	extension_data
};

LV2_SYMBOL_EXPORT
const LV2_Descriptor*
lv2_descriptor(uint32_t index)
{
	switch (index) {
	case 0:
		return &descriptor;
	default:
		return NULL;
	}
}
----------
== Fifths ==

This plugin demonstrates simple MIDI event reading and writing.

=== manifest.ttl.in ===
[source,turtle]
---------------
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix ui:   <http://lv2plug.in/ns/extensions/ui#> .

<http://lv2plug.in/plugins/eg-fifths>
	a lv2:Plugin ;
	lv2:binary <fifths@LIB_EXT@> ;
	rdfs:seeAlso <fifths.ttl> .
---------------
=== fifths.ttl ===
[source,turtle]
---------------
@prefix atom:  <http://lv2plug.in/ns/ext/atom#> .
@prefix doap:  <http://usefulinc.com/ns/doap#> .
@prefix lv2:   <http://lv2plug.in/ns/lv2core#> .
@prefix urid:  <http://lv2plug.in/ns/ext/urid#> .
@prefix midi:  <http://lv2plug.in/ns/ext/midi#> .

<http://lv2plug.in/plugins/eg-fifths>
	a lv2:Plugin ;
	doap:name "Example Fifths" ;
	doap:license <http://opensource.org/licenses/isc> ;
	lv2:project <http://lv2plug.in/ns/lv2> ;
	lv2:requiredFeature urid:map ;
	lv2:optionalFeature lv2:hardRTCapable ;
	lv2:port [
		a lv2:InputPort ,
			atom:AtomPort ;
		atom:bufferType atom:Sequence ;
		atom:supports midi:MidiEvent ;
		lv2:index 0 ;
		lv2:symbol "in" ;
		lv2:name "In"
	] , [
		a lv2:OutputPort ,
			atom:AtomPort ;
		atom:bufferType atom:Sequence ;
		atom:supports midi:MidiEvent ;
		lv2:index 1 ;
		lv2:symbol "out" ;
		lv2:name "Out"
	] .
---------------
=== fifths.c ===
[source,c]
----------
#include "./uris.h"

#include "lv2/atom/atom.h"
#include "lv2/atom/util.h"
#include "lv2/core/lv2.h"
#include "lv2/core/lv2_util.h"
#include "lv2/log/log.h"
#include "lv2/log/logger.h"
#include "lv2/midi/midi.h"
#include "lv2/urid/urid.h"

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

enum {
	FIFTHS_IN  = 0,
	FIFTHS_OUT = 1
};

typedef struct {
	// Features
	LV2_URID_Map*  map;
	LV2_Log_Logger logger;

	// Ports
	const LV2_Atom_Sequence* in_port;
	LV2_Atom_Sequence*       out_port;

	// URIs
	FifthsURIs uris;
} Fifths;

static void
connect_port(LV2_Handle instance,
             uint32_t   port,
             void*      data)
{
	Fifths* self = (Fifths*)instance;
	switch (port) {
	case FIFTHS_IN:
		self->in_port = (const LV2_Atom_Sequence*)data;
		break;
	case FIFTHS_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 and initialise instance structure.
	Fifths* self = (Fifths*)calloc(1, sizeof(Fifths));
	if (!self) {
		return NULL;
	}

	// Scan host features for URID map
	const char*  missing = lv2_features_query(
		features,
		LV2_LOG__log,  &self->logger.log, false,
		LV2_URID__map, &self->map,        true,
		NULL);
	lv2_log_logger_set_map(&self->logger, self->map);
	if (missing) {
		lv2_log_error(&self->logger, "Missing feature <%s>\n", missing);
		free(self);
		return NULL;
	}

	map_fifths_uris(self->map, &self->uris);

	return (LV2_Handle)self;
}

static void
cleanup(LV2_Handle instance)
{
	free(instance);
}

static void
run(LV2_Handle instance,
    uint32_t   sample_count)
{
	Fifths*     self = (Fifths*)instance;
	FifthsURIs* uris = &self->uris;

	// Struct for a 3 byte MIDI event, used for writing notes
	typedef struct {
		LV2_Atom_Event event;
		uint8_t        msg[3];
	} MIDINoteEvent;

	// Initially self->out_port contains a Chunk with size set to capacity

	// Get the capacity
	const uint32_t out_capacity = self->out_port->atom.size;

	// Write an empty Sequence header to the output
	lv2_atom_sequence_clear(self->out_port);
	self->out_port->atom.type = self->in_port->atom.type;

	// Read incoming events
	LV2_ATOM_SEQUENCE_FOREACH(self->in_port, ev) {
		if (ev->body.type == uris->midi_Event) {
			const uint8_t* const msg = (const uint8_t*)LV2_ATOM_BODY_CONST(ev);
			switch (lv2_midi_message_type(msg)) {
			case LV2_MIDI_MSG_NOTE_ON:
			case LV2_MIDI_MSG_NOTE_OFF:
				// Forward note to output
				lv2_atom_sequence_append_event(
					self->out_port, out_capacity, ev);

				if (msg[1] <= 127 - 7) {
					// Make a note one 5th (7 semitones) higher than input
					MIDINoteEvent fifth;

					// Could simply do fifth.event = *ev here instead...
					fifth.event.time.frames = ev->time.frames;  // Same time
					fifth.event.body.type   = ev->body.type;    // Same type
					fifth.event.body.size   = ev->body.size;    // Same size

					fifth.msg[0] = msg[0];      // Same status
					fifth.msg[1] = msg[1] + 7;  // Pitch up 7 semitones
					fifth.msg[2] = msg[2];      // Same velocity

					// Write 5th event
					lv2_atom_sequence_append_event(
						self->out_port, out_capacity, &fifth.event);
				}
				break;
			default:
				// Forward all other MIDI events directly
				lv2_atom_sequence_append_event(
					self->out_port, out_capacity, ev);
				break;
			}
		}
	}
}

static const void*
extension_data(const char* uri)
{
	return NULL;
}

static const LV2_Descriptor descriptor = {
	EG_FIFTHS_URI,
	instantiate,
	connect_port,
	NULL,  // activate,
	run,
	NULL,  // deactivate,
	cleanup,
	extension_data
};

LV2_SYMBOL_EXPORT
const LV2_Descriptor* lv2_descriptor(uint32_t index)
{
	switch (index) {
	case 0:
		return &descriptor;
	default:
		return NULL;
	}
}
----------
=== uris.h ===
[source,c]
----------
#ifndef FIFTHS_URIS_H
#define FIFTHS_URIS_H

#include "lv2/atom/atom.h"
#include "lv2/log/log.h"
#include "lv2/midi/midi.h"
#include "lv2/patch/patch.h"
#include "lv2/state/state.h"

#define EG_FIFTHS_URI "http://lv2plug.in/plugins/eg-fifths"

typedef struct {
	LV2_URID atom_Path;
	LV2_URID atom_Resource;
	LV2_URID atom_Sequence;
	LV2_URID atom_URID;
	LV2_URID atom_eventTransfer;
	LV2_URID midi_Event;
	LV2_URID patch_Set;
	LV2_URID patch_property;
	LV2_URID patch_value;
} FifthsURIs;

static inline void
map_fifths_uris(LV2_URID_Map* map, FifthsURIs* uris)
{
	uris->atom_Path          = map->map(map->handle, LV2_ATOM__Path);
	uris->atom_Resource      = map->map(map->handle, LV2_ATOM__Resource);
	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->midi_Event         = map->map(map->handle, LV2_MIDI__MidiEvent);
	uris->patch_Set          = map->map(map->handle, LV2_PATCH__Set);
	uris->patch_property     = map->map(map->handle, LV2_PATCH__property);
	uris->patch_value        = map->map(map->handle, LV2_PATCH__value);
}

#endif  /* FIFTHS_URIS_H */
----------
== Metronome ==

This plugin demonstrates tempo synchronisation by clicking on every beat.  The
host sends this information to the plugin as events, so an event with new time
and tempo information will be received whenever there is a change.

Time is assumed to continue rolling at the tempo and speed defined by the last
received tempo event, even across cycles, until a new tempo event is received
or the plugin is deactivated.

=== manifest.ttl.in ===
[source,turtle]
---------------
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<http://lv2plug.in/plugins/eg-metro>
	a lv2:Plugin ;
	lv2:binary <metro@LIB_EXT@> ;
	rdfs:seeAlso <metro.ttl> .
---------------
=== metro.ttl ===
[source,turtle]
---------------
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix time: <http://lv2plug.in/ns/ext/time#> .
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .

<http://lv2plug.in/plugins/eg-metro>
	a lv2:Plugin ;
	doap:name "Example Metronome" ;
	doap:license <http://opensource.org/licenses/isc> ;
	lv2:project <http://lv2plug.in/ns/lv2> ;
	lv2:requiredFeature urid:map ;
	lv2:optionalFeature lv2:hardRTCapable ;
	lv2:port [
		a lv2:InputPort ,
			atom:AtomPort ;
		atom:bufferType atom:Sequence ;
---------------


Since this port supports time:Position, the host knows to deliver time and 
tempo information

[source,turtle]
---------------
		atom:supports time:Position ;
		lv2:index 0 ;
		lv2:symbol "control" ;
		lv2:name "Control" ;
	] , [
		a lv2:AudioPort ,
			lv2:OutputPort ;
		lv2:index 1 ;
		lv2:symbol "out" ;
		lv2:name "Out" ;
	] .
---------------
=== metro.c ===
[source,c]
----------
#include "lv2/atom/atom.h"
#include "lv2/atom/util.h"
#include "lv2/core/lv2.h"
#include "lv2/core/lv2_util.h"
#include "lv2/log/log.h"
#include "lv2/log/logger.h"
#include "lv2/time/time.h"
#include "lv2/urid/urid.h"

#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifndef M_PI
#    define M_PI 3.14159265
#endif

#define EG_METRO_URI "http://lv2plug.in/plugins/eg-metro"

typedef struct {
	LV2_URID atom_Blank;
	LV2_URID atom_Float;
	LV2_URID atom_Object;
	LV2_URID atom_Path;
	LV2_URID atom_Resource;
	LV2_URID atom_Sequence;
	LV2_URID time_Position;
	LV2_URID time_barBeat;
	LV2_URID time_beatsPerMinute;
	LV2_URID time_speed;
} MetroURIs;

static const double attack_s = 0.005;
static const double decay_s  = 0.075;

enum {
	METRO_CONTROL = 0,
	METRO_OUT     = 1
};
----------


During execution this plugin can be in one of 3 states:

[source,c]
----------
typedef enum {
	STATE_ATTACK,  // Envelope rising
	STATE_DECAY,   // Envelope lowering
	STATE_OFF      // Silent
} State;
----------


This plugin must keep track of more state than previous examples to be able
to render audio.  The basic idea is to generate a single cycle of a sine
wave which is conceptually played continuously.  The 'tick' is generated by
enveloping the amplitude so there is a short attack/decay peak around a
tick, and silence the rest of the time.

This example uses a simple AD envelope with fixed parameters.  A more
sophisticated implementation might use a more advanced envelope and allow
the user to modify these parameters, the frequency of the wave, and so on.

[source,c]
----------
typedef struct {
	LV2_URID_Map*  map;     // URID map feature
	LV2_Log_Logger logger;  // Logger API
	MetroURIs      uris;    // Cache of mapped URIDs

	struct {
		LV2_Atom_Sequence* control;
		float*             output;
	} ports;

	// Variables to keep track of the tempo information sent by the host
	double rate;   // Sample rate
	float  bpm;    // Beats per minute (tempo)
	float  speed;  // Transport speed (usually 0=stop, 1=play)

	uint32_t elapsed_len;  // Frames since the start of the last click
	uint32_t wave_offset;  // Current play offset in the wave
	State    state;        // Current play state

	// One cycle of a sine wave
	float*   wave;
	uint32_t wave_len;

	// Envelope parameters
	uint32_t attack_len;
	uint32_t decay_len;
} Metro;

static void
connect_port(LV2_Handle instance,
             uint32_t   port,
             void*      data)
{
	Metro* self = (Metro*)instance;

	switch (port) {
	case METRO_CONTROL:
		self->ports.control = (LV2_Atom_Sequence*)data;
		break;
	case METRO_OUT:
		self->ports.output = (float*)data;
		break;
	default:
		break;
	}
}
----------


The activate() method resets the state completely, so the wave offset is
zero and the envelope is off.

[source,c]
----------
static void
activate(LV2_Handle instance)
{
	Metro* self = (Metro*)instance;

	self->elapsed_len = 0;
	self->wave_offset = 0;
	self->state       = STATE_OFF;
}
----------


This plugin does a bit more work in instantiate() than the previous
examples.  The tempo updates from the host contain several URIs, so those
are mapped, and the sine wave to be played needs to be generated based on
the current sample rate.

[source,c]
----------
static LV2_Handle
instantiate(const LV2_Descriptor*     descriptor,
            double                    rate,
            const char*               path,
            const LV2_Feature* const* features)
{
	Metro* self = (Metro*)calloc(1, sizeof(Metro));
	if (!self) {
		return NULL;
	}

	// Scan host features for URID map
	const char* missing = lv2_features_query(
		features,
		LV2_LOG__log,  &self->logger.log, false,
		LV2_URID__map, &self->map, true,
		NULL);
	lv2_log_logger_set_map(&self->logger, self->map);
	if (missing) {
		lv2_log_error(&self->logger, "Missing feature <%s>\n", missing);
		free(self);
		return NULL;
	}

	// Map URIS
	MetroURIs* const    uris  = &self->uris;
	LV2_URID_Map* const map   = self->map;
	uris->atom_Blank          = map->map(map->handle, LV2_ATOM__Blank);
	uris->atom_Float          = map->map(map->handle, LV2_ATOM__Float);
	uris->atom_Object         = map->map(map->handle, LV2_ATOM__Object);
	uris->atom_Path           = map->map(map->handle, LV2_ATOM__Path);
	uris->atom_Resource       = map->map(map->handle, LV2_ATOM__Resource);
	uris->atom_Sequence       = map->map(map->handle, LV2_ATOM__Sequence);
	uris->time_Position       = map->map(map->handle, LV2_TIME__Position);
	uris->time_barBeat        = map->map(map->handle, LV2_TIME__barBeat);
	uris->time_beatsPerMinute = map->map(map->handle, LV2_TIME__beatsPerMinute);
	uris->time_speed          = map->map(map->handle, LV2_TIME__speed);

	// Initialise instance fields
	self->rate       = rate;
	self->bpm        = 120.0f;
	self->attack_len = (uint32_t)(attack_s * rate);
	self->decay_len  = (uint32_t)(decay_s * rate);
	self->state      = STATE_OFF;

	// Generate one cycle of a sine wave at the desired frequency
	const double freq = 440.0 * 2.0;
	const double amp  = 0.5;
	self->wave_len = (uint32_t)(rate / freq);
	self->wave     = (float*)malloc(self->wave_len * sizeof(float));
	for (uint32_t i = 0; i < self->wave_len; ++i) {
		self->wave[i] = (float)(sin(i * 2 * M_PI * freq / rate) * amp);
	}

	return (LV2_Handle)self;
}

static void
cleanup(LV2_Handle instance)
{
	free(instance);
}
----------


Play back audio for the range [begin..end) relative to this cycle.  This is
called by run() in-between events to output audio up until the current time.

[source,c]
----------
static void
play(Metro* self, uint32_t begin, uint32_t end)
{
	float* const   output          = self->ports.output;
	const uint32_t frames_per_beat = 60.0f / self->bpm * self->rate;

	if (self->speed == 0.0f) {
		memset(output, 0, (end - begin) * sizeof(float));
		return;
	}

	for (uint32_t i = begin; i < end; ++i) {
		switch (self->state) {
		case STATE_ATTACK:
			// Amplitude increases from 0..1 until attack_len
			output[i] = self->wave[self->wave_offset] *
				self->elapsed_len / (float)self->attack_len;
			if (self->elapsed_len >= self->attack_len) {
				self->state = STATE_DECAY;
			}
			break;
		case STATE_DECAY:
			// Amplitude decreases from 1..0 until attack_len + decay_len
			output[i] = 0.0f;
			output[i] = self->wave[self->wave_offset] *
				(1 - ((self->elapsed_len - self->attack_len) /
				      (float)self->decay_len));
			if (self->elapsed_len >= self->attack_len + self->decay_len) {
				self->state = STATE_OFF;
			}
			break;
		case STATE_OFF:
			output[i] = 0.0f;
		}

		// We continuously play the sine wave regardless of envelope
		self->wave_offset = (self->wave_offset + 1) % self->wave_len;

		// Update elapsed time and start attack if necessary
		if (++self->elapsed_len == frames_per_beat) {
			self->state       = STATE_ATTACK;
			self->elapsed_len = 0;
		}
	}
}
----------


Update the current position based on a host message.  This is called by
run() when a time:Position is received.

[source,c]
----------
static void
update_position(Metro* self, const LV2_Atom_Object* obj)
{
	const MetroURIs* uris = &self->uris;

	// Received new transport position/speed
	LV2_Atom *beat = NULL, *bpm = NULL, *speed = NULL;
	lv2_atom_object_get(obj,
	                    uris->time_barBeat, &beat,
	                    uris->time_beatsPerMinute, &bpm,
	                    uris->time_speed, &speed,
	                    NULL);
	if (bpm && bpm->type == uris->atom_Float) {
		// Tempo changed, update BPM
		self->bpm = ((LV2_Atom_Float*)bpm)->body;
	}
	if (speed && speed->type == uris->atom_Float) {
		// Speed changed, e.g. 0 (stop) to 1 (play)
		self->speed = ((LV2_Atom_Float*)speed)->body;
	}
	if (beat && beat->type == uris->atom_Float) {
		// Received a beat position, synchronise
		// This hard sync may cause clicks, a real plugin would be more graceful
		const float frames_per_beat = 60.0f / self->bpm * self->rate;
		const float bar_beats       = ((LV2_Atom_Float*)beat)->body;
		const float beat_beats      = bar_beats - floorf(bar_beats);
		self->elapsed_len           = beat_beats * frames_per_beat;
		if (self->elapsed_len < self->attack_len) {
			self->state = STATE_ATTACK;
		} else if (self->elapsed_len < self->attack_len + self->decay_len) {
			self->state = STATE_DECAY;
		} else {
			self->state = STATE_OFF;
		}
	}
}

static void
run(LV2_Handle instance, uint32_t sample_count)
{
	Metro*           self = (Metro*)instance;
	const MetroURIs* uris = &self->uris;

	// Work forwards in time frame by frame, handling events as we go
	const LV2_Atom_Sequence* in     = self->ports.control;
	uint32_t                 last_t = 0;
	for (const LV2_Atom_Event* ev = lv2_atom_sequence_begin(&in->body);
	     !lv2_atom_sequence_is_end(&in->body, in->atom.size, ev);
	     ev = lv2_atom_sequence_next(ev)) {

		// Play the click for the time slice from last_t until now
		play(self, last_t, ev->time.frames);

		// Check if this event is an Object
		// (or deprecated Blank to tolerate old hosts)
		if (ev->body.type == uris->atom_Object ||
		    ev->body.type == uris->atom_Blank) {
			const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body;
			if (obj->body.otype == uris->time_Position) {
				// Received position information, update
				update_position(self, obj);
			}
		}

		// Update time for next iteration and move to next event
		last_t = ev->time.frames;
	}

	// Play for remainder of cycle
	play(self, last_t, sample_count);
}

static const LV2_Descriptor descriptor = {
	EG_METRO_URI,
	instantiate,
	connect_port,
	activate,
	run,
	NULL,  // deactivate,
	cleanup,
	NULL,  // extension_data
};

LV2_SYMBOL_EXPORT const LV2_Descriptor*
lv2_descriptor(uint32_t index)
{
	switch (index) {
	case 0:
		return &descriptor;
	default:
		return NULL;
	}
}
----------
== Sampler ==

This plugin loads a single sample from a .wav file and plays it back when a MIDI
note on is received.  Any sample on the system can be loaded via another event.
A Gtk UI is included which does this, but the host can as well.

This plugin illustrates:

- UI <==> Plugin communication via events
- Use of the worker extension for non-realtime tasks (sample loading)
- Use of the log extension to print log messages via the host
- Saving plugin state via the state extension
- Dynamic plugin control via the same properties saved to state
- Network-transparent waveform display with incremental peak transmission

=== manifest.ttl.in ===


Unlike the previous examples, this manifest lists more than one resource: the 
plugin as usual, and the UI.  The descriptions are similar, but have 
different types, so the host can decide from this file alone whether or not 
it is interested, and avoid following the `rdfs:seeAlso` link if not (though 
in this case both are described in the same file).

[source,turtle]
---------------
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix ui:   <http://lv2plug.in/ns/extensions/ui#> .

<http://lv2plug.in/plugins/eg-sampler>
	a lv2:Plugin ;
	lv2:binary <sampler@LIB_EXT@> ;
	rdfs:seeAlso <sampler.ttl> .

<http://lv2plug.in/plugins/eg-sampler#ui>
	a ui:GtkUI ;
	ui:binary <sampler_ui@LIB_EXT@> ;
	rdfs:seeAlso <sampler.ttl> .
---------------
=== sampler.ttl ===
[source,turtle]
---------------
@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 rdfs:  <http://www.w3.org/2000/01/rdf-schema#> .
@prefix state: <http://lv2plug.in/ns/ext/state#> .
@prefix ui:    <http://lv2plug.in/ns/extensions/ui#> .
@prefix urid:  <http://lv2plug.in/ns/ext/urid#> .
@prefix work:  <http://lv2plug.in/ns/ext/worker#> .
@prefix xsd:   <http://www.w3.org/2001/XMLSchema#> .

<http://lv2plug.in/plugins/eg-sampler#sample>
	a lv2:Parameter ;
	rdfs:label "sample" ;
	rdfs:range atom:Path .

<http://lv2plug.in/plugins/eg-sampler>
	a lv2:Plugin ;
	doap:name "Exampler" ;
	doap:license <http://opensource.org/licenses/isc> ;
	lv2:project <http://lv2plug.in/ns/lv2> ;
	lv2:requiredFeature state:loadDefaultState ,
		urid:map ,
		work:schedule ;
	lv2:optionalFeature lv2:hardRTCapable ,
		state:threadSafeRestore ;
	lv2:extensionData state:interface ,
		work:interface ;
	ui:ui <http://lv2plug.in/plugins/eg-sampler#ui> ;
	patch:writable <http://lv2plug.in/plugins/eg-sampler#sample> ,
		param:gain ;
	lv2:port [
		a lv2:InputPort ,
			atom:AtomPort ;
		atom:bufferType atom:Sequence ;
		atom:supports <http://lv2plug.in/ns/ext/midi#MidiEvent> ,
			patch:Message ;
		lv2:designation lv2:control ;
		lv2:index 0 ;
		lv2:symbol "control" ;
		lv2:name "Control"
	] , [
		a lv2:OutputPort ,
			atom:AtomPort ;
		atom:bufferType atom:Sequence ;
		atom:supports patch:Message ;
		lv2:designation lv2:control ;
		lv2:index 1 ;
		lv2:symbol "notify" ;
		lv2:name "Notify"
	] , [
		a lv2:AudioPort ,
			lv2:OutputPort ;
		lv2:index 2 ;
		lv2:symbol "out" ;
		lv2:name "Out"
	] ;
	state:state [
		<http://lv2plug.in/plugins/eg-sampler#sample> <click.wav> ;
		param:gain "1.0"^^xsd:float
	] .

<http://lv2plug.in/plugins/eg-sampler#ui>
	a ui:GtkUI ;
	lv2:requiredFeature urid:map ;
	lv2:optionalFeature ui:requestValue ;
	lv2:extensionData ui:showInterface ;
	ui:portNotification [
		ui:plugin <http://lv2plug.in/plugins/eg-sampler> ;
		lv2:symbol "notify" ;
		ui:notifyType atom:Blank
	] .
---------------
=== sampler.c ===
[source,c]
----------
#include "atom_sink.h"
#include "peaks.h"
#include "uris.h"

#include "lv2/atom/atom.h"
#include "lv2/atom/forge.h"
#include "lv2/atom/util.h"
#include "lv2/core/lv2.h"
#include "lv2/core/lv2_util.h"
#include "lv2/log/log.h"
#include "lv2/log/logger.h"
#include "lv2/midi/midi.h"
#include "lv2/state/state.h"
#include "lv2/urid/urid.h"
#include "lv2/worker/worker.h"

#include <sndfile.h>

#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

enum {
	SAMPLER_CONTROL = 0,
	SAMPLER_NOTIFY  = 1,
	SAMPLER_OUT     = 2
};

typedef struct {
	SF_INFO  info;      // Info about sample from sndfile
	float*   data;      // Sample data in float
	char*    path;      // Path of file
	uint32_t path_len;  // Length of path
} Sample;

typedef struct {
	// Features
	LV2_URID_Map*        map;
	LV2_Worker_Schedule* schedule;
	LV2_Log_Logger       logger;

	// Ports
	const LV2_Atom_Sequence* control_port;
	LV2_Atom_Sequence*       notify_port;
	float*                   output_port;

	// Communication utilities
	LV2_Atom_Forge_Frame notify_frame;  ///< Cached for worker replies
	LV2_Atom_Forge       forge;         ///< Forge for writing atoms in run thread
	PeaksSender          psend;         ///< Audio peaks sender

	// URIs
	SamplerURIs uris;

	// Playback state
	Sample*    sample;
	uint32_t   frame_offset;
	float      gain;
	sf_count_t frame;
	bool       play;
	bool       activated;
	bool       sample_changed;
} Sampler;
----------


An atom-like message used internally to apply/free samples.

This is only used internally to communicate with the worker, it is never
sent to the outside world via a port since it is not POD.  It is convenient
to use an Atom header so actual atoms can be easily sent through the same
ringbuffer.

[source,c]
----------
typedef struct {
	LV2_Atom atom;
	Sample*  sample;
} SampleMessage;
----------


Load a new sample and return it.

Since this is of course not a real-time safe action, this is called in the
worker thread only.  The sample is loaded and returned only, plugin state is
not modified.

[source,c]
----------
static Sample*
load_sample(LV2_Log_Logger* logger, const char* path)
{
	lv2_log_trace(logger, "Loading %s\n", path);

	const size_t   path_len = strlen(path);
	Sample* const  sample   = (Sample*)calloc(1, sizeof(Sample));
	SF_INFO* const info     = &sample->info;
	SNDFILE* const sndfile  = sf_open(path, SFM_READ, info);
	float*         data     = NULL;
	bool           error    = true;
	if (!sndfile || !info->frames) {
		lv2_log_error(logger, "Failed to open %s\n", path);
	} else if (info->channels != 1) {
		lv2_log_error(logger, "%s has %d channels\n", path, info->channels);
	} else if (!(data = (float*)malloc(sizeof(float) * info->frames))) {
		lv2_log_error(logger, "Failed to allocate memory for sample\n");
	} else {
		error = false;
	}

	if (error) {
		free(sample);
		free(data);
		sf_close(sndfile);
		return NULL;
	}

	sf_seek(sndfile, 0ul, SEEK_SET);
	sf_read_float(sndfile, data, info->frames);
	sf_close(sndfile);

	// Fill sample struct and return it
	sample->data     = data;
	sample->path     = (char*)malloc(path_len + 1);
	sample->path_len = (uint32_t)path_len;
	memcpy(sample->path, path, path_len + 1);

	return sample;
}

static void
free_sample(Sampler* self, Sample* sample)
{
	if (sample) {
		lv2_log_trace(&self->logger, "Freeing %s\n", sample->path);
		free(sample->path);
		free(sample->data);
		free(sample);
	}
}
----------


Do work in a non-realtime thread.

This is called for every piece of work scheduled in the audio thread using
self->schedule->schedule_work().  A reply can be sent back to the audio
thread using the provided `respond` function.

[source,c]
----------
static LV2_Worker_Status
work(LV2_Handle                  instance,
     LV2_Worker_Respond_Function respond,
     LV2_Worker_Respond_Handle   handle,
     uint32_t                    size,
     const void*                 data)
{
	Sampler*        self = (Sampler*)instance;
	const LV2_Atom* atom = (const LV2_Atom*)data;
	if (atom->type == self->uris.eg_freeSample) {
		// Free old sample
		const SampleMessage* msg = (const SampleMessage*)data;
		free_sample(self, msg->sample);
	} else if (atom->type == self->forge.Object) {
		// Handle set message (load sample).
		const LV2_Atom_Object* obj  = (const LV2_Atom_Object*)data;
		const char*            path = read_set_file(&self->uris, obj);
		if (!path) {
			lv2_log_error(&self->logger, "Malformed set file request\n");
			return LV2_WORKER_ERR_UNKNOWN;
		}

		// Load sample.
		Sample* sample = load_sample(&self->logger, path);
		if (sample) {
			// Send new sample to run() to be applied
			respond(handle, sizeof(sample), &sample);
		}
	}

	return LV2_WORKER_SUCCESS;
}
----------


Handle a response from work() in the audio thread.

When running normally, this will be called by the host after run().  When
freewheeling, this will be called immediately at the point the work was
scheduled.

[source,c]
----------
static LV2_Worker_Status
work_response(LV2_Handle  instance,
              uint32_t    size,
              const void* data)
{
	Sampler* self       = (Sampler*)instance;
	Sample*  old_sample = self->sample;
	Sample*  new_sample = *(Sample*const*)data;

	// Install the new sample
	self->sample = *(Sample*const*)data;

	// Schedule work to free the old sample
	SampleMessage msg = { { sizeof(Sample*), self->uris.eg_freeSample },
	                      old_sample };
	self->schedule->schedule_work(self->schedule->handle, sizeof(msg), &msg);

	// Send a notification that we're using a new sample
	lv2_atom_forge_frame_time(&self->forge, self->frame_offset);
	write_set_file(&self->forge, &self->uris,
		       new_sample->path,
		       new_sample->path_len);

	return LV2_WORKER_SUCCESS;
}

static void
connect_port(LV2_Handle instance,
             uint32_t   port,
             void*      data)
{
	Sampler* self = (Sampler*)instance;
	switch (port) {
	case SAMPLER_CONTROL:
		self->control_port = (const LV2_Atom_Sequence*)data;
		break;
	case SAMPLER_NOTIFY:
		self->notify_port = (LV2_Atom_Sequence*)data;
		break;
	case SAMPLER_OUT:
		self->output_port = (float*)data;
		break;
	default:
		break;
	}
}

static LV2_Handle
instantiate(const LV2_Descriptor*     descriptor,
            double                    rate,
            const char*               path,
            const LV2_Feature* const* features)
{
	// Allocate and initialise instance structure.
	Sampler* self = (Sampler*)calloc(1, sizeof(Sampler));
	if (!self) {
		return NULL;
	}

	// Get host features
	const char* missing = lv2_features_query(
		features,
		LV2_LOG__log,         &self->logger.log, false,
		LV2_URID__map,        &self->map,        true,
		LV2_WORKER__schedule, &self->schedule,   true,
		NULL);
	lv2_log_logger_set_map(&self->logger, self->map);
	if (missing) {
		lv2_log_error(&self->logger, "Missing feature <%s>\n", missing);
		free(self);
		return NULL;
	}

	// Map URIs and initialise forge
	map_sampler_uris(self->map, &self->uris);
	lv2_atom_forge_init(&self->forge, self->map);
	peaks_sender_init(&self->psend, self->map);

	self->gain = 1.0;

	return (LV2_Handle)self;
}

static void
cleanup(LV2_Handle instance)
{
	Sampler* self = (Sampler*)instance;
	free_sample(self, self->sample);
	free(self);
}

static void
activate(LV2_Handle instance)
{
	((Sampler*)instance)->activated = true;
}

static void
deactivate(LV2_Handle instance)
{
	((Sampler*)instance)->activated = false;
}
----------


Define a macro for converting a gain in dB to a coefficient.

[source,c]
----------
#define DB_CO(g) ((g) > -90.0f ? powf(10.0f, (g) * 0.05f) : 0.0f)
----------


Handle an incoming event in the audio thread.

This performs any actions triggered by an event, such as the start of sample
playback, a sample change, or responding to requests from the UI.

[source,c]
----------
static void
handle_event(Sampler* self, LV2_Atom_Event* ev)
{
	SamplerURIs* uris       = &self->uris;
	PeaksURIs*   peaks_uris = &self->psend.uris;

	if (ev->body.type == uris->midi_Event) {
		const uint8_t* const msg = (const uint8_t*)LV2_ATOM_BODY_CONST(ev);
		switch (lv2_midi_message_type(msg)) {
		case LV2_MIDI_MSG_NOTE_ON:
			self->frame = 0;
			self->play  = true;
			break;
		default:
			break;
		}
	} else if (lv2_atom_forge_is_object_type(&self->forge, ev->body.type)) {
		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, "Set message with no property\n");
				return;
			} else if (property->type != uris->atom_URID) {
				lv2_log_error(&self->logger, "Set property is not a URID\n");
				return;
			}

			const uint32_t key = ((const LV2_Atom_URID*)property)->body;
			if (key == uris->eg_sample) {
				// Sample change, send it to the worker.
				lv2_log_trace(&self->logger, "Scheduling sample change\n");
				self->schedule->schedule_work(self->schedule->handle,
				                              lv2_atom_total_size(&ev->body),
				                              &ev->body);
			} else if (key == uris->param_gain) {
				// Gain change
				if (value->type == uris->atom_Float) {
					self->gain = DB_CO(((LV2_Atom_Float*)value)->body);
				}
			}
		} else if (obj->body.otype == uris->patch_Get && self->sample) {
			const LV2_Atom_URID* accept  = NULL;
			const LV2_Atom_Int*  n_peaks = NULL;
			lv2_atom_object_get_typed(
				obj,
				uris->patch_accept,      &accept,  uris->atom_URID,
				peaks_uris->peaks_total, &n_peaks, peaks_uris->atom_Int, 0);
			if (accept && accept->body == peaks_uris->peaks_PeakUpdate) {
				// Received a request for peaks, prepare for transmission
				peaks_sender_start(&self->psend,
				                   self->sample->data,
				                   self->sample->info.frames,
				                   n_peaks->body);
			} else {
				// Received a get message, emit our state (probably to UI)
				lv2_atom_forge_frame_time(&self->forge, self->frame_offset);
				write_set_file(&self->forge, &self->uris,
				               self->sample->path,
				               self->sample->path_len);
			}
		} else {
			lv2_log_trace(&self->logger,
			              "Unknown object type %d\n", obj->body.otype);
		}
	} else {
		lv2_log_trace(&self->logger,
		              "Unknown event type %d\n", ev->body.type);
	}

}
----------


Output audio for a slice of the current cycle.

[source,c]
----------
static void
render(Sampler* self, uint32_t start, uint32_t end)
{
	float* output = self->output_port;

	if (self->play && self->sample) {
		// Start/continue writing sample to output
		for (; start < end; ++start) {
			output[start] = self->sample->data[self->frame] * self->gain;
			if (++self->frame == self->sample->info.frames) {
				self->play = false;  // Reached end of sample
				break;
			}
		}
	}

	// Write silence to remaining buffer
	for (; start < end; ++start) {
		output[start] = 0.0f;
	}
}

static void
run(LV2_Handle instance, uint32_t sample_count)
{
	Sampler* self = (Sampler*)instance;

	// Set up forge to write directly to notify output port.
	const uint32_t notify_capacity = self->notify_port->atom.size;
	lv2_atom_forge_set_buffer(&self->forge,
	                          (uint8_t*)self->notify_port,
	                          notify_capacity);

	// Start a sequence in the notify output port.
	lv2_atom_forge_sequence_head(&self->forge, &self->notify_frame, 0);

	// Send update to UI if sample has changed due to state restore
	if (self->sample_changed) {
		lv2_atom_forge_frame_time(&self->forge, 0);
		write_set_file(&self->forge, &self->uris,
		               self->sample->path,
		               self->sample->path_len);
		self->sample_changed = false;
	}

	// Iterate over incoming events, emitting audio along the way
	self->frame_offset = 0;
	LV2_ATOM_SEQUENCE_FOREACH(self->control_port, ev) {
		// Render output up to the time of this event
		render(self, self->frame_offset, ev->time.frames);

		/* Update current frame offset to this event's time.  This is stored in
		   the instance because it is used for sychronous worker event
		   execution.  This allows a sample load event to be executed with
		   sample accuracy when running in a non-realtime context (such as
		   exporting a session). */
		self->frame_offset = ev->time.frames;

		// Process this event
		handle_event(self, ev);
	}

	// Use available space after any emitted events to send peaks
	peaks_sender_send(&self->psend, &self->forge, sample_count, self->frame_offset);

	// Render output for the rest of the cycle past the last event
	render(self, self->frame_offset, sample_count);
}

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)
{
	Sampler* self = (Sampler*)instance;
	if (!self->sample) {
		return LV2_STATE_SUCCESS;
	}

	LV2_State_Map_Path* map_path = (LV2_State_Map_Path*)lv2_features_data(
		features, LV2_STATE__mapPath);
	if (!map_path) {
		return LV2_STATE_ERR_NO_FEATURE;
	}

	// Map absolute sample path to an abstract state path
	char* apath = map_path->abstract_path(map_path->handle, self->sample->path);

	// Store eg:sample = abstract path
	store(handle,
	      self->uris.eg_sample,
	      apath,
	      strlen(apath) + 1,
	      self->uris.atom_Path,
	      LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);

	free(apath);
	return LV2_STATE_SUCCESS;
}

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)
{
	Sampler* self = (Sampler*)instance;

	// Get host features
	LV2_Worker_Schedule* schedule = NULL;
	LV2_State_Map_Path*  paths    = NULL;
	const char*          missing  = lv2_features_query(
		features,
		LV2_STATE__mapPath,   &paths,    true,
		LV2_WORKER__schedule, &schedule, false,
		NULL);
	if (missing) {
		lv2_log_error(&self->logger, "Missing feature <%s>\n", missing);
		return LV2_STATE_ERR_NO_FEATURE;
	}

	// Get eg:sample from state
	size_t      size;
	uint32_t    type;
	uint32_t    valflags;
	const void* value = retrieve(handle, self->uris.eg_sample,
	                             &size, &type, &valflags);
	if (!value) {
		lv2_log_error(&self->logger, "Missing eg:sample\n");
		return LV2_STATE_ERR_NO_PROPERTY;
	} else if (type != self->uris.atom_Path) {
		lv2_log_error(&self->logger, "Non-path eg:sample\n");
		return LV2_STATE_ERR_BAD_TYPE;
	}

	// Map abstract state path to absolute path
	const char* apath = (const char*)value;
	char*       path  = paths->absolute_path(paths->handle, apath);

	// Replace current sample with the new one
	if (!self->activated || !schedule) {
		// No scheduling available, load sample immediately
		lv2_log_trace(&self->logger, "Synchronous restore\n");
		Sample* sample = load_sample(&self->logger, path);
		if (sample) {
			free_sample(self, self->sample);
			self->sample         = sample;
			self->sample_changed = true;
		}
	} else {
		// Schedule sample to be loaded by the provided worker
		lv2_log_trace(&self->logger, "Scheduling restore\n");
		LV2_Atom_Forge forge;
		LV2_Atom*      buf = (LV2_Atom*)calloc(1, strlen(path) + 128);
		lv2_atom_forge_init(&forge, self->map);
		lv2_atom_forge_set_sink(&forge, atom_sink, atom_sink_deref, buf);
		write_set_file(&forge, &self->uris, path, strlen(path));

		const uint32_t msg_size = lv2_atom_pad_size(buf->size);
		schedule->schedule_work(self->schedule->handle, msg_size, buf + 1);
		free(buf);
	}

	free(path);

	return LV2_STATE_SUCCESS;
}

static const void*
extension_data(const char* uri)
{
	static const LV2_State_Interface  state  = { save, restore };
	static const LV2_Worker_Interface worker = { work, work_response, NULL };
	if (!strcmp(uri, LV2_STATE__interface)) {
		return &state;
	} else if (!strcmp(uri, LV2_WORKER__interface)) {
		return &worker;
	}
	return NULL;
}

static const LV2_Descriptor descriptor = {
	EG_SAMPLER_URI,
	instantiate,
	connect_port,
	activate,
	run,
	deactivate,
	cleanup,
	extension_data
};

LV2_SYMBOL_EXPORT
const LV2_Descriptor* lv2_descriptor(uint32_t index)
{
	switch (index) {
	case 0:
		return &descriptor;
	default:
		return NULL;
	}
}
----------
=== sampler_ui.c ===
[source,c]
----------
#include "peaks.h"
#include "uris.h"

#include "lv2/atom/atom.h"
#include "lv2/atom/forge.h"
#include "lv2/atom/util.h"
#include "lv2/core/lv2.h"
#include "lv2/core/lv2_util.h"
#include "lv2/log/log.h"
#include "lv2/log/logger.h"
#include "lv2/midi/midi.h"
#include "lv2/ui/ui.h"
#include "lv2/urid/urid.h"

#include <cairo.h>
#include <gdk/gdk.h>
#include <glib-object.h>
#include <glib.h>
#include <gobject/gclosure.h>
#include <gtk/gtk.h>

#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#define SAMPLER_UI_URI "http://lv2plug.in/plugins/eg-sampler#ui"

#define MIN_CANVAS_W 128
#define MIN_CANVAS_H 80

typedef struct {
	LV2_Atom_Forge       forge;
	LV2_URID_Map*        map;
	LV2UI_Request_Value* request_value;
	LV2_Log_Logger       logger;
	SamplerURIs          uris;
	PeaksReceiver        precv;

	LV2UI_Write_Function write;
	LV2UI_Controller     controller;

	GtkWidget* box;
	GtkWidget* play_button;
	GtkWidget* file_button;
	GtkWidget* request_file_button;
	GtkWidget* button_box;
	GtkWidget* canvas;

	uint32_t width;
	uint32_t requested_n_peaks;
	char*    filename;

	uint8_t forge_buf[1024];

	// Optional show/hide interface
	GtkWidget* window;
	bool       did_init;
} SamplerUI;

static void
on_file_set(GtkFileChooserButton* widget, void* handle)
{
	SamplerUI* ui = (SamplerUI*)handle;

	// Get the filename from the file chooser
	char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));

	// Write a set message to the plugin to load new file
	lv2_atom_forge_set_buffer(&ui->forge, ui->forge_buf, sizeof(ui->forge_buf));
	LV2_Atom* msg = (LV2_Atom*)write_set_file(&ui->forge, &ui->uris,
	                                          filename, strlen(filename));

	ui->write(ui->controller, 0, lv2_atom_total_size(msg),
	          ui->uris.atom_eventTransfer,
	          msg);

	g_free(filename);
}

static void
on_request_file(GtkButton* widget, void* handle)
{
	SamplerUI* ui = (SamplerUI*)handle;

	ui->request_value->request(ui->request_value->handle,
	                           ui->uris.eg_sample,
	                           0,
	                           NULL);
}

static void
on_play_clicked(GtkFileChooserButton* widget, void* handle)
{
	SamplerUI* ui = (SamplerUI*)handle;
	struct {
		LV2_Atom atom;
		uint8_t  msg[3];
	} note_on;

	note_on.atom.type = ui->uris.midi_Event;
	note_on.atom.size = 3;
	note_on.msg[0]    = LV2_MIDI_MSG_NOTE_ON;
	note_on.msg[1]    = 60;
	note_on.msg[2]    = 60;
	ui->write(ui->controller, 0, sizeof(note_on),
	          ui->uris.atom_eventTransfer,
	          &note_on);
}

static void
request_peaks(SamplerUI* ui, uint32_t n_peaks)
{
	if (n_peaks == ui->requested_n_peaks) {
		return;
	}

	lv2_atom_forge_set_buffer(&ui->forge, ui->forge_buf, sizeof(ui->forge_buf));

	LV2_Atom_Forge_Frame frame;
	lv2_atom_forge_object(&ui->forge, &frame, 0, ui->uris.patch_Get);
	lv2_atom_forge_key(&ui->forge, ui->uris.patch_accept);
	lv2_atom_forge_urid(&ui->forge, ui->precv.uris.peaks_PeakUpdate);
	lv2_atom_forge_key(&ui->forge, ui->precv.uris.peaks_total);
	lv2_atom_forge_int(&ui->forge, n_peaks);
	lv2_atom_forge_pop(&ui->forge, &frame);

	LV2_Atom* msg = lv2_atom_forge_deref(&ui->forge, frame.ref);
	ui->write(ui->controller, 0, lv2_atom_total_size(msg),
	          ui->uris.atom_eventTransfer,
	          msg);

	ui->requested_n_peaks = n_peaks;
}
----------


Set Cairo color to a GDK color (to follow Gtk theme).

[source,c]
----------
static void
cairo_set_source_gdk(cairo_t* cr, const GdkColor* color)
{
	cairo_set_source_rgb(
		cr, color->red / 65535.0, color->green / 65535.0, color->blue / 65535.0);

}

static gboolean
on_canvas_expose(GtkWidget* widget, GdkEventExpose* event, gpointer data)
{
	SamplerUI* ui = (SamplerUI*)data;

	GtkAllocation size;
	gtk_widget_get_allocation(widget, &size);

	ui->width = size.width;
	if ((uint32_t)ui->width > 2 * ui->requested_n_peaks) {
		request_peaks(ui, 2 * ui->requested_n_peaks);
	}

	cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget));

	cairo_set_line_width(cr, 1.0);
	cairo_translate(cr, 0.5, 0.5);

	const int mid_y = size.height / 2;

	const float* const peaks   = ui->precv.peaks;
	const int32_t      n_peaks = ui->precv.n_peaks;
	if (peaks) {
		// Draw waveform
		const double scale = size.width / ((double)n_peaks - 1.0f);

		// Start at left origin
		cairo_move_to(cr, 0, mid_y);

		// Draw line through top peaks
		for (int i = 0; i < n_peaks; ++i) {
			const float peak = peaks[i];
			cairo_line_to(cr, i * scale, mid_y + (peak / 2.0f) * size.height);
		}

		// Continue through bottom peaks
		for (int i = n_peaks - 1; i >= 0; --i) {
			const float peak = peaks[i];
			cairo_line_to(cr, i * scale, mid_y - (peak / 2.0f) * size.height);
		}

		// Close shape
		cairo_line_to(cr, 0, mid_y);

		cairo_set_source_gdk(cr, widget->style->mid);
		cairo_fill_preserve(cr);

		cairo_set_source_gdk(cr, widget->style->fg);
		cairo_stroke(cr);
	}

	cairo_destroy(cr);
	return TRUE;
}

static void
destroy_window(SamplerUI* ui)
{
	if (ui->window) {
		gtk_container_remove(GTK_CONTAINER(ui->window), ui->box);
		gtk_widget_destroy(ui->window);
		ui->window = NULL;
	}
}

static gboolean
on_window_closed(GtkWidget* widget, GdkEvent* event, gpointer data)
{
	SamplerUI* ui = (SamplerUI*)data;

	// Remove widget so Gtk doesn't delete it when the window is closed
	gtk_container_remove(GTK_CONTAINER(ui->window), ui->box);
	ui->window = NULL;

	return FALSE;
}

static LV2UI_Handle
instantiate(const LV2UI_Descriptor*   descriptor,
            const char*               plugin_uri,
            const char*               bundle_path,
            LV2UI_Write_Function      write_function,
            LV2UI_Controller          controller,
            LV2UI_Widget*             widget,
            const LV2_Feature* const* features)
{
	SamplerUI* ui = (SamplerUI*)calloc(1, sizeof(SamplerUI));
	if (!ui) {
		return NULL;
	}

	ui->write      = write_function;
	ui->controller = controller;
	ui->width      = MIN_CANVAS_W;
	*widget        = NULL;
	ui->window     = NULL;
	ui->did_init   = false;

	// Get host features
	const char* missing = lv2_features_query(
		features,
		LV2_LOG__log,         &ui->logger.log ,   false,
		LV2_URID__map,        &ui->map,           true,
		LV2_UI__requestValue, &ui->request_value, false,
		NULL);
	lv2_log_logger_set_map(&ui->logger, ui->map);
	if (missing) {
		lv2_log_error(&ui->logger, "Missing feature <%s>\n", missing);
		free(ui);
		return NULL;
	}

	// Map URIs and initialise forge
	map_sampler_uris(ui->map, &ui->uris);
	lv2_atom_forge_init(&ui->forge, ui->map);
	peaks_receiver_init(&ui->precv, ui->map);

	// Construct Gtk UI
	ui->box         = gtk_vbox_new(FALSE, 4);
	ui->play_button = gtk_button_new_with_label("▶");
	ui->canvas      = gtk_drawing_area_new();
	ui->button_box  = gtk_hbox_new(FALSE, 4);
	ui->file_button = gtk_file_chooser_button_new(
		"Load Sample", GTK_FILE_CHOOSER_ACTION_OPEN);
	ui->request_file_button = gtk_button_new_with_label("Request Sample");
	gtk_widget_set_size_request(ui->canvas, MIN_CANVAS_W, MIN_CANVAS_H);
	gtk_container_set_border_width(GTK_CONTAINER(ui->box), 4);
	gtk_box_pack_start(GTK_BOX(ui->box), ui->canvas, TRUE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(ui->box), ui->button_box, FALSE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(ui->button_box), ui->play_button, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(ui->button_box), ui->request_file_button, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(ui->button_box), ui->file_button, TRUE, TRUE, 0);

	g_signal_connect(ui->file_button, "file-set",
	                 G_CALLBACK(on_file_set), ui);

	g_signal_connect(ui->request_file_button, "clicked",
	                 G_CALLBACK(on_request_file), ui);

	g_signal_connect(ui->play_button, "clicked",
	                 G_CALLBACK(on_play_clicked), ui);

	g_signal_connect(G_OBJECT(ui->canvas), "expose_event",
	                 G_CALLBACK(on_canvas_expose), ui);

	// Request state (filename) from plugin
	lv2_atom_forge_set_buffer(&ui->forge, ui->forge_buf, sizeof(ui->forge_buf));
	LV2_Atom_Forge_Frame frame;
	LV2_Atom*            msg = (LV2_Atom*)lv2_atom_forge_object(
		&ui->forge, &frame, 0, ui->uris.patch_Get);
	lv2_atom_forge_pop(&ui->forge, &frame);

	ui->write(ui->controller, 0, lv2_atom_total_size(msg),
	          ui->uris.atom_eventTransfer,
	          msg);

	*widget = ui->box;

	return ui;
}

static void
cleanup(LV2UI_Handle handle)
{
	SamplerUI* ui = (SamplerUI*)handle;

	if (ui->window) {
		destroy_window(ui);
	}

	gtk_widget_destroy(ui->canvas);
	gtk_widget_destroy(ui->play_button);
	gtk_widget_destroy(ui->file_button);
	gtk_widget_destroy(ui->request_file_button);
	gtk_widget_destroy(ui->button_box);
	gtk_widget_destroy(ui->box);
	free(ui);
}

static void
port_event(LV2UI_Handle handle,
           uint32_t     port_index,
           uint32_t     buffer_size,
           uint32_t     format,
           const void*  buffer)
{
	SamplerUI* ui = (SamplerUI*)handle;
	if (format == ui->uris.atom_eventTransfer) {
		const LV2_Atom* atom = (const LV2_Atom*)buffer;
		if (lv2_atom_forge_is_object_type(&ui->forge, atom->type)) {
			const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom;
			if (obj->body.otype == ui->uris.patch_Set) {
				const char* path = read_set_file(&ui->uris, obj);
				if (path && (!ui->filename || strcmp(path, ui->filename))) {
					g_free(ui->filename);
					ui->filename = g_strdup(path);
					gtk_file_chooser_set_filename(
						GTK_FILE_CHOOSER(ui->file_button), path);
					peaks_receiver_clear(&ui->precv);
					ui->requested_n_peaks = 0;
					request_peaks(ui, ui->width / 2 * 2);
				} else if (!path) {
					lv2_log_warning(&ui->logger, "Set message has no path\n");
				}
			} else if (obj->body.otype == ui->precv.uris.peaks_PeakUpdate) {
				if (!peaks_receiver_receive(&ui->precv, obj)) {
					gtk_widget_queue_draw(ui->canvas);
				}
			}
		} else {
			lv2_log_error(&ui->logger, "Unknown message type\n");
		}
	} else {
		lv2_log_warning(&ui->logger, "Unknown port event format\n");
	}
}

/* Optional non-embedded UI show interface. */
static int
ui_show(LV2UI_Handle handle)
{
	SamplerUI* ui = (SamplerUI*)handle;

	if (ui->window) {
		return 0;
	}

	if (!ui->did_init) {
		int argc = 0;
		gtk_init_check(&argc, NULL);
		g_object_ref(ui->box);
		ui->did_init = true;
	}

	ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_container_add(GTK_CONTAINER(ui->window), ui->box);

	g_signal_connect(G_OBJECT(ui->window),
	                 "delete-event",
	                 G_CALLBACK(on_window_closed),
	                 handle);

	gtk_widget_show_all(ui->window);
	gtk_window_present(GTK_WINDOW(ui->window));

	return 0;
}

/* Optional non-embedded UI hide interface. */
static int
ui_hide(LV2UI_Handle handle)
{
	SamplerUI* ui = (SamplerUI*)handle;

	if (ui->window) {
		destroy_window(ui);
	}

	return 0;
}

/* Idle interface for optional non-embedded UI. */
static int
ui_idle(LV2UI_Handle handle)
{
	SamplerUI* ui = (SamplerUI*)handle;
	if (ui->window) {
		gtk_main_iteration_do(false);
	}
	return 0;
}

static const void*
extension_data(const char* uri)
{
	static const LV2UI_Show_Interface show = { ui_show, ui_hide };
	static const LV2UI_Idle_Interface idle = { ui_idle };
	if (!strcmp(uri, LV2_UI__showInterface)) {
		return &show;
	} else if (!strcmp(uri, LV2_UI__idleInterface)) {
		return &idle;
	}
	return NULL;
}

static const LV2UI_Descriptor descriptor = {
	SAMPLER_UI_URI,
	instantiate,
	cleanup,
	port_event,
	extension_data
};

LV2_SYMBOL_EXPORT
const LV2UI_Descriptor*
lv2ui_descriptor(uint32_t index)
{
	switch (index) {
	case 0:
		return &descriptor;
	default:
		return NULL;
	}
}
----------
=== atom_sink.h ===
[source,c]
----------
#include "lv2/atom/forge.h"
----------


A forge sink that writes to an atom buffer.

It is assumed that the handle points to an LV2_Atom large enough to store
the forge output.  The forged result is in the body of the buffer atom.

[source,c]
----------
static LV2_Atom_Forge_Ref
atom_sink(LV2_Atom_Forge_Sink_Handle handle, const void* buf, uint32_t size)
{
	LV2_Atom*      atom   = (LV2_Atom*)handle;
	const uint32_t offset = lv2_atom_total_size(atom);
	memcpy((char*)atom + offset, buf, size);
	atom->size += size;
	return offset;
}
----------


Dereference counterpart to atom_sink().

[source,c]
----------
static LV2_Atom*
atom_sink_deref(LV2_Atom_Forge_Sink_Handle handle, LV2_Atom_Forge_Ref ref)
{
	return (LV2_Atom*)((char*)handle + ref);
}
----------
=== peaks.h ===




This file defines utilities for sending and receiving audio peaks for
waveform display.  The functionality is divided into two objects:
PeaksSender, for sending peaks updates from the plugin, and PeaksReceiver,
for receiving such updates and caching the peaks.

This allows peaks for a waveform of any size at any resolution to be
requested, with reasonably sized incremental updates sent over plugin ports.

[source,c]
----------
#ifndef PEAKS_H_INCLUDED
#define PEAKS_H_INCLUDED

#include "lv2/atom/forge.h"

#include <math.h>
#include <stdlib.h>

#define PEAKS_URI          "http://lv2plug.in/ns/peaks#"
#define PEAKS__PeakUpdate  PEAKS_URI "PeakUpdate"
#define PEAKS__magnitudes  PEAKS_URI "magnitudes"
#define PEAKS__offset      PEAKS_URI "offset"
#define PEAKS__total       PEAKS_URI "total"

#ifndef MIN
#    define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef MAX
#    define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif

typedef struct {
	LV2_URID atom_Float;
	LV2_URID atom_Int;
	LV2_URID atom_Vector;
	LV2_URID peaks_PeakUpdate;
	LV2_URID peaks_magnitudes;
	LV2_URID peaks_offset;
	LV2_URID peaks_total;
} PeaksURIs;

typedef struct {
	PeaksURIs    uris;            ///< URIDs used in protocol
	const float* samples;         ///< Sample data
	uint32_t     n_samples;       ///< Total number of samples
	uint32_t     n_peaks;         ///< Total number of peaks
	uint32_t     current_offset;  ///< Current peak offset
	bool         sending;         ///< True iff currently sending
} PeaksSender;

typedef struct {
	PeaksURIs uris;     ///< URIDs used in protocol
	float*    peaks;    ///< Received peaks, or zeroes
	uint32_t  n_peaks;  ///< Total number of peaks
} PeaksReceiver;
----------


Map URIs used in the peaks protocol.

[source,c]
----------
static inline void
peaks_map_uris(PeaksURIs* uris, LV2_URID_Map* map)
{
	uris->atom_Float       = map->map(map->handle, LV2_ATOM__Float);
	uris->atom_Int         = map->map(map->handle, LV2_ATOM__Int);
	uris->atom_Vector      = map->map(map->handle, LV2_ATOM__Vector);
	uris->peaks_PeakUpdate = map->map(map->handle, PEAKS__PeakUpdate);
	uris->peaks_magnitudes = map->map(map->handle, PEAKS__magnitudes);
	uris->peaks_offset     = map->map(map->handle, PEAKS__offset);
	uris->peaks_total      = map->map(map->handle, PEAKS__total);
}
----------


Initialise peaks sender.  The new sender is inactive and will do nothing
when `peaks_sender_send()` is called, until a transmission is started with
`peaks_sender_start()`.

[source,c]
----------
static inline PeaksSender*
peaks_sender_init(PeaksSender* sender, LV2_URID_Map* map)
{
	memset(sender, 0, sizeof(*sender));
	peaks_map_uris(&sender->uris, map);
	return sender;
}
----------


Prepare to start a new peaks transmission.  After this is called, the peaks
can be sent with successive calls to `peaks_sender_send()`.

[source,c]
----------
static inline void
peaks_sender_start(PeaksSender* sender,
                   const float* samples,
                   uint32_t     n_samples,
                   uint32_t     n_peaks)
{
	sender->samples        = samples;
	sender->n_samples      = n_samples;
	sender->n_peaks        = n_peaks;
	sender->current_offset = 0;
	sender->sending        = true;
}
----------


Forge a message which sends a range of peaks.  Writes a peaks:PeakUpdate
object to `forge`, like:

[source,turtle]
----
[]
	   a peaks:PeakUpdate ;
	   peaks:offset 256 ;
	   peaks:total 1024 ;
	   peaks:magnitudes [ 0.2f, 0.3f, ... ] .
----

[source,c]
----------
static inline bool
peaks_sender_send(PeaksSender*    sender,
                  LV2_Atom_Forge* forge,
                  uint32_t        n_frames,
                  uint32_t        offset)
{
	const PeaksURIs* uris = &sender->uris;
	if (!sender->sending || sender->current_offset >= sender->n_peaks) {
		return sender->sending = false;
	}

	// Start PeakUpdate object
	lv2_atom_forge_frame_time(forge, offset);
	LV2_Atom_Forge_Frame frame;
	lv2_atom_forge_object(forge, &frame, 0, uris->peaks_PeakUpdate);

	// eg:offset = OFFSET
	lv2_atom_forge_key(forge, uris->peaks_offset);
	lv2_atom_forge_int(forge, sender->current_offset);

	// eg:total = TOTAL
	lv2_atom_forge_key(forge, uris->peaks_total);
	lv2_atom_forge_int(forge, sender->n_peaks);

	// eg:magnitudes = Vector<Float>(PEAK, PEAK, ...)
	lv2_atom_forge_key(forge, uris->peaks_magnitudes);
	LV2_Atom_Forge_Frame vec_frame;
	lv2_atom_forge_vector_head(
		forge, &vec_frame, sizeof(float), uris->atom_Float);

	// Calculate how many peaks to send this update
	const int      chunk_size = MAX(1, sender->n_samples / sender->n_peaks);
	const uint32_t space      = forge->size - forge->offset;
	const uint32_t remaining  = sender->n_peaks - sender->current_offset;
	const int      n_update   = MIN(remaining,
	                                MIN(n_frames / 4, space / sizeof(float)));

	// Calculate peak (maximum magnitude) for each chunk
	for (int i = 0; i < n_update; ++i) {
		const int start = (sender->current_offset + i) * chunk_size;
		float     peak  = 0.0f;
		for (int j = 0; j < chunk_size; ++j) {
			peak = fmaxf(peak, fabsf(sender->samples[start + j]));
		}
		lv2_atom_forge_float(forge, peak);
	}

	// Finish message
	lv2_atom_forge_pop(forge, &vec_frame);
	lv2_atom_forge_pop(forge, &frame);

	sender->current_offset += n_update;
	return true;
}
----------


Initialise a peaks receiver.  The receiver stores an array of all peaks,
which is updated incrementally with peaks_receiver_receive().

[source,c]
----------
static inline PeaksReceiver*
peaks_receiver_init(PeaksReceiver* receiver, LV2_URID_Map* map)
{
	memset(receiver, 0, sizeof(*receiver));
	peaks_map_uris(&receiver->uris, map);
	return receiver;
}
----------


Clear stored peaks and free all memory.  This should be called when the
peaks are to be updated with a different audio source.

[source,c]
----------
static inline void
peaks_receiver_clear(PeaksReceiver* receiver)
{
	free(receiver->peaks);
	receiver->peaks   = NULL;
	receiver->n_peaks = 0;
}
----------


Handle PeakUpdate message.

The stored peaks array is updated with the slice of peaks in `update`,
resizing if necessary while preserving contents.

Returns 0 if peaks have been updated, negative on error.

[source,c]
----------
static inline int
peaks_receiver_receive(PeaksReceiver* receiver, const LV2_Atom_Object* update)
{
	const PeaksURIs* uris = &receiver->uris;

	// Get properties of interest from update
	const LV2_Atom_Int*    offset = NULL;
	const LV2_Atom_Int*    total  = NULL;
	const LV2_Atom_Vector* peaks  = NULL;
	lv2_atom_object_get_typed(update,
	                          uris->peaks_offset,     &offset, uris->atom_Int,
	                          uris->peaks_total,      &total,  uris->atom_Int,
	                          uris->peaks_magnitudes, &peaks,  uris->atom_Vector,
	                          0);

	if (!offset || !total || !peaks ||
	    peaks->body.child_type != uris->atom_Float) {
		return -1;  // Invalid update
	}

	const uint32_t n = (uint32_t)total->body;
	if (receiver->n_peaks != n) {
		// Update is for a different total number of peaks, resize
		receiver->peaks = (float*)realloc(receiver->peaks, n * sizeof(float));
		if (receiver->n_peaks > 0 && receiver->n_peaks < n) {
			/* The peaks array is being expanded.  Copy the old peaks,
			   duplicating each as necessary to fill the new peaks buffer.
			   This preserves the current peaks so that the peaks array can be
			   reasonably drawn at any time, but the resolution will increase
			   as new updates arrive. */
			const int n_per = n / receiver->n_peaks;
			for (int i = n - 1; i >= 0; --i) {
				receiver->peaks[i] = receiver->peaks[i / n_per];
			}
		} else if (receiver->n_peaks > 0) {
			/* The peak array is being shrunk.  Similar to the above. */
			const int n_per = receiver->n_peaks / n;
			for (int i = n - 1; i >= 0; --i) {
				receiver->peaks[i] = receiver->peaks[i * n_per];
			}
		}
		receiver->n_peaks = n;
	}

	// Copy vector contents to corresponding range in peaks array
	memcpy(receiver->peaks + offset->body,
	       peaks + 1,
	       peaks->atom.size - sizeof(LV2_Atom_Vector_Body));

	return 0;
}

#endif // PEAKS_H_INCLUDED
----------
=== uris.h ===
[source,c]
----------
#ifndef SAMPLER_URIS_H
#define SAMPLER_URIS_H

#include "lv2/log/log.h"
#include "lv2/midi/midi.h"
#include "lv2/parameters/parameters.h"
#include "lv2/patch/patch.h"
#include "lv2/state/state.h"

#include <stdio.h>

#define EG_SAMPLER_URI          "http://lv2plug.in/plugins/eg-sampler"
#define EG_SAMPLER__applySample EG_SAMPLER_URI "#applySample"
#define EG_SAMPLER__freeSample  EG_SAMPLER_URI "#freeSample"
#define EG_SAMPLER__sample      EG_SAMPLER_URI "#sample"

typedef struct {
	LV2_URID atom_Float;
	LV2_URID atom_Path;
	LV2_URID atom_Resource;
	LV2_URID atom_Sequence;
	LV2_URID atom_URID;
	LV2_URID atom_eventTransfer;
	LV2_URID eg_applySample;
	LV2_URID eg_freeSample;
	LV2_URID eg_sample;
	LV2_URID midi_Event;
	LV2_URID param_gain;
	LV2_URID patch_Get;
	LV2_URID patch_Set;
	LV2_URID patch_accept;
	LV2_URID patch_property;
	LV2_URID patch_value;
} SamplerURIs;

static inline void
map_sampler_uris(LV2_URID_Map* map, SamplerURIs* uris)
{
	uris->atom_Float         = map->map(map->handle, LV2_ATOM__Float);
	uris->atom_Path          = map->map(map->handle, LV2_ATOM__Path);
	uris->atom_Resource      = map->map(map->handle, LV2_ATOM__Resource);
	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_applySample     = map->map(map->handle, EG_SAMPLER__applySample);
	uris->eg_freeSample      = map->map(map->handle, EG_SAMPLER__freeSample);
	uris->eg_sample          = map->map(map->handle, EG_SAMPLER__sample);
	uris->midi_Event         = map->map(map->handle, LV2_MIDI__MidiEvent);
	uris->param_gain         = map->map(map->handle, LV2_PARAMETERS__gain);
	uris->patch_Get          = map->map(map->handle, LV2_PATCH__Get);
	uris->patch_Set          = map->map(map->handle, LV2_PATCH__Set);
	uris->patch_accept       = map->map(map->handle, LV2_PATCH__accept);
	uris->patch_property     = map->map(map->handle, LV2_PATCH__property);
	uris->patch_value        = map->map(map->handle, LV2_PATCH__value);
}
----------


Write a message like the following to `forge`:
[source,turtle]
----
[]
a patch:Set ;
patch:property eg:sample ;
patch:value </home/me/foo.wav> .
----

[source,c]
----------
static inline LV2_Atom_Forge_Ref
write_set_file(LV2_Atom_Forge*    forge,
               const SamplerURIs* uris,
               const char*        filename,
               const uint32_t     filename_len)
{
	LV2_Atom_Forge_Frame frame;
	LV2_Atom_Forge_Ref   set = lv2_atom_forge_object(
		forge, &frame, 0, uris->patch_Set);

	lv2_atom_forge_key(forge, uris->patch_property);
	lv2_atom_forge_urid(forge, uris->eg_sample);
	lv2_atom_forge_key(forge, uris->patch_value);
	lv2_atom_forge_path(forge, filename, filename_len);

	lv2_atom_forge_pop(forge, &frame);
	return set;
}
----------


Get the file path from `obj` which is a message like:
[source,turtle]
----
[]
a patch:Set ;
patch:property eg:sample ;
patch:value </home/me/foo.wav> .
----

[source,c]
----------
static inline const char*
read_set_file(const SamplerURIs*     uris,
              const LV2_Atom_Object* obj)
{
	if (obj->body.otype != uris->patch_Set) {
		fprintf(stderr, "Ignoring unknown message type %d\n", obj->body.otype);
		return NULL;
	}

	/* Get property URI. */
	const LV2_Atom* property = NULL;
	lv2_atom_object_get(obj, uris->patch_property, &property, 0);
	if (!property) {
		fprintf(stderr, "Malformed set message has no body.\n");
		return NULL;
	} else if (property->type != uris->atom_URID) {
		fprintf(stderr, "Malformed set message has non-URID property.\n");
		return NULL;
	} else if (((const LV2_Atom_URID*)property)->body != uris->eg_sample) {
		fprintf(stderr, "Set message for unknown property.\n");
		return NULL;
	}

	/* Get value. */
	const LV2_Atom* value = NULL;
	lv2_atom_object_get(obj, uris->patch_value, &value, 0);
	if (!value) {
		fprintf(stderr, "Malformed set message has no value.\n");
		return NULL;
	} else if (value->type != uris->atom_Path) {
		fprintf(stderr, "Set message value is not a Path.\n");
		return NULL;
	}

	return (const char*)LV2_ATOM_BODY_CONST(value);
}

#endif  /* SAMPLER_URIS_H */
----------
== Simple Oscilloscope ==

This plugin displays the waveform of an incoming audio signal using a simple
GTK+Cairo GUI.

This plugin illustrates:

- UI <==> Plugin communication via http://lv2plug.in/ns/ext/atom/[LV2 Atom] events
- Atom vector usage and resize-port extension
- Save/Restore UI state by communicating state to backend
- Saving simple key/value state via the http://lv2plug.in/ns/ext/state/[LV2 State] extension
- Cairo drawing and partial exposure

This plugin intends to outline the basics for building visualization plugins
that rely on atom communication.  The UI looks like an oscilloscope, but is not
a real oscilloscope implementation:

- There is no display synchronisation, results will depend on LV2 host.
- It displays raw audio samples, which a proper scope must not do.
- The display itself just connects min/max line segments.
- No triggering or synchronization.
- No labels, no scale, no calibration, no markers, no numeric readout, etc.

Addressing these issues is beyond the scope of this example.

Please see http://lac.linuxaudio.org/2013/papers/36.pdf for scope design,
https://wiki.xiph.org/Videos/Digital_Show_and_Tell for background information,
and http://lists.lv2plug.in/pipermail/devel-lv2plug.in/2013-November/000545.html
for general LV2 related conceptual criticism regarding real-time visualizations.

A proper oscilloscope based on this example can be found at
https://github.com/x42/sisco.lv2

=== manifest.ttl.in ===
[source,turtle]
---------------
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix ui:   <http://lv2plug.in/ns/extensions/ui#> .
---------------


==== Mono plugin variant ====

[source,turtle]
---------------
<http://lv2plug.in/plugins/eg-scope#Mono>
	a lv2:Plugin ;
	lv2:binary <examploscope@LIB_EXT@>  ;
	rdfs:seeAlso <examploscope.ttl> .
---------------


==== Stereo plugin variant ====

[source,turtle]
---------------
<http://lv2plug.in/plugins/eg-scope#Stereo>
	a lv2:Plugin ;
	lv2:binary <examploscope@LIB_EXT@>  ;
	rdfs:seeAlso <examploscope.ttl> .
---------------


==== Gtk 2.0 UI ====

[source,turtle]
---------------
<http://lv2plug.in/plugins/eg-scope#ui>
	a ui:GtkUI ;
	ui:binary <examploscope_ui@LIB_EXT@> ;
	rdfs:seeAlso <examploscope.ttl> .
---------------
=== examploscope.c ===
[source,c]
----------
#include "./uris.h"

#include "lv2/atom/atom.h"
#include "lv2/atom/forge.h"
#include "lv2/atom/util.h"
#include "lv2/core/lv2.h"
#include "lv2/core/lv2_util.h"
#include "lv2/log/log.h"
#include "lv2/log/logger.h"
#include "lv2/state/state.h"
#include "lv2/urid/urid.h"

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
----------


==== Private Plugin Instance Structure ====

In addition to the usual port buffers and features, this plugin stores the
state of the UI here, so it can be opened and closed without losing the
current settings.  The UI state is communicated between the plugin and the
UI using atom messages via a sequence port, similarly to MIDI I/O.

[source,c]
----------
typedef struct {
	// Port buffers
	float*                   input[2];
	float*                   output[2];
	const LV2_Atom_Sequence* control;
	LV2_Atom_Sequence*       notify;

	// Atom forge and URI mapping
	LV2_URID_Map*        map;
	ScoLV2URIs           uris;
	LV2_Atom_Forge       forge;
	LV2_Atom_Forge_Frame frame;

	// Log feature and convenience API
	LV2_Log_Logger logger;

	// Instantiation settings
	uint32_t n_channels;
	double   rate;

	// UI state
	bool     ui_active;
	bool     send_settings_to_ui;
	float    ui_amp;
	uint32_t ui_spp;
} EgScope;
----------


==== Port Indices ====

[source,c]
----------
typedef enum {
	SCO_CONTROL = 0,  // Event input
	SCO_NOTIFY  = 1,  // Event output
	SCO_INPUT0  = 2,  // Audio input 0
	SCO_OUTPUT0 = 3,  // Audio output 0
	SCO_INPUT1  = 4,  // Audio input 1 (stereo variant)
	SCO_OUTPUT1 = 5,  // Audio input 2 (stereo variant)
} PortIndex;
----------


==== Instantiate Method ====

[source,c]
----------
static LV2_Handle
instantiate(const LV2_Descriptor*     descriptor,
            double                    rate,
            const char*               bundle_path,
            const LV2_Feature* const* features)
{
	(void)descriptor;   // Unused variable
	(void)bundle_path;  // Unused variable

	// Allocate and initialise instance structure.
	EgScope* self = (EgScope*)calloc(1, sizeof(EgScope));
	if (!self) {
		return NULL;
	}

	// Get host features
	const char* missing = lv2_features_query(
		features,
		LV2_LOG__log,  &self->logger.log, false,
		LV2_URID__map, &self->map,        true,
		NULL);
	lv2_log_logger_set_map(&self->logger, self->map);
	if (missing) {
		lv2_log_error(&self->logger, "Missing feature <%s>\n", missing);
		free(self);
		return NULL;
	}

	// Decide which variant to use depending on the plugin URI
	if (!strcmp(descriptor->URI, SCO_URI "#Stereo")) {
		self->n_channels = 2;
	} else if (!strcmp(descriptor->URI, SCO_URI "#Mono")) {
		self->n_channels = 1;
	} else {
		free(self);
		return NULL;
	}

	// Initialise local variables
	self->ui_active           = false;
	self->send_settings_to_ui = false;
	self->rate                = rate;

	// Set default UI settings
	self->ui_spp = 50;
	self->ui_amp = 1.0;

	// Map URIs and initialise forge/logger
	map_sco_uris(self->map, &self->uris);
	lv2_atom_forge_init(&self->forge, self->map);

	return (LV2_Handle)self;
}
----------


==== Connect Port Method ====

[source,c]
----------
static void
connect_port(LV2_Handle handle,
             uint32_t   port,
             void*      data)
{
	EgScope* self = (EgScope*)handle;

	switch ((PortIndex)port) {
	case SCO_CONTROL:
		self->control = (const LV2_Atom_Sequence*)data;
		break;
	case SCO_NOTIFY:
		self->notify = (LV2_Atom_Sequence*)data;
		break;
	case SCO_INPUT0:
		self->input[0] = (float*)data;
		break;
	case SCO_OUTPUT0:
		self->output[0] = (float*)data;
		break;
	case SCO_INPUT1:
		self->input[1] = (float*)data;
		break;
	case SCO_OUTPUT1:
		self->output[1] = (float*)data;
		break;
	}
}
----------


==== Utility Function: `tx_rawaudio` ====

This function forges a message for sending a vector of raw data.  The object
is a http://lv2plug.in/ns/ext/atom#Blank[Blank] with a few properties, like:
[source,turtle]
--------
[]
	a sco:RawAudio ;
	sco:channelID 0 ;
	sco:audioData [ 0.0, 0.0, ... ] .
--------

where the value of the `sco:audioData` property, `[ 0.0, 0.0, ... ]`, is a
http://lv2plug.in/ns/ext/atom#Vector[Vector] of
http://lv2plug.in/ns/ext/atom#Float[Float].

[source,c]
----------
static void
tx_rawaudio(LV2_Atom_Forge* forge,
            ScoLV2URIs*     uris,
            const int32_t   channel,
            const size_t    n_samples,
            const float*    data)
{
	LV2_Atom_Forge_Frame frame;

	// Forge container object of type 'RawAudio'
	lv2_atom_forge_frame_time(forge, 0);
	lv2_atom_forge_object(forge, &frame, 0, uris->RawAudio);

	// Add integer 'channelID' property
	lv2_atom_forge_key(forge, uris->channelID);
	lv2_atom_forge_int(forge, channel);

	// Add vector of floats 'audioData' property
	lv2_atom_forge_key(forge, uris->audioData);
	lv2_atom_forge_vector(
		forge, sizeof(float), uris->atom_Float, n_samples, data);

	// Close off object
	lv2_atom_forge_pop(forge, &frame);
}
----------


==== Run Method ====

[source,c]
----------
static void
run(LV2_Handle handle, uint32_t n_samples)
{
	EgScope* self = (EgScope*)handle;

	/* Ensure notify port buffer is large enough to hold all audio-samples and
	   configuration settings.  A minimum size was requested in the .ttl file,
	   but check here just to be sure.

	   TODO: Explain these magic numbers.
	*/
	const size_t   size  = (sizeof(float) * n_samples + 64) * self->n_channels;
	const uint32_t space = self->notify->atom.size;
	if (space < size + 128) {
		/* Insufficient space, report error and do nothing.  Note that a
		   real-time production plugin mustn't call log functions in run(), but
		   this can be useful for debugging and example purposes.
		*/
		lv2_log_error(&self->logger, "Buffer size is insufficient\n");
		return;
	}

	// Prepare forge buffer and initialize atom-sequence
	lv2_atom_forge_set_buffer(&self->forge, (uint8_t*)self->notify, space);
	lv2_atom_forge_sequence_head(&self->forge, &self->frame, 0);

	/* Send settings to UI

	   The plugin can continue to run while the UI is closed and re-opened.
	   The state and settings of the UI are kept here and transmitted to the UI
	   every time it asks for them or if the user initializes a 'load preset'.
	*/
	if (self->send_settings_to_ui && self->ui_active) {
		self->send_settings_to_ui = false;
		// Forge container object of type 'ui_state'
		LV2_Atom_Forge_Frame frame;
		lv2_atom_forge_frame_time(&self->forge, 0);
		lv2_atom_forge_object(&self->forge, &frame, 0, self->uris.ui_State);

		// Add UI state as properties
		lv2_atom_forge_key(&self->forge, self->uris.ui_spp);
		lv2_atom_forge_int(&self->forge, self->ui_spp);
		lv2_atom_forge_key(&self->forge, self->uris.ui_amp);
		lv2_atom_forge_float(&self->forge, self->ui_amp);
		lv2_atom_forge_key(&self->forge, self->uris.param_sampleRate);
		lv2_atom_forge_float(&self->forge, self->rate);
		lv2_atom_forge_pop(&self->forge, &frame);
	}

	// Process incoming events from GUI
	if (self->control) {
		const LV2_Atom_Event* ev = lv2_atom_sequence_begin(
			&(self->control)->body);
		// For each incoming message...
		while (!lv2_atom_sequence_is_end(
			       &self->control->body, self->control->atom.size, ev)) {
			// If the event is an atom:Blank object
			if (lv2_atom_forge_is_object_type(&self->forge, ev->body.type)) {
				const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body;
				if (obj->body.otype == self->uris.ui_On) {
					// If the object is a ui-on, the UI was activated
					self->ui_active           = true;
					self->send_settings_to_ui = true;
				} else if (obj->body.otype == self->uris.ui_Off) {
					// If the object is a ui-off, the UI was closed
					self->ui_active = false;
				} else if (obj->body.otype == self->uris.ui_State) {
					// If the object is a ui-state, it's the current UI settings
					const LV2_Atom* spp = NULL;
					const LV2_Atom* amp = NULL;
					lv2_atom_object_get(obj, self->uris.ui_spp, &spp,
					                    self->uris.ui_amp, &amp,
					                    0);
					if (spp) {
						self->ui_spp = ((const LV2_Atom_Int*)spp)->body;
					}
					if (amp) {
						self->ui_amp = ((const LV2_Atom_Float*)amp)->body;
					}
				}
			}
			ev = lv2_atom_sequence_next(ev);
		}
	}

	// Process audio data
	for (uint32_t c = 0; c < self->n_channels; ++c) {
		if (self->ui_active) {
			// If UI is active, send raw audio data to UI
			tx_rawaudio(&self->forge, &self->uris, c, n_samples, self->input[c]);
		}
		// If not processing audio in-place, forward audio
		if (self->input[c] != self->output[c]) {
			memcpy(self->output[c], self->input[c], sizeof(float) * n_samples);
		}
	}

	// Close off sequence
	lv2_atom_forge_pop(&self->forge, &self->frame);
}

static void
cleanup(LV2_Handle handle)
{
	free(handle);
}
----------


==== State Methods ====

This plugin's state consists of two basic properties: one `int` and one
`float`.  No files are used.  Note these values are POD, but not portable,
since different machines may have a different integer endianness or floating
point format.  However, since standard Atom types are used, a good host will
be able to save them portably as text anyway.

[source,c]
----------
static LV2_State_Status
state_save(LV2_Handle                instance,
           LV2_State_Store_Function  store,
           LV2_State_Handle          handle,
           uint32_t                  flags,
           const LV2_Feature* const* features)
{
	EgScope* self = (EgScope*)instance;
	if (!self) {
		return LV2_STATE_SUCCESS;
	}

	store(handle, self->uris.ui_spp,
	      (void*)&self->ui_spp, sizeof(uint32_t),
	      self->uris.atom_Int,
	      LV2_STATE_IS_POD);

	store(handle, self->uris.ui_amp,
	      (void*)&self->ui_amp, sizeof(float),
	      self->uris.atom_Float,
	      LV2_STATE_IS_POD);

	return LV2_STATE_SUCCESS;
}

static LV2_State_Status
state_restore(LV2_Handle                  instance,
              LV2_State_Retrieve_Function retrieve,
              LV2_State_Handle            handle,
              uint32_t                    flags,
              const LV2_Feature* const*   features)
{
	EgScope* self = (EgScope*)instance;

	size_t   size;
	uint32_t type;
	uint32_t valflags;

	const void* spp = retrieve(
		handle, self->uris.ui_spp, &size, &type, &valflags);
	if (spp && size == sizeof(uint32_t) && type == self->uris.atom_Int) {
		self->ui_spp              = *((const uint32_t*)spp);
		self->send_settings_to_ui = true;
	}

	const void* amp = retrieve(
		handle, self->uris.ui_amp, &size, &type, &valflags);
	if (amp && size == sizeof(float) && type == self->uris.atom_Float) {
		self->ui_amp              = *((const float*)amp);
		self->send_settings_to_ui = true;
	}

	return LV2_STATE_SUCCESS;
}

static const void*
extension_data(const char* uri)
{
	static const LV2_State_Interface state = { state_save, state_restore };
	if (!strcmp(uri, LV2_STATE__interface)) {
		return &state;
	}
	return NULL;
}
----------


==== Plugin Descriptors ====

[source,c]
----------
static const LV2_Descriptor descriptor_mono = {
	SCO_URI "#Mono",
	instantiate,
	connect_port,
	NULL,
	run,
	NULL,
	cleanup,
	extension_data
};

static const LV2_Descriptor descriptor_stereo = {
	SCO_URI "#Stereo",
	instantiate,
	connect_port,
	NULL,
	run,
	NULL,
	cleanup,
	extension_data
};

LV2_SYMBOL_EXPORT
const LV2_Descriptor*
lv2_descriptor(uint32_t index)
{
	switch (index) {
	case 0:
		return &descriptor_mono;
	case 1:
		return &descriptor_stereo;
	default:
		return NULL;
	}
}
----------
=== examploscope_ui.c ===
[source,c]
----------
#include "./uris.h"

#include "lv2/atom/atom.h"
#include "lv2/atom/forge.h"
#include "lv2/atom/util.h"
#include "lv2/core/lv2.h"
#include "lv2/ui/ui.h"
#include "lv2/urid/urid.h"

#include <cairo.h>
#include <gdk/gdk.h>
#include <glib-object.h>
#include <glib.h>
#include <gobject/gclosure.h>
#include <gtk/gtk.h>

#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Drawing area size
#define DAWIDTH  (640)
#define DAHEIGHT (200)
----------


Max continuous points on path.  Many short-path segments are
expensive|inefficient long paths are not supported by all surfaces (usually
its a miter - not point - limit, depending on used cairo backend)

[source,c]
----------
#define MAX_CAIRO_PATH (128)
----------


Representation of the raw audio-data for display (min | max) values for a
given 'index' position.

[source,c]
----------
typedef struct {
	float data_min[DAWIDTH];
	float data_max[DAWIDTH];

	uint32_t idx;
	uint32_t sub;
} ScoChan;

typedef struct {
	LV2_Atom_Forge forge;
	LV2_URID_Map*  map;
	ScoLV2URIs     uris;

	LV2UI_Write_Function write;
	LV2UI_Controller     controller;

	GtkWidget*     hbox;
	GtkWidget*     vbox;
	GtkWidget*     sep[2];
	GtkWidget*     darea;
	GtkWidget*     btn_pause;
	GtkWidget*     lbl_speed;
	GtkWidget*     lbl_amp;
	GtkWidget*     spb_speed;
	GtkWidget*     spb_amp;
	GtkAdjustment* spb_speed_adj;
	GtkAdjustment* spb_amp_adj;

	ScoChan  chn[2];
	uint32_t stride;
	uint32_t n_channels;
	bool     paused;
	float    rate;
	bool     updating;
} EgScopeUI;
----------


Send current UI settings to backend.

[source,c]
----------
static void
send_ui_state(LV2UI_Handle handle)
{
	EgScopeUI*  ui   = (EgScopeUI*)handle;
	const float gain = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_amp));

	// Use local buffer on the stack to build atom
	uint8_t obj_buf[1024];
	lv2_atom_forge_set_buffer(&ui->forge, obj_buf, sizeof(obj_buf));

	// Start a ui:State object
	LV2_Atom_Forge_Frame frame;
	LV2_Atom*            msg = (LV2_Atom*)lv2_atom_forge_object(
		&ui->forge, &frame, 0, ui->uris.ui_State);

	// msg[samples-per-pixel] = integer
	lv2_atom_forge_key(&ui->forge, ui->uris.ui_spp);
	lv2_atom_forge_int(&ui->forge, ui->stride);

	// msg[amplitude] = float
	lv2_atom_forge_key(&ui->forge, ui->uris.ui_amp);
	lv2_atom_forge_float(&ui->forge, gain);

	// Finish ui:State object
	lv2_atom_forge_pop(&ui->forge, &frame);

	// Send message to plugin port '0'
	ui->write(ui->controller,
	          0,
	          lv2_atom_total_size(msg),
	          ui->uris.atom_eventTransfer,
	          msg);
}
----------


Notify backend that UI is closed.

[source,c]
----------
static void
send_ui_disable(LV2UI_Handle handle)
{
	EgScopeUI* ui = (EgScopeUI*)handle;
	send_ui_state(handle);

	uint8_t obj_buf[64];
	lv2_atom_forge_set_buffer(&ui->forge, obj_buf, sizeof(obj_buf));

	LV2_Atom_Forge_Frame frame;
	LV2_Atom*            msg = (LV2_Atom*)lv2_atom_forge_object(
		&ui->forge, &frame, 0, ui->uris.ui_Off);
	lv2_atom_forge_pop(&ui->forge, &frame);
	ui->write(ui->controller,
	          0,
	          lv2_atom_total_size(msg),
	          ui->uris.atom_eventTransfer,
	          msg);
}
----------


Notify backend that UI is active.

The plugin should send state and enable data transmission.

[source,c]
----------
static void
send_ui_enable(LV2UI_Handle handle)
{
	EgScopeUI* ui = (EgScopeUI*)handle;

	uint8_t obj_buf[64];
	lv2_atom_forge_set_buffer(&ui->forge, obj_buf, sizeof(obj_buf));

	LV2_Atom_Forge_Frame frame;
	LV2_Atom*            msg = (LV2_Atom*)lv2_atom_forge_object(
		&ui->forge, &frame, 0, ui->uris.ui_On);
	lv2_atom_forge_pop(&ui->forge, &frame);
	ui->write(ui->controller,
	          0,
	          lv2_atom_total_size(msg),
	          ui->uris.atom_eventTransfer,
	          msg);
}
----------


Gtk widget callback.

[source,c]
----------
static gboolean
on_cfg_changed(GtkWidget* widget, gpointer data)
{
	EgScopeUI* ui = (EgScopeUI*)data;
	if (!ui->updating) {
		// Only send UI state if the change is from user interaction
		send_ui_state(data);
	}
	return TRUE;
}
----------


Gdk drawing area draw callback.

Called in Gtk's main thread and uses Cairo to draw the data.

[source,c]
----------
static gboolean
on_expose_event(GtkWidget* widget, GdkEventExpose* ev, gpointer data)
{
	EgScopeUI*  ui   = (EgScopeUI*)data;
	const float gain = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_amp));

	// Get cairo type for the gtk window
	cairo_t* cr;
	cr = gdk_cairo_create(ui->darea->window);

	// Limit cairo-drawing to exposed area
	cairo_rectangle(cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
	cairo_clip(cr);

	// Clear background
	cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
	cairo_rectangle(cr, 0, 0, DAWIDTH, DAHEIGHT * ui->n_channels);
	cairo_fill(cr);

	cairo_set_line_width(cr, 1.0);

	const uint32_t start = ev->area.x;
	const uint32_t end   = ev->area.x + ev->area.width;

	assert(start < DAWIDTH);
	assert(end <= DAWIDTH);
	assert(start < end);

	for (uint32_t c = 0; c < ui->n_channels; ++c) {
		ScoChan* chn = &ui->chn[c];

		/* Drawing area Y-position of given sample-value.
		 * Note: cairo-pixel at 0 spans -0.5 .. +0.5, hence (DAHEIGHT / 2.0 -0.5)
		 * also the cairo Y-axis points upwards (hence 'minus value')
		 *
		 * == (   DAHEIGHT * (CHN)        // channel offset
		 *      + (DAHEIGHT / 2) - 0.5    // vertical center -- '0'
		 *      - (DAHEIGHT / 2) * (VAL) * (GAIN)
		 *    )
		 */
		const float chn_y_offset = DAHEIGHT * c + DAHEIGHT * 0.5f - 0.5f;
		const float chn_y_scale  = DAHEIGHT * 0.5f * gain;

#define CYPOS(VAL) (chn_y_offset - (VAL) * chn_y_scale)

		cairo_save(cr);

		/* Restrict drawing to current channel area, don't bleed drawing into
		   neighboring channels. */
		cairo_rectangle(cr, 0, DAHEIGHT * c, DAWIDTH, DAHEIGHT);
		cairo_clip(cr);

		// Set color of wave-form
		cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0);

		/* This is a somewhat 'smart' mechanism to plot audio data using
		   alternating up/down line-directions.  It works well for both cases:
		   1 pixel <= 1 sample and 1 pixel represents more than 1 sample, but
		   is not ideal for either. */
		if (start == chn->idx) {
			cairo_move_to(cr, start - 0.5, CYPOS(0));
		} else {
			cairo_move_to(cr, start - 0.5, CYPOS(chn->data_max[start]));
		}

		uint32_t pathlength = 0;
		for (uint32_t i = start; i < end; ++i) {
			if (i == chn->idx) {
				continue;
			} else if (i % 2) {
				cairo_line_to(cr, i - .5, CYPOS(chn->data_min[i]));
				cairo_line_to(cr, i - .5, CYPOS(chn->data_max[i]));
				++pathlength;
			} else {
				cairo_line_to(cr, i - .5, CYPOS(chn->data_max[i]));
				cairo_line_to(cr, i - .5, CYPOS(chn->data_min[i]));
				++pathlength;
			}

			
----------


Limit the max cairo path length.  This is an optimization trade
			    off: too short path: high load CPU/GPU load.  too-long path:
			    bad anti-aliasing, or possibly lost points

[source,c]
----------
			if (pathlength > MAX_CAIRO_PATH) {
				pathlength = 0;
				cairo_stroke(cr);
				if (i % 2) {
					cairo_move_to(cr, i - .5, CYPOS(chn->data_max[i]));
				} else {
					cairo_move_to(cr, i - .5, CYPOS(chn->data_min[i]));
				}
			}
		}

		if (pathlength > 0) {
			cairo_stroke(cr);
		}

		// Draw current position vertical line if display is slow
		if (ui->stride >= ui->rate / 4800.0f || ui->paused) {
			cairo_set_source_rgba(cr, .9, .2, .2, .6);
			cairo_move_to(cr, chn->idx - .5, DAHEIGHT * c);
			cairo_line_to(cr, chn->idx - .5, DAHEIGHT * (c + 1));
			cairo_stroke(cr);
		}

		// Undo the 'clipping' restriction
		cairo_restore(cr);

		// Channel separator
		if (c > 0) {
			cairo_set_source_rgba(cr, .5, .5, .5, 1.0);
			cairo_move_to(cr, 0, DAHEIGHT * c - .5);
			cairo_line_to(cr, DAWIDTH, DAHEIGHT * c - .5);
			cairo_stroke(cr);
		}

		// Zero scale line
		cairo_set_source_rgba(cr, .3, .3, .7, .5);
		cairo_move_to(cr, 0, DAHEIGHT * (c + .5) - .5);
		cairo_line_to(cr, DAWIDTH, DAHEIGHT * (c + .5) - .5);
		cairo_stroke(cr);
	}

	cairo_destroy(cr);
	return TRUE;
}
----------


Parse raw audio data and prepare for later drawing.

Note this is a toy example, which is really a waveform display, not an
oscilloscope.  A serious scope would not display samples as is.

Signals above ~ 1/10 of the sampling-rate will not yield a useful visual
display and result in a rather unintuitive representation of the actual
waveform.

Ideally the audio-data would be buffered and upsampled here and after that
written in a display buffer for later use.

For more information, see
https://wiki.xiph.org/Videos/Digital_Show_and_Tell
http://lac.linuxaudio.org/2013/papers/36.pdf
and https://github.com/x42/sisco.lv2

[source,c]
----------
static int
process_channel(EgScopeUI*   ui,
                ScoChan*     chn,
                const size_t n_elem,
                float const* data,
                uint32_t*    idx_start,
                uint32_t*    idx_end)
{
	int overflow = 0;
	*idx_start = chn->idx;
	for (size_t i = 0; i < n_elem; ++i) {
		if (data[i] < chn->data_min[chn->idx]) {
			chn->data_min[chn->idx] = data[i];
		}
		if (data[i] > chn->data_max[chn->idx]) {
			chn->data_max[chn->idx] = data[i];
		}
		if (++chn->sub >= ui->stride) {
			chn->sub = 0;
			chn->idx = (chn->idx + 1) % DAWIDTH;
			if (chn->idx == 0) {
				++overflow;
			}
			chn->data_min[chn->idx] = 1.0;
			chn->data_max[chn->idx] = -1.0;
		}
	}
	*idx_end = chn->idx;
	return overflow;
}
----------


Called via port_event() which is called by the host, typically at a rate of
around 25 FPS.

[source,c]
----------
static void
update_scope(EgScopeUI*    ui,
             const int32_t channel,
             const size_t  n_elem,
             float const*  data)
{
	// Never trust input data which could lead to application failure.
	if (channel < 0 || (uint32_t)channel > ui->n_channels) {
		return;
	}

	// Update state in sync with 1st channel
	if (channel == 0) {
		ui->stride = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_speed));
		const bool paused = gtk_toggle_button_get_active(
			GTK_TOGGLE_BUTTON(ui->btn_pause));

		if (paused != ui->paused) {
			ui->paused = paused;
			gtk_widget_queue_draw(ui->darea);
		}
	}
	if (ui->paused) {
		return;
	}

	uint32_t idx_start;  // Display pixel start
	uint32_t idx_end;    // Display pixel end
	int      overflow;   // Received more audio-data than display-pixel

	// Process this channel's audio-data for display
	ScoChan* chn = &ui->chn[channel];
	overflow = process_channel(ui, chn, n_elem, data, &idx_start, &idx_end);

	// Signal gtk's main thread to redraw the widget after the last channel
	if ((uint32_t)channel + 1 == ui->n_channels) {
		if (overflow > 1) {
			// Redraw complete widget
			gtk_widget_queue_draw(ui->darea);
		} else if (idx_end > idx_start) {
			// Redraw area between start -> end pixel
			gtk_widget_queue_draw_area(ui->darea, idx_start - 2, 0, 3
			                           + idx_end - idx_start,
			                           DAHEIGHT * ui->n_channels);
		} else if (idx_end < idx_start) {
			// Wrap-around: redraw area between 0->start AND end->right-end
			gtk_widget_queue_draw_area(
				ui->darea,
				idx_start - 2, 0,
				3 + DAWIDTH - idx_start, DAHEIGHT * ui->n_channels);
			gtk_widget_queue_draw_area(
				ui->darea,
				0, 0,
				idx_end + 1, DAHEIGHT * ui->n_channels);
		}
	}
}

static LV2UI_Handle
instantiate(const LV2UI_Descriptor*   descriptor,
            const char*               plugin_uri,
            const char*               bundle_path,
            LV2UI_Write_Function      write_function,
            LV2UI_Controller          controller,
            LV2UI_Widget*             widget,
            const LV2_Feature* const* features)
{
	EgScopeUI* ui = (EgScopeUI*)calloc(1, sizeof(EgScopeUI));

	if (!ui) {
		fprintf(stderr, "EgScope.lv2 UI: out of memory\n");
		return NULL;
	}

	ui->map = NULL;
	*widget = NULL;

	if (!strcmp(plugin_uri, SCO_URI "#Mono")) {
		ui->n_channels = 1;
	} else if (!strcmp(plugin_uri, SCO_URI "#Stereo")) {
		ui->n_channels = 2;
	} else {
		free(ui);
		return NULL;
	}

	for (int i = 0; features[i]; ++i) {
		if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) {
			ui->map = (LV2_URID_Map*)features[i]->data;
		}
	}

	if (!ui->map) {
		fprintf(stderr, "EgScope.lv2 UI: Host does not support urid:map\n");
		free(ui);
		return NULL;
	}

	// Initialize private data structure
	ui->write      = write_function;
	ui->controller = controller;

	ui->vbox   = NULL;
	ui->hbox   = NULL;
	ui->darea  = NULL;
	ui->stride = 25;
	ui->paused = false;
	ui->rate   = 48000;

	ui->chn[0].idx = 0;
	ui->chn[0].sub = 0;
	ui->chn[1].idx = 0;
	ui->chn[1].sub = 0;
	memset(ui->chn[0].data_min, 0, sizeof(float) * DAWIDTH);
	memset(ui->chn[0].data_max, 0, sizeof(float) * DAWIDTH);
	memset(ui->chn[1].data_min, 0, sizeof(float) * DAWIDTH);
	memset(ui->chn[1].data_max, 0, sizeof(float) * DAWIDTH);

	map_sco_uris(ui->map, &ui->uris);
	lv2_atom_forge_init(&ui->forge, ui->map);

	// Setup UI
	ui->hbox = gtk_hbox_new(FALSE, 0);
	ui->vbox = gtk_vbox_new(FALSE, 0);

	ui->darea = gtk_drawing_area_new();
	gtk_widget_set_size_request(ui->darea, DAWIDTH, DAHEIGHT * ui->n_channels);

	ui->lbl_speed = gtk_label_new("Samples/Pixel");
	ui->lbl_amp   = gtk_label_new("Amplitude");

	ui->sep[0]    = gtk_hseparator_new();
	ui->sep[1]    = gtk_label_new("");
	ui->btn_pause = gtk_toggle_button_new_with_label("Pause");

	ui->spb_speed_adj = (GtkAdjustment*)gtk_adjustment_new(
			25.0, 1.0, 1000.0, 1.0, 5.0, 0.0);
	ui->spb_speed = gtk_spin_button_new(ui->spb_speed_adj, 1.0, 0);

	ui->spb_amp_adj = (GtkAdjustment*)gtk_adjustment_new(
		1.0, 0.1, 6.0, 0.1, 1.0, 0.0);
	ui->spb_amp = gtk_spin_button_new(ui->spb_amp_adj, 0.1, 1);

	gtk_box_pack_start(GTK_BOX(ui->hbox), ui->darea,     FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(ui->hbox), ui->vbox,      FALSE, FALSE, 4);

	gtk_box_pack_start(GTK_BOX(ui->vbox), ui->lbl_speed, FALSE, FALSE, 2);
	gtk_box_pack_start(GTK_BOX(ui->vbox), ui->spb_speed, FALSE, FALSE, 2);
	gtk_box_pack_start(GTK_BOX(ui->vbox), ui->sep[0],    FALSE, FALSE, 8);
	gtk_box_pack_start(GTK_BOX(ui->vbox), ui->lbl_amp,   FALSE, FALSE, 2);
	gtk_box_pack_start(GTK_BOX(ui->vbox), ui->spb_amp,   FALSE, FALSE, 2);
	gtk_box_pack_start(GTK_BOX(ui->vbox), ui->sep[1],     TRUE, FALSE, 8);
	gtk_box_pack_start(GTK_BOX(ui->vbox), ui->btn_pause, FALSE, FALSE, 2);

	g_signal_connect(G_OBJECT(ui->darea), "expose_event",
	                 G_CALLBACK(on_expose_event), ui);
	g_signal_connect(G_OBJECT(ui->spb_amp), "value-changed",
	                 G_CALLBACK(on_cfg_changed), ui);
	g_signal_connect(G_OBJECT(ui->spb_speed), "value-changed",
	                 G_CALLBACK(on_cfg_changed), ui);

	*widget = ui->hbox;

	/* Send UIOn message to plugin, which will request state and enable message
	   transmission. */
	send_ui_enable(ui);

	return ui;
}

static void
cleanup(LV2UI_Handle handle)
{
	EgScopeUI* ui = (EgScopeUI*)handle;
	/* Send UIOff message to plugin, which will save state and disable message
	 * transmission. */
	send_ui_disable(ui);
	gtk_widget_destroy(ui->darea);
	free(ui);
}

static int
recv_raw_audio(EgScopeUI* ui, const LV2_Atom_Object* obj)
{
	const LV2_Atom* chan_val = NULL;
	const LV2_Atom* data_val = NULL;
	const int n_props  = lv2_atom_object_get(
		obj,
		ui->uris.channelID, &chan_val,
		ui->uris.audioData, &data_val,
		NULL);

	if (n_props != 2 ||
	    chan_val->type != ui->uris.atom_Int ||
	    data_val->type != ui->uris.atom_Vector) {
		// Object does not have the required properties with correct types
		fprintf(stderr, "eg-scope.lv2 UI error: Corrupt audio message\n");
		return 1;
	}

	// Get the values we need from the body of the property value atoms
	const int32_t          chn = ((const LV2_Atom_Int*)chan_val)->body;
	const LV2_Atom_Vector* vec = (const LV2_Atom_Vector*)data_val;
	if (vec->body.child_type != ui->uris.atom_Float) {
		return 1;  // Vector has incorrect element type
	}

	// Number of elements = (total size - header size) / element size
	const size_t n_elem = ((data_val->size - sizeof(LV2_Atom_Vector_Body))
	                       / sizeof(float));

	// Float elements immediately follow the vector body header
	const float* data = (const float*)(&vec->body + 1);

	// Update display
	update_scope(ui, chn, n_elem, data);
	return 0;
}

static int
recv_ui_state(EgScopeUI* ui, const LV2_Atom_Object* obj)
{
	const LV2_Atom* spp_val  = NULL;
	const LV2_Atom* amp_val  = NULL;
	const LV2_Atom* rate_val = NULL;
	const int n_props  = lv2_atom_object_get(
		obj,
		ui->uris.ui_spp, &spp_val,
		ui->uris.ui_amp, &amp_val,
		ui->uris.param_sampleRate, &rate_val,
		NULL);

	if (n_props != 3 ||
		spp_val->type != ui->uris.atom_Int ||
		amp_val->type != ui->uris.atom_Float ||
	    rate_val->type != ui->uris.atom_Float) {
		// Object does not have the required properties with correct types
		fprintf(stderr, "eg-scope.lv2 UI error: Corrupt state message\n");
		return 1;
	}

	// Get the values we need from the body of the property value atoms
	const int32_t spp  = ((const LV2_Atom_Int*)spp_val)->body;
	const float   amp  = ((const LV2_Atom_Float*)amp_val)->body;
	const float   rate = ((const LV2_Atom_Float*)rate_val)->body;

	// Disable transmission and update UI
	ui->updating = true;
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(ui->spb_speed), spp);
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(ui->spb_amp),   amp);
	ui->updating = false;
	ui->rate = rate;

	return 0;
}
----------


Receive data from the DSP-backend.

This is called by the host, typically at a rate of around 25 FPS.

Ideally this happens regularly and with relatively low latency, but there
are no hard guarantees about message delivery.

[source,c]
----------
static void
port_event(LV2UI_Handle handle,
           uint32_t     port_index,
           uint32_t     buffer_size,
           uint32_t     format,
           const void*  buffer)
{
	EgScopeUI*      ui   = (EgScopeUI*)handle;
	const LV2_Atom* atom = (const LV2_Atom*)buffer;

	/* Check type of data received
	 *  - format == 0: Control port event (float)
	 *  - format > 0:  Message (atom)
	 */
	if (format == ui->uris.atom_eventTransfer &&
	    lv2_atom_forge_is_object_type(&ui->forge, atom->type)) {
		const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom;
		if (obj->body.otype == ui->uris.RawAudio) {
			recv_raw_audio(ui, obj);
		} else if (obj->body.otype == ui->uris.ui_State) {
			recv_ui_state(ui, obj);
		}
	}
}

static const LV2UI_Descriptor descriptor = {
	SCO_URI "#ui",
	instantiate,
	cleanup,
	port_event,
	NULL
};

LV2_SYMBOL_EXPORT
const LV2UI_Descriptor*
lv2ui_descriptor(uint32_t index)
{
	switch (index) {
	case 0:
		return &descriptor;
	default:
		return NULL;
	}
}
----------
=== uris.h ===
[source,c]
----------
#ifndef SCO_URIS_H
#define SCO_URIS_H

#include "lv2/atom/atom.h"
#include "lv2/atom/forge.h"
#include "lv2/parameters/parameters.h"
#include "lv2/urid/urid.h"

#define SCO_URI "http://lv2plug.in/plugins/eg-scope"

typedef struct {
	// URIs defined in LV2 specifications
	LV2_URID atom_Vector;
	LV2_URID atom_Float;
	LV2_URID atom_Int;
	LV2_URID atom_eventTransfer;
	LV2_URID param_sampleRate;

	/* URIs defined for this plugin.  It is best to re-use existing URIs as
	   much as possible, but plugins may need more vocabulary specific to their
	   needs.  These are used as types and properties for plugin:UI
	   communication, as well as for saving state. */
	LV2_URID RawAudio;
	LV2_URID channelID;
	LV2_URID audioData;
	LV2_URID ui_On;
	LV2_URID ui_Off;
	LV2_URID ui_State;
	LV2_URID ui_spp;
	LV2_URID ui_amp;
} ScoLV2URIs;

static inline void
map_sco_uris(LV2_URID_Map* map, ScoLV2URIs* uris)
{
	uris->atom_Vector        = map->map(map->handle, LV2_ATOM__Vector);
	uris->atom_Float         = map->map(map->handle, LV2_ATOM__Float);
	uris->atom_Int           = map->map(map->handle, LV2_ATOM__Int);
	uris->atom_eventTransfer = map->map(map->handle, LV2_ATOM__eventTransfer);
	uris->param_sampleRate   = map->map(map->handle, LV2_PARAMETERS__sampleRate);

	/* Note the convention that URIs for types are capitalized, and URIs for
	   everything else (mainly properties) are not, just as in LV2
	   specifications. */
	uris->RawAudio  = map->map(map->handle, SCO_URI "#RawAudio");
	uris->audioData = map->map(map->handle, SCO_URI "#audioData");
	uris->channelID = map->map(map->handle, SCO_URI "#channelID");
	uris->ui_On     = map->map(map->handle, SCO_URI "#UIOn");
	uris->ui_Off    = map->map(map->handle, SCO_URI "#UIOff");
	uris->ui_State  = map->map(map->handle, SCO_URI "#UIState");
	uris->ui_spp    = map->map(map->handle, SCO_URI "#ui-spp");
	uris->ui_amp    = map->map(map->handle, SCO_URI "#ui-amp");
}

#endif  /* SCO_URIS_H */
----------
== 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.

=== manifest.ttl.in ===
[source,turtle]
---------------
@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> .
---------------
=== params.ttl ===
[source,turtle]
---------------
@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:

[source,turtle]
---------------
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.

[source,turtle]
---------------
<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:

[source,turtle]
---------------
	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:

[source,turtle]
---------------
		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.

[source,turtle]
---------------
	state:state [
		plug:int 0 ;
		plug:long "0"^^xsd:long ;
		plug:float "0.1234"^^xsd:float ;
		plug:double "0e0"^^xsd:double ;
		plug:bool false ;
		plug:string "Hello, world" ;
		plug:path <params.ttl> ;
		plug:spring "0.0"^^xsd:float ;
		plug:lfo "0.0"^^xsd:float
	] .
---------------
=== params.c ===
[source,c]
----------
#include "state_map.h"

#include "lv2/atom/atom.h"
#include "lv2/atom/forge.h"
#include "lv2/atom/util.h"
#include "lv2/core/lv2.h"
#include "lv2/core/lv2_util.h"
#include "lv2/log/log.h"
#include "lv2/log/logger.h"
#include "lv2/midi/midi.h"
#include "lv2/patch/patch.h"
#include "lv2/state/state.h"
#include "lv2/urid/urid.h"

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.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.

[source,c]
----------
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.

[source,c]
----------
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.

[source,c]
----------
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;
}
----------
=== state_map.h ===
[source,c]
----------
#include "lv2/atom/atom.h"
#include "lv2/urid/urid.h"

#include <stdarg.h>
#include <stdlib.h>
----------


Entry in an array that serves as a dictionary of properties.

[source,c]
----------
typedef struct {
	const char* uri;
	LV2_URID    urid;
	LV2_Atom*   value;
} StateMapItem;
----------


Comparator for StateMapItems sorted by URID.

[source,c]
----------
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.

[source,c]
----------
#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);

[source,c]
----------
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.

[source,c]
----------
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);
}
----------
