diff options
| author | David Robillard <d@drobilla.net> | 2012-02-16 23:16:37 +0000 | 
|---|---|---|
| committer | David Robillard <d@drobilla.net> | 2012-02-16 23:16:37 +0000 | 
| commit | 495f83c7f13349930df3526789d09c75c1312bd4 (patch) | |
| tree | 5187d1b195dfd9e23caaafad30ab272882468720 | |
| parent | 75d524c764a6f91aa9662040a2c218e8d2802438 (diff) | |
| download | lv2-495f83c7f13349930df3526789d09c75c1312bd4.tar.xz | |
Implement real-time safe sample loading.
| -rw-r--r-- | lv2/lv2plug.in/ns/extensions/ui/ui.ttl | 23 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/sampler.c | 312 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/sampler.ttl | 14 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/uris.h | 26 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/wscript | 7 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/zix/ring.c | 231 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/zix/ring.h | 136 | 
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 */ |