summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2012-07-07 06:41:46 +0000
committerDavid Robillard <d@drobilla.net>2012-07-07 06:41:46 +0000
commit277cd0f1959f75db24ecf48be1030aa1af03cd6d (patch)
tree5d384ed3a63f1de3f2278cbdfebba46f3e4a95b0
downloadmatriseq.lv2-277cd0f1959f75db24ecf48be1030aa1af03cd6d.tar.gz
matriseq.lv2-277cd0f1959f75db24ecf48be1030aa1af03cd6d.tar.bz2
matriseq.lv2-277cd0f1959f75db24ecf48be1030aa1af03cd6d.zip
Add matriseq, a step sequencer for the Novation Launchpad.
git-svn-id: http://svn.drobilla.net/lad/trunk/plugins/matriseq@4512 a436a847-0d15-0410-975c-d299462d15a1
-rw-r--r--manifest.ttl.in8
-rw-r--r--matriseq.c341
-rw-r--r--matriseq.ttl39
l---------waf1
-rw-r--r--wscript76
-rw-r--r--zix/common.h83
-rw-r--r--zix/ring.c225
-rw-r--r--zix/ring.h130
-rw-r--r--zix/thread.h133
9 files changed, 1036 insertions, 0 deletions
diff --git a/manifest.ttl.in b/manifest.ttl.in
new file mode 100644
index 0000000..4c08639
--- /dev/null
+++ b/manifest.ttl.in
@@ -0,0 +1,8 @@
+@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://drobilla.net/plugins/matriseq>
+ a lv2:Plugin ;
+ lv2:binary <matriseq@LIB_EXT@> ;
+ rdfs:seeAlso <matriseq.ttl> .
diff --git a/matriseq.c b/matriseq.c
new file mode 100644
index 0000000..bb9c1fe
--- /dev/null
+++ b/matriseq.c
@@ -0,0 +1,341 @@
+/*
+ Copyright 2012 David Robillard <d@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 <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "naub/naub.h"
+#include "zix/thread.h"
+#include "zix/ring.h"
+
+#include "lv2/lv2plug.in/ns/ext/atom/forge.h"
+#include "lv2/lv2plug.in/ns/ext/log/log.h"
+#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
+#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
+#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
+
+#define MATRISEQ_URI "http://drobilla.net/plugins/matriseq"
+
+static const uint32_t STEP_TYPE = 8;
+static const uint32_t RING_SIZE = 4096;
+
+typedef enum {
+ MATRISEQ_IN = 0,
+ MATRISEQ_OUT = 1
+} PortIndex;
+
+// URIDs used by this plugin
+typedef struct {
+ LV2_URID log_Error;
+ LV2_URID midi_MidiEvent;
+} MatriseqURIs;
+
+typedef struct {
+ // Port buffers
+ LV2_Atom_Sequence* in;
+ LV2_Atom_Sequence* out;
+
+ // Features
+ LV2_URID_Map* map;
+ LV2_Log_Log* log;
+
+ // LV2 stuff
+ LV2_Atom_Forge forge;
+ MatriseqURIs uris;
+
+ // USB stuff
+ NaubWorld* naub;
+ ZixThread thread;
+ ZixRing* ring;
+ bool exit;
+
+ // State
+ uint32_t time_frames;
+ uint8_t grid[8][8];
+ uint32_t step;
+ double rate;
+ float bpm;
+} Matriseq;
+
+// Log a message to the host if available, or stderr otherwise.
+LV2_LOG_FUNC(3, 4)
+static void
+print(Matriseq* self, LV2_URID type, const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ if (self->log) {
+ self->log->vprintf(self->log->handle, type, fmt, args);
+ } else {
+ vfprintf(stderr, fmt, args);
+ }
+ va_end(args);
+}
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor,
+ double rate,
+ const char* bundle_path,
+ const LV2_Feature* const* features)
+{
+ Matriseq* self = (Matriseq*)calloc(1, sizeof(Matriseq));
+ if (!self) {
+ return NULL;
+ }
+
+ // Get 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) {
+ print(self, self->uris.log_Error, "Missing feature urid:map.\n");
+ free(self);
+ return NULL;
+ }
+
+ // Initialise LV2 stuff
+ LV2_URID_Map* map = self->map;
+ self->uris.log_Error = map->map(map->handle, LV2_LOG__Error);
+ self->uris.midi_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent);
+ lv2_atom_forge_init(&self->forge, self->map);
+
+ if (self) {
+ self->ring = zix_ring_new(RING_SIZE);
+ self->rate = rate;
+ self->bpm = 140.0f;
+
+ zix_ring_mlock(self->ring);
+ }
+
+ return (LV2_Handle)self;
+}
+
+static void
+connect_port(LV2_Handle instance,
+ uint32_t port,
+ void* data)
+{
+ Matriseq* self = (Matriseq*)instance;
+
+ switch ((PortIndex)port) {
+ case MATRISEQ_IN:
+ self->in = (LV2_Atom_Sequence*)data;
+ break;
+ case MATRISEQ_OUT:
+ self->out = (LV2_Atom_Sequence*)data;
+ break;
+ }
+}
+
+static void
+set_button(Matriseq* self, NaubControlID control, bool active)
+{
+ int32_t value = 0;
+ if (self->grid[control.y][control.x]) {
+ value = active ? naub_rgb(1, 0, 0) : naub_rgb(1, 1, 0);
+ } else {
+ value = active ? naub_rgb(0, 0.4, 0) : naub_rgb(0, 0, 0);
+ }
+ naub_set_control(self->naub, control, value);
+}
+
+static void
+naub_event(void* instance,
+ const NaubEvent* event)
+{
+ Matriseq* self = (Matriseq*)instance;
+ if (event->type != NAUB_EVENT_BUTTON) { // Odd...
+ return;
+ }
+
+ const NaubControlID control = event->button.control;
+ if (control.group != 0) {
+ return;
+ }
+
+ /*
+ fprintf(stderr, "PRESS %d:%d.%d.%d\n",
+ control.device,
+ control.group,
+ control.x,
+ control.y);
+ */
+
+ if (event->button.pressed) {
+ naub_set_control(self->naub, control, naub_rgb(1, 0, 0));
+ } else {
+ uint8_t* cell = &self->grid[control.y][control.x];
+ *cell = *cell ? 0 : 1;
+ set_button(self, control, self->step == control.y);
+ }
+
+ naub_flush(self->naub);
+}
+
+static void*
+thread_run(void* instance)
+{
+ Matriseq* self = (Matriseq*)instance;
+ uint32_t step = self->step;
+
+ while (!naub_handle_events_timeout(self->naub, 10) && !self->exit) {
+ uint32_t new_step;
+ if (zix_ring_read_space(self->ring) >= sizeof(new_step)) {
+ zix_ring_read(self->ring, &new_step, sizeof(new_step));
+
+ // De-highlight old active row
+ for (int y = 0; y < 8; ++y) {
+ const NaubControlID control = { 0, 0, step, y };
+ set_button(self, control, false);
+ }
+
+ // Highlight new active row
+ for (int y = 0; y < 8; ++y) {
+ const NaubControlID control = { 0, 0, new_step, y };
+ set_button(self, control, true);
+ }
+
+ // Send bulk update to device
+ naub_flush(self->naub);
+
+ step = new_step;
+ }
+ }
+ return NULL;
+}
+
+static void
+activate(LV2_Handle instance)
+{
+ Matriseq* self = (Matriseq*)instance;
+ self->naub = naub_world_new(self, naub_event);
+ if (self->naub) {
+ if (!naub_world_open(
+ self->naub, NAUB_VENDOR_NOVATION, NAUB_PRODUCT_LAUNCHPAD)) {
+ fprintf(stderr, "Opened Launchpad\n");
+ if (zix_thread_create(&self->thread, 1024, thread_run, self)) {
+ fprintf(stderr, "Failed to create thread\n");
+ return;
+ }
+ }
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t n_frames)
+{
+ Matriseq* self = (Matriseq*)instance;
+ const float bps = self->bpm / 60.0f; // Beats per second
+ const float spb = 1.0 / bps; // Seconds per beat
+ //const float spt = spb / STEP_TYPE;
+ const float spt = spb / 2; // Seconds per tick (step)
+
+ // Prepare for writing to out port
+ const uint32_t out_capacity = self->out->atom.size;
+ lv2_atom_forge_set_buffer(&self->forge, (uint8_t*)self->out, out_capacity);
+
+ // Initialise output port to empty sequence
+ LV2_Atom_Forge_Frame out_frame;
+ lv2_atom_forge_sequence_head(&self->forge, &out_frame, 0);
+
+ //fprintf(stderr, "BPS: %f SPB: %f\n", bps, spb);
+ for (uint32_t i = 0; i < n_frames; ++i) {
+ const double time_s = self->time_frames / self->rate;
+ const uint32_t step = (uint32_t)(time_s / spt) % STEP_TYPE;
+ if (step != self->step) {
+ // Update step and frame time
+ self->step = step;
+ if (step == 0) {
+ self->time_frames = 0;
+ } else {
+ ++self->time_frames;
+ }
+
+ // Notify USB thread of new step
+ //fprintf(stderr, "LV2 STEP %d @ %f\n", self->step, time_s);
+ zix_ring_write(self->ring, &self->step, sizeof(self->step));
+
+ // Send note ons for enabled notes this step
+ for (uint32_t y = 0; y < 8; ++y) {
+ if (self->grid[y][step]) {
+ const uint8_t note = (7 - y) + 36;
+ const uint8_t ev[] = { 0x90, note, 0x40 };
+ lv2_atom_forge_frame_time(&self->forge, i);
+ lv2_atom_forge_atom(&self->forge, 3, self->uris.midi_MidiEvent);
+ lv2_atom_forge_write(&self->forge, ev, 3);
+ }
+ }
+ } else {
+ ++self->time_frames;
+ }
+ }
+}
+
+static void
+deactivate(LV2_Handle instance)
+{
+ Matriseq* self = (Matriseq*)instance;
+ self->exit = true;
+ void* thread_ret = NULL;
+ zix_thread_join(self->thread, &thread_ret);
+ naub_world_free(self->naub);
+ self->naub = NULL;
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ Matriseq* self = (Matriseq*)instance;
+ zix_ring_free(self->ring);
+ free(self);
+}
+
+const void*
+extension_data(const char* uri)
+{
+ return NULL;
+}
+
+static const LV2_Descriptor descriptor = {
+ MATRISEQ_URI,
+ instantiate,
+ connect_port,
+ activate,
+ run,
+ deactivate,
+ cleanup,
+ extension_data
+};
+
+LV2_SYMBOL_EXPORT
+const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch (index) {
+ case 0:
+ return &descriptor;
+ default:
+ return NULL;
+ }
+}
diff --git a/matriseq.ttl b/matriseq.ttl
new file mode 100644
index 0000000..2abf153
--- /dev/null
+++ b/matriseq.ttl
@@ -0,0 +1,39 @@
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+<http://drobilla.net/drobilla#me>
+ a foaf:Person ;
+ foaf:name "David Robillard" ;
+ foaf:mbox <mailto:d@drobilla.net> ;
+ rdfs:seeAlso <http://drobilla.net/drobilla> .
+
+<http://drobilla.net/plugins/matriseq>
+ a lv2:Plugin ,
+ lv2:AmplifierPlugin ;
+ doap:maintainer <http://drobilla.net/drobilla#me> ;
+ doap:name "Matriseq" ;
+ doap:license <http://opensource.org/licenses/isc> ;
+ lv2:optionalFeature lv2:hardRTCapable ;
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports <http://lv2plug.in/ns/ext/midi#MidiEvent> ,
+ <http://lv2plug.in/ns/ext/patch#Message> ;
+ lv2:index 0 ;
+ lv2:symbol "in" ;
+ lv2:name "In"
+ ] , [
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports <http://lv2plug.in/ns/ext/midi#MidiEvent> ,
+ <http://lv2plug.in/ns/ext/patch#Message> ;
+ lv2:index 1 ;
+ lv2:symbol "out" ;
+ lv2:name "Out"
+ ] .
diff --git a/waf b/waf
new file mode 120000
index 0000000..59a1ac9
--- /dev/null
+++ b/waf
@@ -0,0 +1 @@
+../../waf \ No newline at end of file
diff --git a/wscript b/wscript
new file mode 100644
index 0000000..474e96e
--- /dev/null
+++ b/wscript
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+from waflib.extras import autowaf as autowaf
+
+# Variables for 'waf dist'
+APPNAME = 'matriseq.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.display_header('Matriseq Configuration')
+
+ if conf.env['MSVC_COMPILER']:
+ conf.env.append_unique('CFLAGS', ['-TP', '-MD'])
+ else:
+ conf.env.append_unique('CFLAGS', '-std=c99')
+
+ autowaf.check_pkg(conf, 'lv2', atleast_version='1.0.0', uselib_store='LV2')
+ autowaf.check_pkg(conf, 'naub-0', atleast_version='0.0.0', uselib_store='NAUB')
+
+ # Set env['pluginlib_PATTERN']
+ pat = conf.env['cshlib_PATTERN']
+ if pat.startswith('lib'):
+ pat = pat[3:]
+ conf.env['pluginlib_PATTERN'] = pat
+ conf.env['pluginlib_EXT'] = pat[pat.rfind('.'):]
+
+ autowaf.display_msg(conf, "LV2 bundle directory", conf.env['LV2DIR'])
+ print('')
+
+def build(bld):
+ bundle = 'matriseq.lv2'
+
+ # Build manifest.ttl by substitution (for portable lib extension)
+ bld(features = 'subst',
+ source = 'manifest.ttl.in',
+ target = '%s/%s' % (bundle, 'manifest.ttl'),
+ install_path = '${LV2DIR}/%s' % bundle,
+ LIB_EXT = bld.env['pluginlib_EXT'])
+
+ # Copy other data files to build bundle (build/matriseq.lv2)
+ for i in ['matriseq.ttl']:
+ bld(features = 'subst',
+ source = i,
+ target = '%s/%s' % (bundle, i),
+ install_path = '${LV2DIR}/%s' % bundle,
+ LIB_EXT = bld.env['pluginlib_EXT'])
+
+ # Create a build environment that builds module-style library names
+ # e.g. matriseq.so instead of libmatriseq.so
+ # Note for C++ you must set cxxshlib_PATTERN instead
+ penv = bld.env.derive()
+ penv['cshlib_PATTERN'] = bld.env['pluginlib_PATTERN']
+
+ includes = None
+ if autowaf.is_child:
+ includes = '../..'
+
+ # Build plugin library
+ obj = bld(features = 'c cshlib',
+ env = penv,
+ source = ['matriseq.c', 'zix/ring.c'],
+ name = 'matriseq',
+ target = '%s/matriseq' % bundle,
+ install_path = '${LV2DIR}/%s' % bundle,
+ includes = includes)
+ autowaf.use_lib(bld, obj, 'LV2 NAUB')
+
diff --git a/zix/common.h b/zix/common.h
new file mode 100644
index 0000000..59e1f55
--- /dev/null
+++ b/zix/common.h
@@ -0,0 +1,83 @@
+/*
+ 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_COMMON_H
+#define ZIX_COMMON_H
+
+/**
+ @addtogroup zix
+ @{
+*/
+
+/** @cond */
+#ifdef ZIX_SHARED
+# ifdef _WIN32
+# define ZIX_LIB_IMPORT __declspec(dllimport)
+# define ZIX_LIB_EXPORT __declspec(dllexport)
+# else
+# define ZIX_LIB_IMPORT __attribute__((visibility("default")))
+# define ZIX_LIB_EXPORT __attribute__((visibility("default")))
+# endif
+# ifdef ZIX_INTERNAL
+# define ZIX_API ZIX_LIB_EXPORT
+# else
+# define ZIX_API ZIX_LIB_IMPORT
+# endif
+#else
+# define ZIX_API
+#endif
+/** @endcond */
+
+#ifdef __cplusplus
+extern "C" {
+#else
+# include <stdbool.h>
+#endif
+
+typedef enum {
+ ZIX_STATUS_SUCCESS,
+ ZIX_STATUS_ERROR,
+ ZIX_STATUS_NO_MEM,
+ ZIX_STATUS_NOT_FOUND,
+ ZIX_STATUS_EXISTS,
+ ZIX_STATUS_BAD_ARG,
+ ZIX_STATUS_BAD_PERMS,
+} ZixStatus;
+
+/**
+ Function for comparing two elements.
+*/
+typedef int (*ZixComparator)(const void* a, const void* b, void* user_data);
+
+/**
+ Function for testing equality of two elements.
+*/
+typedef bool (*ZixEqualFunc)(const void* a, const void* b);
+
+/**
+ Function to destroy an element.
+*/
+typedef void (*ZixDestroyFunc)(void* ptr);
+
+/**
+ @}
+*/
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ZIX_COMMON_H */
diff --git a/zix/ring.c b/zix/ring.c
new file mode 100644
index 0000000..7ebbe67
--- /dev/null
+++ b/zix/ring.c
@@ -0,0 +1,225 @@
+/*
+ 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;
+}
diff --git a/zix/ring.h b/zix/ring.h
new file mode 100644
index 0000000..34ba2fa
--- /dev/null
+++ b/zix/ring.h
@@ -0,0 +1,130 @@
+/*
+ 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);
+
+/**
+ @}
+ @}
+*/
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ZIX_RING_H */
diff --git a/zix/thread.h b/zix/thread.h
new file mode 100644
index 0000000..ff5a727
--- /dev/null
+++ b/zix/thread.h
@@ -0,0 +1,133 @@
+/*
+ Copyright 2012 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_THREAD_H
+#define ZIX_THREAD_H
+
+#ifdef _WIN32
+# include <windows.h>
+#else
+# include <errno.h>
+# include <pthread.h>
+#endif
+
+#include "zix/common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#else
+# include <stdbool.h>
+#endif
+
+/**
+ @addtogroup zix
+ @{
+ @name Thread
+ @{
+*/
+
+#ifdef _WIN32
+typedef HANDLE ZixThread;
+#else
+typedef pthread_t ZixThread;
+#endif
+
+/**
+ Initialize @c thread to a new thread.
+
+ The thread will immediately be launched, calling @c function with @c arg
+ as the only parameter.
+*/
+static inline ZixStatus
+zix_thread_create(ZixThread* thread,
+ size_t stack_size,
+ void* (*function)(void*),
+ void* arg);
+
+/**
+ Join @c thread (block until @c thread exits).
+*/
+static inline ZixStatus
+zix_thread_join(ZixThread thread, void** retval);
+
+#ifdef _WIN32
+
+static inline ZixStatus
+zix_thread_create(ZixThread* thread,
+ size_t stack_size,
+ void* (*function)(void*),
+ void* arg)
+{
+ *thread = CreateThread(NULL, stack_size,
+ (LPTHREAD_START_ROUTINE)function, arg,
+ 0, NULL);
+ return *thread ? ZIX_STATUS_SUCCESS : ZIX_STATUS_ERROR;
+}
+
+static inline ZixStatus
+zix_thread_join(ZixThread thread, void** retval)
+{
+ return WaitForSingleObject(thread, INFINITE)
+ ? ZIX_STATUS_SUCCESS : ZIX_STATUS_ERROR;
+}
+
+#else /* !defined(_WIN32) */
+
+static inline ZixStatus
+zix_thread_create(ZixThread* thread,
+ size_t stack_size,
+ void* (*function)(void*),
+ void* arg)
+{
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setstacksize(&attr, stack_size);
+
+ const int ret = pthread_create(thread, NULL, function, arg);
+ pthread_attr_destroy(&attr);
+
+ if (ret == EAGAIN) {
+ return ZIX_STATUS_NO_MEM;
+ } else if (ret == EINVAL) {
+ return ZIX_STATUS_BAD_ARG;
+ } else if (ret == EPERM) {
+ return ZIX_STATUS_BAD_PERMS;
+ } else if (ret) {
+ return ZIX_STATUS_ERROR;
+ }
+
+ return ZIX_STATUS_SUCCESS;
+}
+
+static inline ZixStatus
+zix_thread_join(ZixThread thread, void** retval)
+{
+ return pthread_join(thread, retval)
+ ? ZIX_STATUS_ERROR : ZIX_STATUS_SUCCESS;
+}
+
+#endif
+
+/**
+ @}
+ @}
+*/
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ZIX_THREAD_H */