[LV2] [PATCH 1/5] add eg05-scope example plugin

Robin Gareus robin at gareus.org
Mon Dec 9 11:41:11 PST 2013


Signed-off-by: Robin Gareus <robin at gareus.org>
---
 plugins/eg05-scope.lv2/README.txt          |   35 ++
 plugins/eg05-scope.lv2/examploscope.c      |  375 +++++++++++++++++
 plugins/eg05-scope.lv2/examploscope.ttl.in |  130 ++++++
 plugins/eg05-scope.lv2/examploscope_ui.c   |  629 ++++++++++++++++++++++++++++
 plugins/eg05-scope.lv2/manifest.ttl.in     |   18 +
 plugins/eg05-scope.lv2/uris.h              |   64 +++
 plugins/eg05-scope.lv2/wscript             |   73 ++++
 7 files changed, 1324 insertions(+)
 create mode 100644 plugins/eg05-scope.lv2/README.txt
 create mode 100644 plugins/eg05-scope.lv2/examploscope.c
 create mode 100644 plugins/eg05-scope.lv2/examploscope.ttl.in
 create mode 100644 plugins/eg05-scope.lv2/examploscope_ui.c
 create mode 100644 plugins/eg05-scope.lv2/manifest.ttl.in
 create mode 100644 plugins/eg05-scope.lv2/uris.h
 create mode 100644 plugins/eg05-scope.lv2/wscript

diff --git a/plugins/eg05-scope.lv2/README.txt b/plugins/eg05-scope.lv2/README.txt
new file mode 100644
index 0000000..9c5512e
--- /dev/null
+++ b/plugins/eg05-scope.lv2/README.txt
@@ -0,0 +1,35 @@
+== Simple Oscilloscope ==
+
+This plugin displays the wave-form of an incoming audio-signal using a
+simple GTK+Cairo GUI.
+
+This plugin illustrates:
+
+- UI <==> Plugin communication via LV2 atom events
+- LV2 Atom vector usage and resize-port extension
+- Save/Restore UI state by communicating state to backend
+- Cairo drawing and partial exposure
+
+and includes explicit source-code annotations regarding UI Threading.
+
+
+This plugin intends to outline the basics for building visualization
+plugins that rely on Atom Vector communication. While the eg-scope
+looks like an Oscilloscope it is not:
+
+* no thread sync. It redraws somewhat randomly depending on LV2 Host
+* it displays raw audio samples (a proper scope must not do that)
+* the display itself just connects min/max line segments
+* no triggering or synchronization
+* no labels, no scale, no calibration, no markers, no numeric readout
+* ...
+
+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
diff --git a/plugins/eg05-scope.lv2/examploscope.c b/plugins/eg05-scope.lv2/examploscope.c
new file mode 100644
index 0000000..cdcab4a
--- /dev/null
+++ b/plugins/eg05-scope.lv2/examploscope.c
@@ -0,0 +1,375 @@
+/* simple scope -- example pipe raw audio data to UI
+ *
+ * Copyright (C) 2013 Robin Gareus <robin at gareus.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
+#include "lv2/lv2plug.in/ns/ext/state/state.h"
+
+#include "./uris.h"
+
+/**
+ * The private data structure of the plugin.
+ */
+typedef struct {
+	/* I/O ports */
+	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;
+
+	/* instatiation settings */
+	uint32_t n_channels;
+	double rate;
+
+	/* the state of the UI is stored here, so that
+	 * the GUI can be displayed & closed
+	 * without loosing current settings.
+	 */
+	bool ui_active;
+	bool send_settings_to_ui;
+	float ui_amp;
+	uint32_t ui_spp;
+
+} EgScope;
+
+typedef enum {
+	SCO_CONTROL  = 0,
+	SCO_NOTIFY   = 1,
+	SCO_INPUT0   = 2,
+	SCO_OUTPUT0  = 3,
+	SCO_INPUT1   = 4,
+	SCO_OUTPUT1  = 5,
+} PortIndex;
+
+
+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 */
+
+	EgScope* self = (EgScope*)calloc(1, sizeof(EgScope));
+	if(!self) {
+		return NULL;
+	}
+
+	/* check if the host provides the uri-map feature */
+	int i;
+	for (i=0; features[i]; ++i) {
+		if (!strcmp(features[i]->URI, LV2_URID__map)) {
+			self->map = (LV2_URID_Map*)features[i]->data;
+		}
+	}
+
+	if (!self->map) {
+		fprintf(stderr, "EgScope.lv2 error: Host does not support urid:map\n");
+		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;
+	}
+
+	/* initialize local variables */
+	self->ui_active = false;
+	self->send_settings_to_ui = false;
+	self->rate = rate;
+
+	/* default settings for UI */
+	self->ui_spp = 50;
+	self->ui_amp = 1.0;
+
+	/* initialize the Atom forge and map URIs*/
+	lv2_atom_forge_init(&self->forge, self->map);
+	map_sco_uris(self->map, &self->uris);
+
+	return (LV2_Handle)self;
+}
+
+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;
+	}
+}
+
+/** forge atom-vector of raw data
+ *
+ * @param forge the forge
+ * @param uris mapped URI identifiers
+ * @param channel the channel ID to transmit
+ * @param n_samples number of audio-samples to transmit
+ * @param data actual audio data
+ * */
+static void
+tx_rawaudio(LV2_Atom_Forge *forge, ScoLV2URIs *uris,
+		const int32_t channel, const size_t n_samples, void *data)
+{
+	LV2_Atom_Forge_Frame frame;
+	/* forge container object of type 'rawaudio' */
+	lv2_atom_forge_frame_time(forge, 0);
+	lv2_atom_forge_blank(forge, &frame, 1, uris->rawaudio);
+
+	/* add integer attribute 'channelid' */
+	lv2_atom_forge_property_head(forge, uris->channelid, 0);
+	lv2_atom_forge_int(forge, channel);
+
+	/* add vector of floats raw 'audiodata' */
+	lv2_atom_forge_property_head(forge, uris->audiodata, 0);
+	lv2_atom_forge_vector(forge, sizeof(float), uris->atom_Float, n_samples, data);
+
+	/* close off atom-object */
+	lv2_atom_forge_pop(forge, &frame);
+}
+
+/** Process a block of audio */
+static void
+run(LV2_Handle handle, uint32_t n_samples)
+{
+	EgScope* self = (EgScope*)handle;
+
+	/* Query the size of the Atom port -- a minimum size was requested in
+	 * the .ttl file  (rsz:minimumSize) we assure that the host supports this.
+	 */
+	const size_t size = (sizeof(float) * n_samples + 64) * self->n_channels;
+	const uint32_t capacity = self->notify->atom.size;
+
+	/* check if atom-port buffer is large enough to hold
+	 * all audio-samples and configuration settings */
+	if (capacity < size + 128) {
+		/* NB. This print is not real-time safe, ideally
+		 * this notification would also only be printed once.
+		 * This notification is only here for example purposes, a real
+		 * plugin would handle this case gracefully.
+		 */
+		fprintf(stderr, "EgScope.lv2 error: LV2 comm-buffersize is insufficient.\n");
+		return;
+	}
+
+	/* prepare forge buffer and initialize atom-sequence */
+	lv2_atom_forge_set_buffer(&self->forge, (uint8_t*)self->notify, capacity);
+	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 actual state and settings of the UI are kept
+	 * here and re-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_blank(&self->forge, &frame, 1, self->uris.ui_state);
+		/* forge container object of type 'ui_state' */
+		lv2_atom_forge_property_head(&self->forge, self->uris.ui_spp, 0); lv2_atom_forge_int(&self->forge, self->ui_spp);
+		lv2_atom_forge_property_head(&self->forge, self->uris.ui_amp, 0); lv2_atom_forge_float(&self->forge, self->ui_amp);
+		lv2_atom_forge_property_head(&self->forge, self->uris.samplerate, 0); lv2_atom_forge_float(&self->forge, self->rate);
+		lv2_atom_forge_pop(&self->forge, &frame);
+	}
+
+	/* Process incoming events from GUI */
+	if (self->control) {
+		LV2_Atom_Event* ev = lv2_atom_sequence_begin(&(self->control)->body);
+		/* for each message from UI... */
+		while(!lv2_atom_sequence_is_end(&(self->control)->body, (self->control)->atom.size, ev)) {
+			/* .. only look at atom-events.. */
+			if (ev->body.type == self->uris.atom_Blank) {
+				const LV2_Atom_Object* obj = (LV2_Atom_Object*)&ev->body;
+				/* interpret atom-objects: */
+				if (obj->body.otype == self->uris.ui_on) {
+					/* UI was activated */
+					self->ui_active = true;
+					self->send_settings_to_ui = true;
+				} else if (obj->body.otype == self->uris.ui_off) {
+					/* UI was closed */
+					self->ui_active = false;
+				} else if (obj->body.otype == self->uris.ui_state) {
+					/* UI sends current 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 = ((LV2_Atom_Int*)spp)->body;
+					if (amp) self->ui_amp = ((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 atom-sequence */
+	lv2_atom_forge_pop(&self->forge, &self->frame);
+}
+
+static void
+cleanup(LV2_Handle handle)
+{
+	free(handle);
+}
+
+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 | LV2_STATE_IS_PORTABLE);
+	store(handle, self->uris.ui_amp,
+			(void*) &self->ui_amp, sizeof(float),
+			self->uris.atom_Float,
+			LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+	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 * value = retrieve(handle, self->uris.ui_spp, &size, &type, &valflags);
+	if (value && size == sizeof(uint32_t) && type == self->uris.atom_Int) {
+		self->ui_spp = *((uint32_t*) value);
+		self->send_settings_to_ui = true;
+	}
+
+	value = retrieve(handle, self->uris.ui_amp, &size, &type, &valflags);
+	if (value && size == sizeof(float) && type == self->uris.atom_Float) {
+		self->ui_amp = *((float*) value);
+		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;
+}
+
+
+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;
+	}
+}
+
+/* vi:set ts=8 sts=8 sw=8: */
diff --git a/plugins/eg05-scope.lv2/examploscope.ttl.in b/plugins/eg05-scope.lv2/examploscope.ttl.in
new file mode 100644
index 0000000..0b76962
--- /dev/null
+++ b/plugins/eg05-scope.lv2/examploscope.ttl.in
@@ -0,0 +1,130 @@
+ at prefix atom:    <http://lv2plug.in/ns/ext/atom#> .
+ at prefix bufsz:   <http://lv2plug.in/ns/ext/buf-size#> .
+ at prefix doap:    <http://usefulinc.com/ns/doap#> .
+ at prefix foaf:    <http://xmlns.com/foaf/0.1/> .
+ at prefix lv2:     <http://lv2plug.in/ns/lv2core#> .
+ at prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
+ at prefix ui:      <http://lv2plug.in/ns/extensions/ui#> .
+ at prefix urid:    <http://lv2plug.in/ns/ext/urid#> .
+ at prefix rsz:     <http://lv2plug.in/ns/ext/resize-port#> .
+ at prefix state:   <http://lv2plug.in/ns/ext/state#> .
+ at prefix egscope: <http://lv2plug.in/plugins/eg-scope#> .
+
+<http://gareus.org/rgareus#me>
+	a foaf:Person ;
+	foaf:name "Robin Gareus" ;
+	foaf:mbox <mailto:robin at gareus.org> ;
+	foaf:homepage <http://gareus.org/> .
+
+<http://lv2plug.in/plugins/eg-scope>
+	a doap:Project ;
+	doap:maintainer <http://gareus.org/rgareus#me> ;
+	doap:name "Example Scope" .
+
+egscope:Mono
+	a lv2:Plugin, lv2:AnalyserPlugin ;
+	doap:name "Example Scope (Mono)" ;
+	lv2:project <http://lv2plug.in/plugins/eg-scope> ;
+	doap:license <http://usefulinc.com/doap/licenses/gpl> ;
+	lv2:requiredFeature urid:map ;
+	lv2:optionalFeature lv2:hardRTCapable ;
+	lv2:extensionData state:interface ;
+	ui:ui egscope:ui ;
+	lv2:port [
+		a atom:AtomPort ,
+			lv2:InputPort ;
+		atom:bufferType atom:Sequence ;
+		lv2:designation lv2:control ;
+		lv2:index 0 ;
+		lv2:symbol "control" ;
+		lv2:name "Control"
+	] , [
+		a atom:AtomPort ,
+			lv2:OutputPort ;
+		atom:bufferType atom:Sequence ;
+		lv2:designation lv2:control ;
+		lv2:index 1 ;
+		lv2:symbol "notify" ;
+		lv2:name "Notify" ;
+		# 8192 * sizeof(float) + LV2-Atoms
+		rsz:minimumSize 32832;
+	] , [
+		a lv2:AudioPort ,
+			lv2:InputPort ;
+		lv2:index 2 ;
+		lv2:symbol "in" ;
+		lv2:name "In"
+	] , [
+		a lv2:AudioPort ,
+			lv2:OutputPort ;
+		lv2:index 3 ;
+		lv2:symbol "out" ;
+		lv2:name "Out"
+	] .
+
+
+egscope:Stereo
+	a lv2:Plugin, lv2:AnalyserPlugin ;
+	doap:name "Example Scope (Stereo)" ;
+	lv2:project <http://lv2plug.in/plugins/eg-scope> ;
+	doap:license <http://usefulinc.com/doap/licenses/gpl> ;
+	lv2:requiredFeature urid:map ;
+	lv2:optionalFeature lv2:hardRTCapable ;
+	lv2:extensionData state:interface ;
+	ui:ui egscope:ui ;
+	lv2:port [
+		a atom:AtomPort ,
+			lv2:InputPort ;
+		atom:bufferType atom:Sequence ;
+		lv2:designation lv2:control ;
+		lv2:index 0 ;
+		lv2:symbol "control" ;
+		lv2:name "Control"
+	] , [
+		a atom:AtomPort ,
+			lv2:OutputPort ;
+		atom:bufferType atom:Sequence ;
+		lv2:designation lv2:control ;
+		lv2:index 1 ;
+		lv2:symbol "notify" ;
+		lv2:name "Notify" ;
+		rsz:minimumSize 65664;
+	] , [
+		a lv2:AudioPort ,
+			lv2:InputPort ;
+		lv2:index 2 ;
+		lv2:symbol "in0" ;
+		lv2:name "InL"
+	] , [
+		a lv2:AudioPort ,
+			lv2:OutputPort ;
+		lv2:index 3 ;
+		lv2:symbol "out0" ;
+		lv2:name "OutL"
+	] , [
+		a lv2:AudioPort ,
+			lv2:InputPort ;
+		lv2:index 4 ;
+		lv2:symbol "in1" ;
+		lv2:name "InR"
+	] , [
+		a lv2:AudioPort ,
+			lv2:OutputPort ;
+		lv2:index 5 ;
+		lv2:symbol "out1" ;
+		lv2:name "OutR"
+	] .
+
+
+egscope:ui
+	a ui:GtkUI ;
+	lv2:requiredFeature urid:map ;
+	ui:portNotification [
+		ui:plugin egscope:Mono ;
+		lv2:symbol "notify" ;
+		ui:notifyType atom:Blank
+	] , [
+		ui:plugin egscope:Stereo ;
+		lv2:symbol "notify" ;
+		ui:notifyType atom:Blank
+	] .
diff --git a/plugins/eg05-scope.lv2/examploscope_ui.c b/plugins/eg05-scope.lv2/examploscope_ui.c
new file mode 100644
index 0000000..9f8f415
--- /dev/null
+++ b/plugins/eg05-scope.lv2/examploscope_ui.c
@@ -0,0 +1,629 @@
+/* simple scope -- example pipe raw audio data to UI
+ *
+ * Copyright (C) 2013 Robin Gareus <robin at gareus.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <gtk/gtk.h>
+#include <cairo.h>
+
+#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
+#include "./uris.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)
+ */
+#define MAX_CAIRO_PATH (128)
+
+/**
+ * representation of the raw audio-data for display
+ * (min | max) values for a given 'index' position.
+ */
+typedef struct {
+	float data_min[DAWIDTH];
+	float data_max[DAWIDTH];
+
+	uint32_t idx;
+	uint32_t sub;
+	pthread_mutex_t lock;
+} ScoChan;
+
+typedef struct {
+	LV2_Atom_Forge forge;
+	LV2_URID_Map*  map;
+	ScoLV2URIs     uris;
+
+	LV2UI_Write_Function write;
+	LV2UI_Controller controller;
+
+	GtkWidget *hbox, *vbox;
+	GtkWidget *sep[2];
+	GtkWidget *darea;
+	GtkWidget *btn_pause;
+	GtkWidget *lbl_speed, *lbl_amp;
+	GtkWidget *spb_speed, *spb_amp;
+	GtkAdjustment *spb_speed_adj, *spb_amp_adj;
+
+	ScoChan  chn[2];
+	uint32_t stride;
+	uint32_t n_channels;
+	bool     paused;
+	float    rate;
+} EgScopeUI;
+
+
+/** send current settings to backend */
+static void 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 for forge */
+	uint8_t obj_buf[1024];
+	lv2_atom_forge_set_buffer(&ui->forge, obj_buf, 1024);
+
+	LV2_Atom_Forge_Frame frame;
+	/* prepare a new frame for an atom-sequence */
+	lv2_atom_forge_frame_time(&ui->forge, 0);
+	LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_blank(&ui->forge, &frame, 1, ui->uris.ui_state);
+	/* add a key + value pair  -- samples-per-pixel + integer*/
+	lv2_atom_forge_property_head(&ui->forge, ui->uris.ui_spp, 0); lv2_atom_forge_int(&ui->forge, ui->stride);
+	/* add a key + value pair  -- amplitude + float*/
+	lv2_atom_forge_property_head(&ui->forge, ui->uris.ui_amp, 0); lv2_atom_forge_float(&ui->forge, gain);
+	/* close-off forged data (pads message and writ frame-data to obj_buf */
+	lv2_atom_forge_pop(&ui->forge, &frame);
+	/* transmit the data 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 */
+static void ui_disable(LV2UI_Handle handle)
+{
+	EgScopeUI* ui = (EgScopeUI*)handle;
+	ui_state(handle);
+
+	uint8_t obj_buf[64];
+	lv2_atom_forge_set_buffer(&ui->forge, obj_buf, 64);
+	LV2_Atom_Forge_Frame frame;
+	lv2_atom_forge_frame_time(&ui->forge, 0);
+	LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_blank(&ui->forge, &frame, 1, 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:
+ * request state and enable data-transmission */
+static void ui_enable(LV2UI_Handle handle)
+{
+	EgScopeUI* ui = (EgScopeUI*)handle;
+	uint8_t obj_buf[64];
+	lv2_atom_forge_set_buffer(&ui->forge, obj_buf, 64);
+	LV2_Atom_Forge_Frame frame;
+	lv2_atom_forge_frame_time(&ui->forge, 0);
+	LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_blank(&ui->forge, &frame, 1, 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 */
+gboolean cfg_changed (GtkWidget *widget, gpointer data)
+{
+	ui_state(data);
+	return TRUE;
+}
+
+
+/* gdk drawing area draw callback
+ * -- this runs in gtk's main thread
+ * libcairo is used for drawing the raw data
+ */
+gboolean expose_event_callback (GtkWidget *widget, GdkEventExpose *ev, gpointer data)
+{
+	/* see also note in process_channel()
+	 *
+	 * ideally one would read from ring-buffer or directly blit a
+	 * cairo-surface prepared in process_channel() instead of [b]locking here.
+	 * It is only the UI that blocks for a short time, so this is not a big deal.
+	 * */
+
+	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, 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 >= 0);
+	assert(start < DAWIDTH);
+	assert(end >= 0);
+	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 -.5 .. +.5, hence (DAHEIGHT / 2.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 * .5f - .5f;
+		const float chn_y_scale = DAHEIGHT * .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, 1.0, .0, 1.0);
+
+		pthread_mutex_lock(&chn->lock);
+
+		/* 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 it is not ideal for either.
+		 */
+		if (start == chn->idx) {
+			cairo_move_to(cr, start - .5, CYPOS(0));
+		} else {
+			cairo_move_to(cr, start - .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
+			 */
+			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
+		 * the display is reasonably 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);
+		}
+
+		pthread_mutex_unlock(&chn->lock);
+
+		/* 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 from and prepare for later drawing
+ *
+ * NB. This is a very simple & stupid example.
+ * any serious scope will not display samples as is.
+ *
+ * This example is a wave-form display -- not an oscilloscope.
+ *
+ * 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.
+ *
+ * please 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
+ */
+static int process_channel(EgScopeUI *ui, ScoChan *chn,
+		const size_t n_elem, float const *data,
+		uint32_t *idx_start, uint32_t *idx_end)
+{
+
+	/* see note in expose_event_callback()
+	 *
+	 * Ideally this callback should write data into a lock-free
+	 * ringbuffer instead of locking the data-structure.
+	 *
+	 * Both the display callback as well as this
+	 * port_event() callback are host-provided threads.
+	 * Locking the data is a valid solution.
+	 */
+	pthread_mutex_lock(&chn->lock);
+
+	int overflow = 0;
+	*idx_start = chn->idx;
+	for (int 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;
+	pthread_mutex_unlock(&chn->lock);
+	return overflow;
+}
+
+
+/** this callback runs in the "communication" thread of the LV2-host
+ * usually a g_timeout() at ~25fps
+ * -- invoked via port_event(); please see notes there.
+ */
+static void update_scope(EgScopeUI* ui, const int channel, const size_t n_elem, float const *data)
+{
+	/* never trust input data which could lead to application failure */
+	if (channel > ui->n_channels || channel < 0) {
+		return;
+	}
+
+	/* update state in sync with 1st channel */
+	if (channel == 0) {
+		ui->stride = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_speed));
+		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, idx_end; // display pixel start/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 (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*)malloc(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);
+	pthread_mutex_init(&ui->chn[0].lock, NULL);
+	pthread_mutex_init(&ui->chn[1].lock, NULL);
+
+	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(expose_event_callback), ui);
+	g_signal_connect(G_OBJECT(ui->spb_amp), "value-changed", G_CALLBACK(cfg_changed), ui);
+	g_signal_connect(G_OBJECT(ui->spb_speed), "value-changed", G_CALLBACK(cfg_changed), ui);
+
+	*widget = ui->hbox;
+
+	/* send message to DSP backend:
+	 * enable message transmission & request state
+	 */
+	ui_enable(ui);
+
+	return ui;
+}
+
+static void
+cleanup(LV2UI_Handle handle)
+{
+	EgScopeUI* ui = (EgScopeUI*)handle;
+	/* send message to DSP backend:
+	 * save state & disable message transmission
+	 */
+	ui_disable(ui);
+	pthread_mutex_destroy(&ui->chn[0].lock);
+	pthread_mutex_destroy(&ui->chn[1].lock);
+	gtk_widget_destroy(ui->darea);
+	free(ui);
+}
+
+/** receive data from the DSP-backend.
+ *
+ * this callback runs in the "communication" thread of the LV2-host
+ * jalv and ardour do this via a g_timeout() function at ~25fps
+ *
+ * the atom-events from the DSP backend are written into a ringbuffer
+ * in the host (in the DSP|jack realtime thread) the host then
+ * empties this ringbuffer by sending port_event()s to the UI at some
+ * random time later.  When CPU and DSP load are large the host-buffer
+ * may overflow and some events may get lost.
+ *
+ * This thread does is not [usually] the 'drawing' thread (it does not
+ * have X11 or gl context).
+ */
+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;
+	LV2_Atom* atom = (LV2_Atom*)buffer;
+
+	/* check type of data received
+	 *  format == 0: [float] control-port event
+	 *  format > 0: message
+	 *  Every event message is sent as separate port-event
+	 */
+	if (format == ui->uris.atom_eventTransfer
+			&& atom->type == ui->uris.atom_Blank
+	   )
+	{
+		/* cast the buffer to Atom Object */
+		LV2_Atom_Object* obj = (LV2_Atom_Object*)atom;
+		LV2_Atom *a0 = NULL;
+		LV2_Atom *a1 = NULL;
+		LV2_Atom *a2 = NULL;
+		if (
+				/* handle raw-audio data objects */
+				obj->body.otype == ui->uris.rawaudio
+				/* retrieve properties from object and
+				 * check that there the [here] two required properties are set.. */
+				&& 2 == lv2_atom_object_get(obj, ui->uris.channelid, &a0, ui->uris.audiodata, &a1, NULL)
+				/* ..and non-null.. */
+				&& a0
+				&& a1
+				/* ..and match the expected type */
+				&& a0->type == ui->uris.atom_Int
+				&& a1->type == ui->uris.atom_Vector
+		   )
+		{
+			/* single integer value can be directly dereferenced */
+			const int32_t chn = ((LV2_Atom_Int*)a0)->body;
+
+			/* dereference and typecast vector pointer */
+			LV2_Atom_Vector* vof = (LV2_Atom_Vector*)LV2_ATOM_BODY(a1);
+			/* check if atom is indeed a vector of the expected type*/
+			if (vof->atom.type == ui->uris.atom_Float) {
+				/* get number of elements in vector
+				 * = (raw 8bit data-length - header-length) / sizeof(expected data type:float) */
+				const size_t n_elem = (a1->size - sizeof(LV2_Atom_Vector_Body)) / vof->atom.size;
+				/* typecast, dereference pointer to vector */
+				const float *data = (float*) LV2_ATOM_BODY(&vof->atom);
+				/* call function that handles the actual data */
+				update_scope(ui, chn, n_elem, data);
+			}
+		}
+		else if (
+				/* handle 'state/settings' data object */
+				obj->body.otype == ui->uris.ui_state
+				/* retrieve properties from object and
+				 * check that there the [here] three required properties are set.. */
+				&& 3 == lv2_atom_object_get(obj,
+					ui->uris.ui_spp, &a0,
+					ui->uris.ui_amp, &a1,
+					ui->uris.samplerate, &a2, NULL)
+				/* ..and non-null.. */
+				&& a0 && a1 && a2
+				/* ..and match the expected type */
+				&& a0->type == ui->uris.atom_Int
+				&& a1->type == ui->uris.atom_Float
+				&& a2->type == ui->uris.atom_Float
+			)
+		{
+			/* dereference the data pointers */
+			int spp = ((LV2_Atom_Int*)a0)->body;
+			float amp = ((LV2_Atom_Float*)a1)->body;
+			ui->rate = ((LV2_Atom_Float*)a2)->body;
+			/* and apply the values */
+			gtk_spin_button_set_value(GTK_SPIN_BUTTON(ui->spb_speed), spp);
+			gtk_spin_button_set_value(GTK_SPIN_BUTTON(ui->spb_amp), amp);
+		}
+	}
+}
+
+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;
+	}
+}
+
+/* vi:set ts=8 sts=8 sw=8: */
diff --git a/plugins/eg05-scope.lv2/manifest.ttl.in b/plugins/eg05-scope.lv2/manifest.ttl.in
new file mode 100644
index 0000000..028a673
--- /dev/null
+++ b/plugins/eg05-scope.lv2/manifest.ttl.in
@@ -0,0 +1,18 @@
+ at prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
+ at prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+ at prefix ui:   <http://lv2plug.in/ns/extensions/ui#> .
+
+<http://lv2plug.in/plugins/eg-scope#Mono>
+	a lv2:Plugin ;
+	lv2:binary <examploscope at LIB_EXT@>  ;
+	rdfs:seeAlso <examploscope.ttl> .
+
+<http://lv2plug.in/plugins/eg-scope#Stereo>
+	a lv2:Plugin ;
+	lv2:binary <examploscope at LIB_EXT@>  ;
+	rdfs:seeAlso <examploscope.ttl> .
+
+<http://lv2plug.in/plugins/eg-scope#ui>
+	a ui:GtkUI ;
+	ui:binary <examploscope_ui at LIB_EXT@> ;
+	rdfs:seeAlso <examploscope.ttl> .
diff --git a/plugins/eg05-scope.lv2/uris.h b/plugins/eg05-scope.lv2/uris.h
new file mode 100644
index 0000000..17602ac
--- /dev/null
+++ b/plugins/eg05-scope.lv2/uris.h
@@ -0,0 +1,64 @@
+/* simple scope -- example pipe raw audio data to UI
+ *
+ * Copyright (C) 2013 Robin Gareus <robin at gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SCO_URIS_H
+#define SCO_URIS_H
+
+#include "lv2/lv2plug.in/ns/ext/atom/atom.h"
+#include "lv2/lv2plug.in/ns/ext/atom/forge.h"
+#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
+
+#define SCO_URI "http://lv2plug.in/plugins/eg-scope"
+
+typedef struct {
+	LV2_URID atom_Blank;
+	LV2_URID atom_Vector;
+	LV2_URID atom_Float;
+	LV2_URID atom_Int;
+	LV2_URID atom_eventTransfer;
+	LV2_URID rawaudio;
+	LV2_URID channelid;
+	LV2_URID audiodata;
+
+	LV2_URID samplerate;
+	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_Blank         = map->map(map->handle, LV2_ATOM__Blank);
+	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->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->samplerate         = map->map(map->handle, SCO_URI "#samplerate");
+	uris->ui_on              = map->map(map->handle, SCO_URI "#ui_on");
+	uris->ui_off             = map->map(map->handle, SCO_URI "#ui_off");
+	uris->ui_state           = map->map(map->handle, SCO_URI "#ui_state");
+	uris->ui_spp             = map->map(map->handle, SCO_URI "#ui_spp");
+	uris->ui_amp             = map->map(map->handle, SCO_URI "#ui_amp");
+}
+
+#endif
diff --git a/plugins/eg05-scope.lv2/wscript b/plugins/eg05-scope.lv2/wscript
new file mode 100644
index 0000000..807d15d
--- /dev/null
+++ b/plugins/eg05-scope.lv2/wscript
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+from waflib.extras import autowaf as autowaf
+import re
+
+# Variables for 'waf dist'
+APPNAME = 'eg-scope.lv2'
+VERSION = '1.0.0'
+
+# Mandatory variables
+top = '.'
+out = 'build'
+
+def options(opt):
+    opt.load('compiler_c')
+    autowaf.set_options(opt)
+
+def configure(conf):
+    conf.load('compiler_c')
+    autowaf.configure(conf)
+    autowaf.set_c99_mode(conf)
+    autowaf.display_header('Scope Configuration')
+
+    if not autowaf.is_child():
+        autowaf.check_pkg(conf, 'lv2', atleast_version='1.2.1', uselib_store='LV2')
+
+    autowaf.check_pkg(conf, 'cairo', uselib_store='CAIRO',
+                      atleast_version='1.8.10', mandatory=True)
+    autowaf.check_pkg(conf, 'gtk+-2.0', uselib_store='GTK2',
+                      atleast_version='2.18.0', mandatory=False)
+
+    autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR)
+    print('')
+
+def build(bld):
+    bundle = 'eg-scope.lv2'
+
+    # Make a pattern for shared objects without the 'lib' prefix
+    module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN)
+    module_ext = module_pat[module_pat.rfind('.'):]
+
+    # Build manifest.ttl by substitution (for portable lib extension)
+    for i in ['manifest.ttl', 'examploscope.ttl']:
+        bld(features     = 'subst',
+            source       = i + '.in',
+            target       = '%s/%s' % (bundle, i),
+            install_path = '${LV2DIR}/%s' % bundle,
+            LIB_EXT      = module_ext)
+
+    # Use LV2 headers from parent directory if building as a sub-project
+    includes = ['.']
+    if autowaf.is_child:
+        includes += ['../..']
+
+    # Build plugin library
+    obj = bld(features     = 'c cshlib',
+              source       = 'examploscope.c',
+              name         = 'examploscope',
+              target       = '%s/examploscope' % bundle,
+              install_path = '${LV2DIR}/%s' % bundle,
+              use          = 'LV2',
+              includes     = includes)
+    obj.env.cshlib_PATTERN = module_pat
+
+    # Build UI library
+    if bld.is_defined('HAVE_GTK2'):
+        obj = bld(features     = 'c cshlib',
+                  source       = 'examploscope_ui.c',
+                  name         = 'examploscope_ui',
+                  target       = '%s/examploscope_ui' % bundle,
+                  install_path = '${LV2DIR}/%s' % bundle,
+                  use          = 'GTK2 CAIRO LV2',
+                  includes     = includes)
+    obj.env.cshlib_PATTERN = module_pat
-- 
1.7.10.4




More information about the Devel mailing list