aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2012-02-16 23:16:37 +0000
committerDavid Robillard <d@drobilla.net>2012-02-16 23:16:37 +0000
commit495f83c7f13349930df3526789d09c75c1312bd4 (patch)
tree5187d1b195dfd9e23caaafad30ab272882468720
parent75d524c764a6f91aa9662040a2c218e8d2802438 (diff)
downloadlv2-495f83c7f13349930df3526789d09c75c1312bd4.tar.xz
Implement real-time safe sample loading.
-rw-r--r--lv2/lv2plug.in/ns/extensions/ui/ui.ttl23
-rw-r--r--plugins/eg-sampler.lv2/sampler.c312
-rw-r--r--plugins/eg-sampler.lv2/sampler.ttl14
-rw-r--r--plugins/eg-sampler.lv2/uris.h26
-rw-r--r--plugins/eg-sampler.lv2/wscript7
-rw-r--r--plugins/eg-sampler.lv2/zix/ring.c231
-rw-r--r--plugins/eg-sampler.lv2/zix/ring.h136
7 files changed, 608 insertions, 141 deletions
diff --git a/lv2/lv2plug.in/ns/extensions/ui/ui.ttl b/lv2/lv2plug.in/ns/extensions/ui/ui.ttl
index 9eb0933..010580f 100644
--- a/lv2/lv2plug.in/ns/extensions/ui/ui.ttl
+++ b/lv2/lv2plug.in/ns/extensions/ui/ui.ttl
@@ -204,7 +204,17 @@ A PortNotification MUST have exactly one ui:plugin which is a lv2:Plugin.
A PortNotification MUST have exactly one ui:portIndex which is an xsd:decimal.
"""
] ;
- rdfs:comment "Port Notification" .
+ lv2:documentation """
+<p>A port notification. This describes which ports the host must send
+notifications to the UI about. The port can be specific by index, using the
+ui:portIndex property, or symbol, using the lv2:symbol property. Since port
+indices are not guaranteed to be stable between different revisions (or even
+instantiations) of a plugin, symbol is recommended, and index may only be used
+by UIs shipped in the same bundle as the plugin.</p>
+
+<p>A ui:PortNotification MUST have either a ui:portIndex or a lv2:symbol to
+indicate which port it refers to.</p>
+""" .
ui:portNotification
a rdf:Property ,
@@ -233,3 +243,14 @@ ui:portIndex
rdfs:comment """
The index of the port a portNotification applies to.
""" .
+
+ui:notifyType
+ a rdf:Property ;
+ rdfs:domain ui:PortNotification ;
+ lv2:comment """
+<p>Indicates a particular type that the UI should be notified of. In the case
+of ports where several types of data can be present (e.g. event ports), this
+can be used to indicate that only a particular type of data should cause
+notification. This is useful where port traffic is very dense, but only a
+certain small number of events are actually of interest to the UI.</p>
+""" .
diff --git a/plugins/eg-sampler.lv2/sampler.c b/plugins/eg-sampler.lv2/sampler.c
index 960a391..0842c4a 100644
--- a/plugins/eg-sampler.lv2/sampler.c
+++ b/plugins/eg-sampler.lv2/sampler.c
@@ -47,9 +47,11 @@
#include "zix/sem.h"
#include "zix/thread.h"
+#include "zix/ring.h"
#include "./uris.h"
+#define RING_SIZE 4096
#define STRING_BUF 8192
enum {
@@ -61,24 +63,24 @@ enum {
static const char* default_sample_file = "monosample.wav";
typedef struct {
- char filepath[STRING_BUF];
SF_INFO info;
float* data;
-} SampleFile;
+ char* path;
+} Sample;
typedef struct {
/* Features */
LV2_URID_Map* map;
- /* Worker thread */
+ /* Worker thread, communication, and sync */
ZixThread worker_thread;
ZixSem signal;
+ ZixRing* to_worker;
+ ZixRing* from_worker;
bool exit;
/* Sample */
- SampleFile* samp;
- SampleFile* pending_samp;
- int pending_sample_ready;
+ Sample* sample;
/* Ports */
float* output_port;
@@ -91,56 +93,141 @@ typedef struct {
/* Playback state */
sf_count_t frame;
bool play;
-
} Sampler;
-static void
-handle_load_sample(Sampler* plugin)
+/** An atom-like message used internally to apply/free samples.
+ *
+ * This is only used internally via ringbuffers, since it is not POD and
+ * therefore not strictly an Atom.
+ */
+typedef struct
+{
+ LV2_Atom atom;
+ Sample* sample;
+} SampleMessage;
+
+static Sample*
+load_sample(Sampler* plugin, const char* path, uint32_t path_len)
{
- plugin->pending_sample_ready = 0;
-
- printf("Loading sample %s\n", plugin->pending_samp->filepath);
- SF_INFO* const info = &plugin->pending_samp->info;
- SNDFILE* const sample = sf_open(plugin->pending_samp->filepath,
- SFM_READ,
- info);
-
- if (!sample
- || !info->frames
- || (info->channels != 1)) {
- fprintf(stderr, "failed to open sample '%s'.\n",
- plugin->pending_samp->filepath);
- return;
+ printf("Loading sample %s\n", path);
+ Sample* const sample = (Sample*)malloc(sizeof(Sample));
+ SF_INFO* const info = &sample->info;
+ SNDFILE* const sndfile = sf_open(path, SFM_READ, info);
+
+ if (!sndfile || !info->frames || (info->channels != 1)) {
+ fprintf(stderr, "failed to open sample '%s'.\n", path);
+ free(sample);
+ return NULL;
}
/* Read data */
float* const data = malloc(sizeof(float) * info->frames);
- plugin->pending_samp->data = data;
-
if (!data) {
fprintf(stderr, "failed to allocate memory for sample.\n");
- return;
+ return NULL;
}
+ sf_seek(sndfile, 0ul, SEEK_SET);
+ sf_read_float(sndfile, data, info->frames);
+ sf_close(sndfile);
- sf_seek(sample, 0ul, SEEK_SET);
- sf_read_float(sample, data, info->frames);
- sf_close(sample);
+ /* Fill sample struct and return it. */
+ sample->data = data;
+ sample->path = (char*)malloc(path_len + 1);
+ memcpy(sample->path, path, path_len + 1);
- /* Queue the sample for installation on next run() */
- plugin->pending_sample_ready = 1;
+ return sample;
+}
+
+static bool
+is_object_type(Sampler* plugin, LV2_URID type)
+{
+ return type == plugin->uris.atom_Resource
+ || type == plugin->uris.atom_Blank;
}
+static bool
+handle_set_message(Sampler* plugin,
+ const LV2_Atom_Object* obj)
+{
+ if (obj->type != plugin->uris.msg_Set) {
+ fprintf(stderr, "Ignoring unknown message type %d\n", obj->type);
+ return false;
+ }
+
+ /* Message should look like this:
+ * [
+ * a msg:SetMessage ;
+ * msg:body [
+ * eg:filename "/some/value.wav" ;
+ * ] ;
+ * ]
+ */
+
+ /* Get body of message. */
+ const LV2_Atom_Object* body = NULL;
+ lv2_object_getv(obj, plugin->uris.msg_body, &body, 0);
+ if (!body) {
+ fprintf(stderr, "Malformed set message has no body.\n");
+ return false;
+ }
+ if (!is_object_type(plugin, body->atom.type)) {
+ fprintf(stderr, "Malformed set message has non-object body.\n");
+ return false;
+ }
+
+ /* Get filename from body. */
+ const LV2_Atom* filename = NULL;
+ lv2_object_getv(body, plugin->uris.eg_filename, &filename, 0);
+ if (!filename) {
+ fprintf(stderr, "Ignored set message with no filename.\n");
+ return false;
+ }
+
+ /* Load sample. */
+ const char* path = (const char*)LV2_ATOM_BODY(filename);
+ Sample* sample = load_sample(plugin, path, filename->size - 1);
+
+ if (sample) {
+ /* Loaded sample, send it to run() to be applied. */
+ const SampleMessage msg = {
+ { plugin->uris.eg_applySample, sizeof(sample) },
+ sample
+ };
+ zix_ring_write(plugin->from_worker,
+ &msg,
+ lv2_atom_pad_size(sizeof(msg)));
+ }
+
+ return true;
+}
+
void*
worker_thread_main(void* arg)
{
Sampler* plugin = (Sampler*)arg;
- while (!plugin->exit) {
- /* Wait for run() to signal that we need to load a sample */
- zix_sem_wait(&plugin->signal);
-
- /* Then load it */
- handle_load_sample(plugin);
+ while (!zix_sem_wait(&plugin->signal) && !plugin->exit) {
+ /* Peek message header to see how much we need to read. */
+ LV2_Atom head;
+ zix_ring_peek(plugin->to_worker, &head, sizeof(head));
+
+ /* Read message. */
+ const uint32_t size = lv2_atom_pad_size(sizeof(LV2_Atom) + head.size);
+ uint8_t buf[size];
+ LV2_Atom* obj = (LV2_Atom*)buf;
+ zix_ring_read(plugin->to_worker, buf, size);
+
+ if (obj->type == plugin->uris.eg_freeSample) {
+ /* Free old sample */
+ SampleMessage* msg = (SampleMessage*)obj;
+ fprintf(stderr, "Freeing %s\n", msg->sample->path);
+ free(msg->sample->path);
+ free(msg->sample->data);
+ free(msg->sample);
+ } else {
+ /* Handle set message (load sample). */
+ handle_set_message(plugin, (LV2_Atom_Object*)obj);
+ }
}
return 0;
@@ -179,14 +266,12 @@ instantiate(const LV2_Descriptor* descriptor,
return NULL;
}
- plugin->samp = (SampleFile*)malloc(sizeof(SampleFile));
- plugin->pending_samp = (SampleFile*)malloc(sizeof(SampleFile));
- if (!plugin->samp || !plugin->pending_samp) {
+ plugin->sample = (Sample*)malloc(sizeof(Sample));
+ if (!plugin->sample) {
return NULL;
}
- memset(plugin->samp, 0, sizeof(SampleFile));
- memset(plugin->pending_samp, 0, sizeof(SampleFile));
+ memset(plugin->sample, 0, sizeof(Sample));
memset(&plugin->uris, 0, sizeof(plugin->uris));
/* Create signal for waking up worker thread */
@@ -203,6 +288,10 @@ instantiate(const LV2_Descriptor* descriptor,
goto fail;
}
+ /* Create ringbuffers for communicating with worker thread */
+ plugin->to_worker = zix_ring_new(RING_SIZE);
+ plugin->from_worker = zix_ring_new(RING_SIZE);
+
/* Scan host features for URID map */
LV2_URID_Map* map = NULL;
for (int i = 0; features[i]; ++i) {
@@ -219,12 +308,14 @@ instantiate(const LV2_Descriptor* descriptor,
plugin->map = map;
map_sampler_uris(plugin->map, &plugin->uris);
- /* Open the default sample file */
- strncpy(plugin->pending_samp->filepath, path, STRING_BUF);
- strncat(plugin->pending_samp->filepath,
- default_sample_file,
- STRING_BUF - strlen(plugin->pending_samp->filepath) );
- handle_load_sample(plugin);
+ /* Load the default sample file */
+ const size_t path_len = strlen(path);
+ const size_t sample_file_len = strlen(default_sample_file);
+ const size_t len = path_len + sample_file_len;
+ char* sample_path = (char*)malloc(len + 1);
+ memcpy(sample_path, path, path_len);
+ memcpy(sample_path + path_len, default_sample_file, sample_file_len + 1);
+ plugin->sample = load_sample(plugin, sample_path, len);
return (LV2_Handle)plugin;
@@ -242,67 +333,15 @@ cleanup(LV2_Handle instance)
zix_sem_post(&plugin->signal);
zix_thread_join(plugin->worker_thread, 0);
zix_sem_destroy(&plugin->signal);
+ zix_ring_free(plugin->to_worker);
+ zix_ring_free(plugin->from_worker);
- free(plugin->samp->data);
- free(plugin->pending_samp->data);
- free(plugin->samp);
- free(plugin->pending_samp);
+ free(plugin->sample->data);
+ free(plugin->sample->path);
+ free(plugin->sample);
free(instance);
}
-static bool
-is_object_type(Sampler* plugin, LV2_URID type)
-{
- return type == plugin->uris.atom_Resource
- || type == plugin->uris.atom_Blank;
-}
-
-static bool
-handle_message(Sampler* plugin,
- const LV2_Atom_Object* obj)
-{
- if (obj->type != plugin->uris.msg_Set) {
- fprintf(stderr, "Ignoring unknown message type %d\n", obj->type);
- return false;
- }
-
- /* Message should look like this:
- * [
- * a msg:SetMessage ;
- * msg:body [
- * eg:filename "/some/value.wav" ;
- * ] ;
- * ]
- */
-
- /* Get body of message */
- const LV2_Atom_Object* body = NULL;
- lv2_object_getv(obj, plugin->uris.msg_body, &body, 0);
- if (!body) {
- fprintf(stderr, "Malformed set message has no body.\n");
- return false;
- }
- if (!is_object_type(plugin, body->atom.type)) {
- fprintf(stderr, "Malformed set message has non-object body.\n");
- return false;
- }
-
- /* Get filename from body */
- const LV2_Atom* filename = NULL;
- lv2_object_getv(body, plugin->uris.eg_filename, &filename, 0);
- if (!filename) {
- fprintf(stderr, "Ignored set message with no filename.\n");
- return false;
- }
-
- char* str = (char*)LV2_ATOM_BODY(filename);
- fprintf(stderr, "Request to load %s\n", str);
- memcpy(plugin->pending_samp->filepath, str, filename->size);
- zix_sem_post(&plugin->signal);
-
- return true;
-}
-
static void
run(LV2_Handle instance,
uint32_t sample_count)
@@ -322,9 +361,19 @@ run(LV2_Handle instance,
plugin->frame = 0;
plugin->play = true;
}
- } else if (ev->body.type == plugin->uris.atom_Resource
- || ev->body.type == plugin->uris.atom_Blank) {
- handle_message(plugin, (LV2_Atom_Object*)&ev->body);
+ } else if (is_object_type(plugin, ev->body.type)) {
+ const LV2_Atom_Object* obj = (LV2_Atom_Object*)&ev->body;
+ if (obj->type == plugin->uris.msg_Set) {
+ /* Received a set message, send it to the worker thread. */
+ fprintf(stderr, "Queueing set message\n");
+ zix_ring_write(plugin->to_worker,
+ obj,
+ lv2_atom_pad_size(
+ lv2_atom_total_size(&obj->atom)));
+ zix_sem_post(&plugin->signal);
+ } else {
+ fprintf(stderr, "Unknown object type %d\n", obj->type);
+ }
} else {
fprintf(stderr, "Unknown event type %d\n", ev->body.type);
}
@@ -333,14 +382,14 @@ run(LV2_Handle instance,
/* Render the sample (possibly already in progress) */
if (plugin->play) {
uint32_t f = plugin->frame;
- const uint32_t lf = plugin->samp->info.frames;
+ const uint32_t lf = plugin->sample->info.frames;
for (pos = 0; pos < start_frame; ++pos) {
output[pos] = 0;
}
for (; pos < sample_count && f < lf; ++pos, ++f) {
- output[pos] = plugin->samp->data[f];
+ output[pos] = plugin->sample->data[f];
}
plugin->frame = f;
@@ -350,20 +399,32 @@ run(LV2_Handle instance,
}
}
- /* Check if we have a sample pending */
- if (!plugin->play && plugin->pending_sample_ready) {
- /* Install the new sample */
- SampleFile* tmp = plugin->samp;
- plugin->samp = plugin->pending_samp;
- plugin->pending_samp = tmp;
- plugin->pending_sample_ready = 0;
- free(plugin->pending_samp->data); // FIXME: non-realtime!
- }
-
/* Add zeros to end if sample not long enough (or not playing) */
for (; pos < sample_count; ++pos) {
output[pos] = 0.0f;
}
+
+ /* Read messages from worker thread */
+ SampleMessage m;
+ const uint32_t msize = lv2_atom_pad_size(sizeof(m));
+ while (zix_ring_read(plugin->from_worker, &m, msize) == msize) {
+ if (m.atom.type == plugin->uris.eg_applySample) {
+ /** Send a message to the worker to free the current sample */
+ SampleMessage free_msg = {
+ { plugin->uris.eg_freeSample, sizeof(plugin->sample) },
+ plugin->sample
+ };
+ zix_ring_write(plugin->to_worker,
+ &free_msg,
+ lv2_atom_pad_size(sizeof(free_msg)));
+ zix_sem_post(&plugin->signal);
+
+ /** Install the new sample */
+ plugin->sample = m.sample;
+ } else {
+ fprintf(stderr, "Unknown message from worker\n");
+ }
+ }
}
static uint32_t
@@ -388,12 +449,12 @@ save(LV2_Handle instance,
Sampler* plugin = (Sampler*)instance;
char* apath = map_path->abstract_path(map_path->handle,
- plugin->samp->filepath);
+ plugin->sample->path);
store(callback_data,
map_uri(plugin, FILENAME_URI),
apath,
- strlen(plugin->samp->filepath) + 1,
+ strlen(plugin->sample->path) + 1,
plugin->uris.state_Path,
LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
@@ -419,9 +480,10 @@ restore(LV2_Handle instance,
&size, &type, &valflags);
if (value) {
- printf("Restoring filename %s\n", (const char*)value);
- strncpy(plugin->pending_samp->filepath, value, STRING_BUF);
- handle_load_sample(plugin);
+ const char* path = (const char*)value;
+ printf("Restoring filename %s\n", path);
+ // FIXME: leak?
+ plugin->sample = load_sample(plugin, path, size - 1);
}
}
diff --git a/plugins/eg-sampler.lv2/sampler.ttl b/plugins/eg-sampler.lv2/sampler.ttl
index cf36415..e2cbb10 100644
--- a/plugins/eg-sampler.lv2/sampler.ttl
+++ b/plugins/eg-sampler.lv2/sampler.ttl
@@ -20,12 +20,13 @@
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
+@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
<http://lv2plug.in/plugins/eg-sampler>
a lv2:Plugin ;
doap:name "Example Sampler" ;
doap:license <http://opensource.org/licenses/isc> ;
- lv2:requiredFeature <http://lv2plug.in/ns/ext/urid#map> ;
+ lv2:requiredFeature urid:map ;
lv2:optionalFeature lv2:hardRTCapable ;
lv2:extensionData <http://lv2plug.in/ns/ext/state#Interface> ;
ui:ui <http://lv2plug.in/plugins/eg-sampler#ui> ;
@@ -44,8 +45,8 @@
atom:bufferType atom:Sequence ;
atom:supports <http://lv2plug.in/ns/ext/message#Message> ;
lv2:index 1 ;
- lv2:symbol "response" ;
- lv2:name "Response"
+ lv2:symbol "notify" ;
+ lv2:name "Notify"
] , [
a lv2:AudioPort ,
lv2:OutputPort ;
@@ -56,4 +57,9 @@
<http://lv2plug.in/plugins/eg-sampler#ui>
a ui:GtkUI ;
- lv2:requiredFeature <http://lv2plug.in/ns/ext/urid#map> .
+ lv2:requiredFeature urid:map ;
+ ui:portNotification [
+ ui:plugin <http://lv2plug.in/plugins/eg-sampler> ;
+ lv2:symbol "notify" ;
+ ui:notifyType atom:Blank ;
+ ] .
diff --git a/plugins/eg-sampler.lv2/uris.h b/plugins/eg-sampler.lv2/uris.h
index e1c6edd..a607737 100644
--- a/plugins/eg-sampler.lv2/uris.h
+++ b/plugins/eg-sampler.lv2/uris.h
@@ -23,13 +23,17 @@
#define NS_ATOM "http://lv2plug.in/ns/ext/atom#"
#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-#define SAMPLER_URI "http://lv2plug.in/plugins/eg-sampler"
-#define MIDI_EVENT_URI "http://lv2plug.in/ns/ext/midi#MidiEvent"
-#define FILENAME_URI SAMPLER_URI "#filename"
+#define SAMPLER_URI "http://lv2plug.in/plugins/eg-sampler"
+#define MIDI_EVENT_URI "http://lv2plug.in/ns/ext/midi#MidiEvent"
+#define FILENAME_URI SAMPLER_URI "#filename"
+#define APPLY_SAMPLE_URI SAMPLER_URI "#applySample"
+#define FREE_SAMPLE_URI SAMPLER_URI "#freeSample"
typedef struct {
LV2_URID atom_Blank;
LV2_URID atom_Resource;
+ LV2_URID eg_applySample;
+ LV2_URID eg_freeSample;
LV2_URID eg_filename;
LV2_URID midi_Event;
LV2_URID msg_Set;
@@ -40,13 +44,15 @@ typedef struct {
static inline void
map_sampler_uris(LV2_URID_Map* map, SamplerURIs* uris)
{
- uris->atom_Blank = map->map(map->handle, NS_ATOM "Blank");
- uris->atom_Resource = map->map(map->handle, NS_ATOM "Resource");
- uris->eg_filename = map->map(map->handle, FILENAME_URI);
- uris->midi_Event = map->map(map->handle, MIDI_EVENT_URI);
- uris->msg_Set = map->map(map->handle, LV2_MESSAGE_Set);
- uris->msg_body = map->map(map->handle, LV2_MESSAGE_body);
- uris->state_Path = map->map(map->handle, LV2_STATE_PATH_URI);
+ uris->atom_Blank = map->map(map->handle, NS_ATOM "Blank");
+ uris->atom_Resource = map->map(map->handle, NS_ATOM "Resource");
+ uris->eg_applySample = map->map(map->handle, APPLY_SAMPLE_URI);
+ uris->eg_filename = map->map(map->handle, FILENAME_URI);
+ uris->eg_freeSample = map->map(map->handle, FREE_SAMPLE_URI);
+ uris->midi_Event = map->map(map->handle, MIDI_EVENT_URI);
+ uris->msg_Set = map->map(map->handle, LV2_MESSAGE_Set);
+ uris->msg_body = map->map(map->handle, LV2_MESSAGE_body);
+ uris->state_Path = map->map(map->handle, LV2_STATE_PATH_URI);
}
#endif /* SAMPLER_URIS_H */
diff --git a/plugins/eg-sampler.lv2/wscript b/plugins/eg-sampler.lv2/wscript
index cbff316..3cb51dc 100644
--- a/plugins/eg-sampler.lv2/wscript
+++ b/plugins/eg-sampler.lv2/wscript
@@ -33,6 +33,11 @@ def configure(conf):
autowaf.check_pkg(conf, 'lv2-lv2plug.in-ns-ext-message',
uselib_store='LV2_MESSAGE')
+ conf.check(function_name='mlock',
+ header_name='sys/mman.h',
+ define_name='HAVE_MLOCK',
+ mandatory=False)
+
autowaf.check_pkg(conf, 'sndfile', uselib_store='SNDFILE',
atleast_version='1.0.0', mandatory=True)
autowaf.check_pkg(conf, 'gtk+-2.0', uselib_store='GTK2',
@@ -81,7 +86,7 @@ def build(bld):
# Build plugin library
obj = bld(features = 'c cshlib',
env = penv,
- source = 'sampler.c',
+ source = ['sampler.c', 'zix/ring.c'],
name = 'sampler',
target = '%s/sampler' % bundle,
install_path = '${LV2DIR}/%s' % bundle,
diff --git a/plugins/eg-sampler.lv2/zix/ring.c b/plugins/eg-sampler.lv2/zix/ring.c
new file mode 100644
index 0000000..0e40515
--- /dev/null
+++ b/plugins/eg-sampler.lv2/zix/ring.c
@@ -0,0 +1,231 @@
+/*
+ Copyright 2011 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_MLOCK
+# include <sys/mman.h>
+# define ZIX_MLOCK(ptr, size) mlock((ptr), (size))
+#elif defined(_WIN32)
+# include <windows.h>
+# define ZIX_MLOCK(ptr, size) VirtualLock((ptr), (size))
+#else
+# pragma message("warning: No memory locking, possible RT violations")
+# define ZIX_MLOCK(ptr, size)
+#endif
+
+#if defined(__APPLE__)
+# include <libkern/OSAtomic.h>
+# define ZIX_FULL_BARRIER() OSMemoryBarrier()
+#elif defined(_WIN32)
+# include <windows.h>
+# define ZIX_FULL_BARRIER() MemoryBarrier()
+#elif (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)
+# define ZIX_FULL_BARRIER() __sync_synchronize()
+#else
+# pragma message("warning: No memory barriers, possible SMP bugs")
+# define ZIX_FULL_BARRIER()
+#endif
+
+/* No support for any systems with separate read and write barriers */
+#define ZIX_READ_BARRIER() ZIX_FULL_BARRIER()
+#define ZIX_WRITE_BARRIER() ZIX_FULL_BARRIER()
+
+#include "zix/ring.h"
+
+struct ZixRingImpl {
+ uint32_t write_head; ///< Read index into buf
+ uint32_t read_head; ///< Write index into buf
+ uint32_t size; ///< Size (capacity) in bytes
+ uint32_t size_mask; ///< Mask for fast modulo
+ char* buf; ///< Contents
+};
+
+static inline uint32_t
+next_power_of_two(uint32_t size)
+{
+ // http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
+ size--;
+ size |= size >> 1;
+ size |= size >> 2;
+ size |= size >> 4;
+ size |= size >> 8;
+ size |= size >> 16;
+ size++;
+ return size;
+}
+
+ZixRing*
+zix_ring_new(uint32_t size)
+{
+ ZixRing* ring = (ZixRing*)malloc(sizeof(ZixRing));
+ ring->write_head = 0;
+ ring->read_head = 0;
+ ring->size = next_power_of_two(size);
+ ring->size_mask = ring->size - 1;
+ ring->buf = (char*)malloc(ring->size);
+ return ring;
+}
+
+void
+zix_ring_free(ZixRing* ring)
+{
+ free(ring->buf);
+ free(ring);
+}
+
+void
+zix_ring_mlock(ZixRing* ring)
+{
+ ZIX_MLOCK(ring, sizeof(ZixRing));
+ ZIX_MLOCK(ring->buf, ring->size);
+}
+
+void
+zix_ring_reset(ZixRing* ring)
+{
+ ring->write_head = 0;
+ ring->read_head = 0;
+}
+
+static inline uint32_t
+read_space_internal(const ZixRing* ring, uint32_t r, uint32_t w)
+{
+ if (r < w) {
+ return w - r;
+ } else {
+ return (w - r + ring->size) & ring->size_mask;
+ }
+}
+
+uint32_t
+zix_ring_read_space(const ZixRing* ring)
+{
+ return read_space_internal(ring, ring->read_head, ring->write_head);
+}
+
+static inline uint32_t
+write_space_internal(const ZixRing* ring, uint32_t r, uint32_t w)
+{
+ if (r == w) {
+ return ring->size - 1;
+ } else if (r < w) {
+ return ((r - w + ring->size) & ring->size_mask) - 1;
+ } else {
+ return (r - w) - 1;
+ }
+}
+
+uint32_t
+zix_ring_write_space(const ZixRing* ring)
+{
+ return write_space_internal(ring, ring->read_head, ring->write_head);
+}
+
+uint32_t
+zix_ring_capacity(const ZixRing* ring)
+{
+ return ring->size - 1;
+}
+
+static inline uint32_t
+peek_internal(const ZixRing* ring, uint32_t r, uint32_t w,
+ uint32_t size, void* dst)
+{
+ if (read_space_internal(ring, r, w) < size) {
+ return 0;
+ }
+
+ if (r + size < ring->size) {
+ memcpy(dst, &ring->buf[r], size);
+ } else {
+ const uint32_t first_size = ring->size - r;
+ memcpy(dst, &ring->buf[r], first_size);
+ memcpy((char*)dst + first_size, &ring->buf[0], size - first_size);
+ }
+
+ return size;
+}
+
+uint32_t
+zix_ring_peek(ZixRing* ring, void* dst, uint32_t size)
+{
+ const uint32_t r = ring->read_head;
+ const uint32_t w = ring->write_head;
+
+ return peek_internal(ring, r, w, size, dst);
+}
+
+uint32_t
+zix_ring_read(ZixRing* ring, void* dst, uint32_t size)
+{
+ const uint32_t r = ring->read_head;
+ const uint32_t w = ring->write_head;
+
+ if (peek_internal(ring, r, w, size, dst)) {
+ ZIX_READ_BARRIER();
+ ring->read_head = (r + size) & ring->size_mask;
+ return size;
+ } else {
+ return 0;
+ }
+}
+
+uint32_t
+zix_ring_skip(ZixRing* ring, uint32_t size)
+{
+ const uint32_t r = ring->read_head;
+ const uint32_t w = ring->write_head;
+ if (read_space_internal(ring, r, w) < size) {
+ return 0;
+ }
+
+ ZIX_READ_BARRIER();
+ ring->read_head = (r + size) & ring->size_mask;
+ return size;
+}
+
+uint32_t
+zix_ring_write(ZixRing* ring, const void* src, uint32_t size)
+{
+ const uint32_t r = ring->read_head;
+ const uint32_t w = ring->write_head;
+ if (write_space_internal(ring, r, w) < size) {
+ return 0;
+ }
+
+ if (w + size <= ring->size) {
+ memcpy(&ring->buf[w], src, size);
+ ZIX_WRITE_BARRIER();
+ ring->write_head = (w + size) & ring->size_mask;
+ } else {
+ const uint32_t this_size = ring->size - w;
+ memcpy(&ring->buf[w], src, this_size);
+ memcpy(&ring->buf[0], (char*)src + this_size, size - this_size);
+ ZIX_WRITE_BARRIER();
+ ring->write_head = size - this_size;
+ }
+
+ return size;
+}
+
+void*
+zix_ring_write_head(ZixRing* ring)
+{
+ return &ring->buf[ring->write_head];
+}
diff --git a/plugins/eg-sampler.lv2/zix/ring.h b/plugins/eg-sampler.lv2/zix/ring.h
new file mode 100644
index 0000000..ea673fe
--- /dev/null
+++ b/plugins/eg-sampler.lv2/zix/ring.h
@@ -0,0 +1,136 @@
+/*
+ Copyright 2011 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#ifndef ZIX_RING_H
+#define ZIX_RING_H
+
+#include <stdint.h>
+
+#include "zix/common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ @addtogroup zix
+ @{
+ @name Ring
+ @{
+*/
+
+/**
+ A lock-free ring buffer.
+
+ Thread-safe with a single reader and single writer, and realtime safe
+ on both ends.
+*/
+typedef struct ZixRingImpl ZixRing;
+
+/**
+ Create a new ring.
+ @param size Size in bytes (note this may be rounded up).
+
+ At most @c size - 1 bytes may be stored in the ring at once.
+*/
+ZixRing*
+zix_ring_new(uint32_t size);
+
+/**
+ Destroy a ring.
+*/
+void
+zix_ring_free(ZixRing* ring);
+
+/**
+ Lock the ring data into physical memory.
+
+ This function is NOT thread safe or real-time safe, but it should be called
+ after zix_ring_new() to lock all ring memory to avoid page faults while
+ using the ring (i.e. this function MUST be called first in order for the
+ ring to be truly real-time safe).
+
+*/
+void
+zix_ring_mlock(ZixRing* ring);
+
+/**
+ Reset (empty) a ring.
+
+ This function is NOT thread-safe, it may only be called when there are no
+ readers or writers.
+*/
+void
+zix_ring_reset(ZixRing* ring);
+
+/**
+ Return the number of bytes of space available for reading.
+*/
+uint32_t
+zix_ring_read_space(const ZixRing* ring);
+
+/**
+ Return the number of bytes of space available for writing.
+*/
+uint32_t
+zix_ring_write_space(const ZixRing* ring);
+
+/**
+ Return the capacity (i.e. total write space when empty).
+*/
+uint32_t
+zix_ring_capacity(const ZixRing* ring);
+
+/**
+ Read from the ring without advancing the read head.
+*/
+uint32_t
+zix_ring_peek(ZixRing* ring, void* dst, uint32_t size);
+
+/**
+ Read from the ring and advance the read head.
+*/
+uint32_t
+zix_ring_read(ZixRing* ring, void* dst, uint32_t size);
+
+/**
+ Skip data in the ring (advance read head without reading).
+*/
+uint32_t
+zix_ring_skip(ZixRing* ring, uint32_t size);
+
+/**
+ Write data to the ring.
+*/
+uint32_t
+zix_ring_write(ZixRing* ring, const void* src, uint32_t size);
+
+/**
+ Return a pointer to the current position of the write head.
+*/
+void*
+zix_ring_write_head(ZixRing* ring);
+
+/**
+ @}
+ @}
+*/
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ZIX_RING_H */