[LV2] [PATCH] add eg05-scope example plugin
Robin Gareus
robin at gareus.org
Mon Nov 11 14:38:11 PST 2013
Signed-off-by: Robin Gareus <robin at gareus.org>
---
plugins/eg05-scope.lv2/README.txt | 31 ++
plugins/eg05-scope.lv2/examploscope.c | 342 ++++++++++++++++
plugins/eg05-scope.lv2/examploscope.ttl.in | 130 +++++++
plugins/eg05-scope.lv2/examploscope_ui.c | 581 ++++++++++++++++++++++++++++
plugins/eg05-scope.lv2/manifest.ttl.in | 18 +
plugins/eg05-scope.lv2/uris.h | 64 +++
plugins/eg05-scope.lv2/wscript | 73 ++++
7 files changed, 1239 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..76393ee
--- /dev/null
+++ b/plugins/eg05-scope.lv2/README.txt
@@ -0,0 +1,31 @@
+== 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 selective exposure
+and includes explicit source code annotations for 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's neither accurate nor stable
+* 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.
diff --git a/plugins/eg05-scope.lv2/examploscope.c b/plugins/eg05-scope.lv2/examploscope.c
new file mode 100644
index 0000000..265ec0f
--- /dev/null
+++ b/plugins/eg05-scope.lv2/examploscope.c
@@ -0,0 +1,342 @@
+/* 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/>.
+ */
+
+#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"
+
+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;
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ self->ui_active = false;
+ self->send_settings_to_ui = false;
+ self->ui_spp = 50;
+ self->ui_amp = 1.0;
+ self->rate = rate;
+
+ 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 */
+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);
+}
+
+static void
+run(LV2_Handle handle, uint32_t n_samples)
+{
+ EgScope* self = (EgScope*)handle;
+ 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) {
+ 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 */
+ 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, &, 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 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..a06be73
--- /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 ;
+ 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 ;
+ 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..7962945
--- /dev/null
+++ b/plugins/eg05-scope.lv2/examploscope_ui.c
@@ -0,0 +1,581 @@
+/* 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/>.
+ */
+
+#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)
+
+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));
+
+ uint8_t obj_buf[1024];
+ lv2_atom_forge_set_buffer(&ui->forge, obj_buf, 1024);
+ 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_state);
+ lv2_atom_forge_property_head(&ui->forge, ui->uris.ui_spp, 0); lv2_atom_forge_int(&ui->forge, ui->stride);
+ lv2_atom_forge_property_head(&ui->forge, ui->uris.ui_amp, 0); lv2_atom_forge_float(&ui->forge, gain);
+ lv2_atom_forge_pop(&ui->forge, &frame);
+ ui->write(ui->controller, 0, lv2_atom_total_size(msg), ui->uris.atom_eventTransfer, msg);
+}
+
+/** notfiy 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 */
+gboolean expose_event_callback (GtkWidget *widget, GdkEventExpose *ev, gpointer data)
+{
+ /* TODO: read from ringbuffer or blit cairo surface instead of [b]locking here */
+ EgScopeUI* ui = (EgScopeUI*) data;
+ const float gain = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_amp));
+
+ 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, thus -VAL
+ *
+ * == ( DAHEIGHT * (CHN)
+ * + (DAHEIGHT / 2) - 0.5
+ * - (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);
+ cairo_rectangle (cr, 0, DAHEIGHT * c, DAWIDTH, DAHEIGHT);
+ cairo_clip(cr);
+ cairo_set_source_rgba (cr, .0, 1.0, .0, 1.0);
+
+ pthread_mutex_lock(&chn->lock);
+
+ 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;
+ }
+
+ 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);
+ }
+
+ /* current position vertical-line */
+ 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);
+ }
+ cairo_restore(cr);
+ pthread_mutex_unlock(&chn->lock);
+
+ /* 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.
+ * Signals above maybe 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
+ * and http://lac.linuxaudio.org/2013/papers/36.pdf
+ *
+ * This simple algorithm serves as example how *not* to do it.
+ */
+static int process_channel(EgScopeUI *ui, ScoChan *chn,
+ const size_t n_elem, float const *data,
+ uint32_t *idx_start, uint32_t *idx_end)
+{
+
+ /* TODO: write into ringbuffer instead of locking data.
+ * possibly draw directly into a cairo-surface (that can later be annotated)
+ */
+ 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
+ * -- 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)
+{
+ /* this callback runs in the "communication" thread of the LV2-host
+ * usually a g_timeout() at ~25fps
+ */
+ 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