From 495f83c7f13349930df3526789d09c75c1312bd4 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Thu, 16 Feb 2012 23:16:37 +0000 Subject: Implement real-time safe sample loading. --- lv2/lv2plug.in/ns/extensions/ui/ui.ttl | 23 ++- plugins/eg-sampler.lv2/sampler.c | 312 ++++++++++++++++++++------------- plugins/eg-sampler.lv2/sampler.ttl | 14 +- plugins/eg-sampler.lv2/uris.h | 26 +-- plugins/eg-sampler.lv2/wscript | 7 +- plugins/eg-sampler.lv2/zix/ring.c | 231 ++++++++++++++++++++++++ plugins/eg-sampler.lv2/zix/ring.h | 136 ++++++++++++++ 7 files changed, 608 insertions(+), 141 deletions(-) create mode 100644 plugins/eg-sampler.lv2/zix/ring.c create mode 100644 plugins/eg-sampler.lv2/zix/ring.h 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 """ +

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.

+ +

A ui:PortNotification MUST have either a ui:portIndex or a lv2:symbol to +indicate which port it refers to.

+""" . 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 """ +

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.

+""" . 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: . @prefix lv2: . @prefix ui: . +@prefix urid: . a lv2:Plugin ; doap:name "Example Sampler" ; doap:license ; - lv2:requiredFeature ; + lv2:requiredFeature urid:map ; lv2:optionalFeature lv2:hardRTCapable ; lv2:extensionData ; ui:ui ; @@ -44,8 +45,8 @@ atom:bufferType atom:Sequence ; atom:supports ; lv2:index 1 ; - lv2:symbol "response" ; - lv2:name "Response" + lv2:symbol "notify" ; + lv2:name "Notify" ] , [ a lv2:AudioPort , lv2:OutputPort ; @@ -56,4 +57,9 @@ a ui:GtkUI ; - lv2:requiredFeature . + lv2:requiredFeature urid:map ; + ui:portNotification [ + ui:plugin ; + 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 + + 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 +#include +#include + +#ifdef HAVE_MLOCK +# include +# define ZIX_MLOCK(ptr, size) mlock((ptr), (size)) +#elif defined(_WIN32) +# include +# 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 +# define ZIX_FULL_BARRIER() OSMemoryBarrier() +#elif defined(_WIN32) +# include +# 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 + + 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 + +#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 */ -- cgit v1.2.1