diff options
| author | David Robillard <d@drobilla.net> | 2013-12-15 21:18:33 +0000 | 
|---|---|---|
| committer | David Robillard <d@drobilla.net> | 2013-12-15 21:18:33 +0000 | 
| commit | 8f2c1ead95ed11f42f0c9801c397ec2c9df45707 (patch) | |
| tree | b2d8b3c78a27b986691f5e505c0717381ff0b888 /plugins/eg05-scope.lv2 | |
| parent | 96ff3ee63a97c4f8a87813685340a0270c64c436 (diff) | |
| download | lv2-8f2c1ead95ed11f42f0c9801c397ec2c9df45707.tar.xz | |
Add example scope plugin from Robin Gareus.
Diffstat (limited to 'plugins/eg05-scope.lv2')
| -rw-r--r-- | plugins/eg05-scope.lv2/README.txt | 30 | ||||
| -rw-r--r-- | plugins/eg05-scope.lv2/examploscope.c | 404 | ||||
| -rw-r--r-- | plugins/eg05-scope.lv2/examploscope.ttl.in | 130 | ||||
| -rw-r--r-- | plugins/eg05-scope.lv2/examploscope_ui.c | 653 | ||||
| -rw-r--r-- | plugins/eg05-scope.lv2/manifest.ttl.in | 18 | ||||
| -rw-r--r-- | plugins/eg05-scope.lv2/uris.h | 73 | ||||
| -rw-r--r-- | plugins/eg05-scope.lv2/wscript | 73 | 
7 files changed, 1381 insertions, 0 deletions
| diff --git a/plugins/eg05-scope.lv2/README.txt b/plugins/eg05-scope.lv2/README.txt new file mode 100644 index 0000000..ec3578b --- /dev/null +++ b/plugins/eg05-scope.lv2/README.txt @@ -0,0 +1,30 @@ +== 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 + +This plugin intends to outline the basics for building visualization plugins +that rely on Atom communication.  The UI looks likean 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 diff --git a/plugins/eg05-scope.lv2/examploscope.c b/plugins/eg05-scope.lv2/examploscope.c new file mode 100644 index 0000000..13281ab --- /dev/null +++ b/plugins/eg05-scope.lv2/examploscope.c @@ -0,0 +1,404 @@ +/* +  Copyright 2013 Robin Gareus <robin@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 <stdint.h> + +#include "lv2/lv2plug.in/ns/ext/log/log.h" +#include "lv2/lv2plug.in/ns/ext/log/logger.h" +#include "lv2/lv2plug.in/ns/ext/state/state.h" +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#include "./uris.h" + +/** Private plugin instance structure. */ +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_Log*   log; +	LV2_Log_Logger logger; + +	// Instantiation settings +	uint32_t n_channels; +	double   rate; + +	/* The state of the UI is stored here, so that the GUI can be displayed and +	   closed without losing the current settings.  It is communicated to the +	   UI using atom messages. +	*/ +	bool     ui_active; +	bool     send_settings_to_ui; +	float    ui_amp; +	uint32_t ui_spp; +} EgScope; + +/** Port indices. */ +typedef enum { +	SCO_CONTROL = 0, +	SCO_NOTIFY  = 1, +	SCO_INPUT0  = 2, +	SCO_OUTPUT0 = 3, +	SCO_INPUT1  = 4, +	SCO_OUTPUT1 = 5, +} PortIndex; + +/** Create plugin instance. */ +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 +	for (int i = 0; features[i]; ++i) { +		if (!strcmp(features[i]->URI, LV2_URID__map)) { +			self->map = (LV2_URID_Map*)features[i]->data; +		} else if (!strcmp(features[i]->URI, LV2_LOG__log)) { +			self->log = (LV2_Log_Log*)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; +	} + +	// 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); +	lv2_log_logger_init(&self->logger, self->map, self->log); + +	return (LV2_Handle)self; +} + +/** Connect a port to a buffer. */ +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 vector of raw data. + +   @param forge Forge to use. +   @param uris Mapped URI identifiers. +   @param channel 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; + +	/* 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_blank(&self->forge, &frame, 1, self->uris.ui_State); + +		// Add UI state as properties +		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.param_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 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 (ev->body.type == self->uris.atom_Blank) { +				const LV2_Atom_Object* obj = (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, &, +					                    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 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 state values.  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. */ + +	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              = *((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              = *((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; +} + +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; +	} +} 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 @@ +@prefix atom:    <http://lv2plug.in/ns/ext/atom#> . +@prefix bufsz:   <http://lv2plug.in/ns/ext/buf-size#> . +@prefix doap:    <http://usefulinc.com/ns/doap#> . +@prefix foaf:    <http://xmlns.com/foaf/0.1/> . +@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#> . +@prefix urid:    <http://lv2plug.in/ns/ext/urid#> . +@prefix rsz:     <http://lv2plug.in/ns/ext/resize-port#> . +@prefix state:   <http://lv2plug.in/ns/ext/state#> . +@prefix egscope: <http://lv2plug.in/plugins/eg-scope#> . + +<http://gareus.org/rgareus#me> +	a foaf:Person ; +	foaf:name "Robin Gareus" ; +	foaf:mbox <mailto:robin@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..ffbe573 --- /dev/null +++ b/plugins/eg05-scope.lv2/examploscope_ui.c @@ -0,0 +1,653 @@ +/* +  Copyright 2013 Robin Gareus <robin@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 <cairo.h> +#include <gtk/gtk.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; +} 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; +} EgScopeUI; + + +/** Send current UI settings to backend. */ +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)); + +	// Event body is a ui_state object +	LV2_Atom_Forge_Frame frame; +	LV2_Atom*            msg = (LV2_Atom*)lv2_atom_forge_blank( +		&ui->forge, &frame, 1, ui->uris.ui_State); + +	// msg[samples-per-pixel] = integer +	lv2_atom_forge_property_head(&ui->forge, ui->uris.ui_spp, 0); +	lv2_atom_forge_int(&ui->forge, ui->stride); +	// msg[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 +	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. */ +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_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. + +   The plugin should send state and enable data transmission. +*/ +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_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. */ +static gboolean +on_cfg_changed(GtkWidget* widget, gpointer data) +{ +	send_ui_state(data); +	return TRUE; +} + +/** +   Gdk drawing area draw callback. + +   Called in Gtk's main thread and uses Cairo to draw the data. +*/ +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 */ +			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 +*/ +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. +*/ +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*)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); + +	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, LV2_Atom_Object* obj) +{ +	LV2_Atom* chan_val = NULL; +	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 = ((LV2_Atom_Int*)chan_val)->body; +	LV2_Atom_Vector* vec = (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)) +	                       / sizeof(float)); + +	// Float elements immediately follow the vector body header +	const float* data = (float*)(&vec->body + 1); + +	// Update display +	update_scope(ui, chn, n_elem, data); +	return 0; +} + +static int +recv_ui_state(EgScopeUI* ui, LV2_Atom_Object* obj) +{ +	LV2_Atom* spp_val  = NULL; +	LV2_Atom* amp_val  = NULL; +	LV2_Atom* rate_val = NULL; +	const int n_props  = lv2_atom_object_get( +		obj, +		ui->uris.ui_spp, &spp_val, +		ui->uris.ui_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  = ((LV2_Atom_Int*)spp_val)->body; +	const float   amp  = ((LV2_Atom_Float*)amp_val)->body; +	const float   rate = ((LV2_Atom_Float*)rate_val)->body; + +	// Update UI +	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->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. +*/ +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: Control port event (float) +	 *  - format > 0:  Message (atom) +	 */ +	if (format == ui->uris.atom_eventTransfer && +	    atom->type == ui->uris.atom_Blank) { +		LV2_Atom_Object* obj = (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; +	} +} 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 @@ +@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-scope#Mono> +	a lv2:Plugin ; +	lv2:binary <examploscope@LIB_EXT@>  ; +	rdfs:seeAlso <examploscope.ttl> . + +<http://lv2plug.in/plugins/eg-scope#Stereo> +	a lv2:Plugin ; +	lv2:binary <examploscope@LIB_EXT@>  ; +	rdfs:seeAlso <examploscope.ttl> . + +<http://lv2plug.in/plugins/eg-scope#ui> +	a ui:GtkUI ; +	ui:binary <examploscope_ui@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..bd57551 --- /dev/null +++ b/plugins/eg05-scope.lv2/uris.h @@ -0,0 +1,73 @@ +/* +  Copyright 2013 Robin Gareus <robin@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. +*/ + +#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/parameters/parameters.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" + +#define SCO_URI "http://lv2plug.in/plugins/eg-scope" + +typedef struct { +	// URIs defined in LV2 specifications +	LV2_URID atom_Blank; +	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_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->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 */ 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 |