From 277cd0f1959f75db24ecf48be1030aa1af03cd6d Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 7 Jul 2012 06:41:46 +0000 Subject: 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 --- manifest.ttl.in | 8 ++ matriseq.c | 341 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ matriseq.ttl | 39 +++++++ waf | 1 + wscript | 76 +++++++++++++ zix/common.h | 83 ++++++++++++++ zix/ring.c | 225 +++++++++++++++++++++++++++++++++++++ zix/ring.h | 130 +++++++++++++++++++++ zix/thread.h | 133 ++++++++++++++++++++++ 9 files changed, 1036 insertions(+) create mode 100644 manifest.ttl.in create mode 100644 matriseq.c create mode 100644 matriseq.ttl create mode 120000 waf create mode 100644 wscript create mode 100644 zix/common.h create mode 100644 zix/ring.c create mode 100644 zix/ring.h create mode 100644 zix/thread.h 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: . +@prefix rdfs: . +@prefix ui: . + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . 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 + + 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 +#include +#include +#include + +#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: . +@prefix doap: . +@prefix foaf: . +@prefix lv2: . +@prefix rdf: . +@prefix rdfs: . + + + a foaf:Person ; + foaf:name "David Robillard" ; + foaf:mbox ; + rdfs:seeAlso . + + + a lv2:Plugin , + lv2:AmplifierPlugin ; + doap:maintainer ; + doap:name "Matriseq" ; + doap:license ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:port [ + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports , + ; + lv2:index 0 ; + lv2:symbol "in" ; + lv2:name "In" + ] , [ + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports , + ; + 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 + + 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 +#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 + + 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; +} 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 + + 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); + +/** + @} + @} +*/ + +#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 + + 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 +#else +# include +# include +#endif + +#include "zix/common.h" + +#ifdef __cplusplus +extern "C" { +#else +# include +#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 */ -- cgit v1.2.1