Skip to content

Instantly share code, notes, and snippets.

@rweichler
Last active March 29, 2018 02:11
Show Gist options
  • Select an option

  • Save rweichler/ecdc3ca4cc372306f458a31dbd3928aa to your computer and use it in GitHub Desktop.

Select an option

Save rweichler/ecdc3ca4cc372306f458a31dbd3928aa to your computer and use it in GitHub Desktop.
crossfeed v2
// put in /var/tweak/com.r333d.eqe/lib/com.r333d.eqe.plugin.crossfeed.dylib
#include <Accelerate/Accelerate.h>
#include <string.h>
#include <stdlib.h>
#include <libkern/OSAtomic.h>
#include <pthread.h>
#include <TPCircularBuffer.h>
#define ATOMIC_SWAP32(target, val) while(!OSAtomicCompareAndSwap32(*(int32_t *)&(target), *(int32_t *)&(val), (int32_t *)&(target)));
#define NUM_CHANNELS 2
static float _curSampleRate = 44100;
static uint32_t _curSampleCount = 4096 * 4;
static float **_allCoefs = NULL;
static size_t _numCoefs = 0;
static float ***_keepIn = NULL;
static float ***_keepOut = NULL;
static TPCircularBuffer _oldAudio[NUM_CHANNELS];
static int _oldAudioSize = 32;
static int32_t _enabled = true;
static float _intensity = 0;
static float _delayMS = 0;
uint32_t _delaySamples = 0;
static pthread_mutex_t _mutex;
static pthread_mutex_t _setMutex;
static pthread_mutex_t _coefsMutex;
static bool _didInit = false;
bool shouldFilter = true;
static inline void init()
{
if(_didInit) {
return;
}
_didInit = true;
pthread_mutex_init(&_mutex, NULL);
pthread_mutex_init(&_setMutex, NULL);
pthread_mutex_init(&_coefsMutex, NULL);
for(int c = 0; c < NUM_CHANNELS; c++) {
TPCircularBufferInit(&_oldAudio[c], _oldAudioSize * sizeof(float));
TPCircularBufferSetAtomic(&_oldAudio[c], false);
}
}
static void setDelaySamples(int samples)
{
if(samples + _curSampleCount > _oldAudioSize) {
// realloc ringbuffer
int newSize = _oldAudioSize;
while(newSize < samples + _curSampleCount) {
newSize *= 2;
}
pthread_mutex_lock(&_mutex);
for(int c = 0; c < NUM_CHANNELS; c++) {
uint32_t availableBytes;
float *buf = TPCircularBufferTail(&_oldAudio[c], &availableBytes);
TPCircularBuffer newBuffer;
TPCircularBufferInit(&newBuffer, newSize * sizeof(float));
TPCircularBufferSetAtomic(&newBuffer, false);
TPCircularBufferProduceBytes(&newBuffer, buf, availableBytes);
TPCircularBufferCleanup(&_oldAudio[c]);
memcpy(&_oldAudio[c], &newBuffer, sizeof(TPCircularBuffer));
}
_oldAudioSize = newSize;
pthread_mutex_unlock(&_mutex);
}
ATOMIC_SWAP32(_delaySamples, samples);
}
void crossfeedSetDelayMS(float delayMS)
{
init();
pthread_mutex_lock(&_setMutex);
_delayMS = delayMS;
int samples = (int)(_curSampleRate * delayMS / 1000);
setDelaySamples(samples);
pthread_mutex_unlock(&_setMutex);
}
float crossfeedGetDelayMS()
{
return _delayMS;
}
void crossfeedSetIntensity(float intensity)
{
ATOMIC_SWAP32(_intensity, intensity);
}
float crossfeedGetIntensity()
{
return _intensity;
}
void crossfeedSetEnabled(bool enabled)
{
uint32_t tmp = enabled;
ATOMIC_SWAP32(_enabled, tmp);
}
bool crossfeedGetEnabled()
{
return _enabled;
}
void crossfeedSetCoefs(float **allCoefs, size_t numCoefs)
{
pthread_mutex_lock(&_coefsMutex);
// dealloc old keep buffers
for(int i = 0; i < _numCoefs; i++) {
for(int c = 0; c < NUM_CHANNELS; c++) {
free(_keepIn[i][c]);
free(_keepOut[i][c]);
}
free(_keepIn[i]);
free(_keepOut[i]);
}
free(_keepIn);
free(_keepOut);
// alloc new keep buffers
_keepIn = malloc(numCoefs * sizeof(float **));
_keepOut = malloc(numCoefs * sizeof(float **));
for(int i = 0; i < numCoefs; i++) {
_keepIn[i] = malloc(NUM_CHANNELS * sizeof(float *));
_keepOut[i] = malloc(NUM_CHANNELS * sizeof(float *));
for(int c = 0; c < NUM_CHANNELS; c++) {
_keepIn[i][c] = calloc(2, sizeof(float));
_keepOut[i][c] = calloc(2, sizeof(float));
}
}
_allCoefs = allCoefs;
_numCoefs = numCoefs;
pthread_mutex_unlock(&_coefsMutex);
}
void crossfeedFilter(float **audio, int numSamples, int numChannels, float sampleRate)
{
if(!_enabled || numChannels != NUM_CHANNELS) {
return;
}
init();
float output[numChannels][numSamples];
uint32_t delay = _delaySamples;
float intensity = _intensity;
if(delay == 0) {
for(int c = 0; c < numChannels; c++) {
int swapC = c == 0 ? 1 : 0;
vDSP_vsmul(audio[c], 1, &intensity, output[swapC], 1, numSamples);
}
} else {
// TODO account for changing sampleRate / numSamples
pthread_mutex_lock(&_mutex);
for(int c = 0; c < numChannels; c++) {
TPCircularBuffer *ring = &_oldAudio[c];
uint32_t availableBytes;
float *buf = TPCircularBufferTail(ring, &availableBytes);
uint32_t desiredSize = delay * sizeof(float);
if(availableBytes < desiredSize) {
float tmp[delay];
memset(tmp, 0, sizeof(tmp) - availableBytes);
memcpy(&tmp[delay - availableBytes/sizeof(float)], buf, availableBytes);
TPCircularBufferConsume(ring, availableBytes);
TPCircularBufferProduceBytes(ring, tmp, sizeof(tmp));
} else if(availableBytes > desiredSize) {
TPCircularBufferConsume(ring, availableBytes - delay * sizeof(float));
}
TPCircularBufferProduceBytes(ring, audio[c], numSamples * sizeof(float));
buf = TPCircularBufferTail(ring, &availableBytes);
int swapC = c == 0 ? 1 : 0;
vDSP_vsmul(buf, 1, &intensity, output[swapC], 1, numSamples);
TPCircularBufferConsume(ring, numSamples * sizeof(float));
}
pthread_mutex_unlock(&_mutex);
}
pthread_mutex_lock(&_coefsMutex);
float tInArr[numSamples + 2];
float tOutArr[numSamples + 2];
float *tIn = tInArr;
float *tOut = tOutArr;
for(int c = 0; c < numChannels; c++) {
memcpy(&tIn[2], output[c], numSamples * sizeof(float));
for(int i = 0; i < _numCoefs; i++ ){
memcpy(tIn, _keepIn[i][c], 2 * sizeof(float));
memcpy(tOut, _keepOut[i][c], 2 * sizeof(float));
vDSP_deq22(tIn, 1, _allCoefs[i], tOut, 1, numSamples);
memcpy(_keepIn[i][c], &tIn[numSamples], 2 * sizeof(float));
memcpy(_keepOut[i][c], &tOut[numSamples], 2 * sizeof(float));
float *tmp = tIn;
tIn = tOut;
tOut = tmp;
}
memcpy(output[c], &tIn[2], numSamples * sizeof(float));
}
pthread_mutex_unlock(&_coefsMutex);
for(int c = 0; c < numChannels; c++) {
vDSP_vadd(audio[c], 1, output[c], 1, audio[c], 1, numSamples);
}
}
-- put this into /var/tweak/com.r333d.eqe/lua/autorun/app/
-- load by restarting the EQE app
local str_esc = require 'str_esc'
local page = {}
page.title = 'Crossfeed (beta)'
page.icon = IMG('headphone.png', PAGE_ICON_COLOR):retain()
local function ipc(s, safe)
s = str_esc(s)
if safe then
return IPC('return eqe.raw('..s..')')
else
return IPC('return eqe.raw('..s..', true)')
end
end
local thumbImg = IMG('thumb.png'):retain()
local pad = 11
local function create_slider(y, width, title, m)
local self = {}
self.onchange = function() end
self.onfinish = function() end
self.label = objc.UILabel:alloc():initWithFrame{{pad,y},{width - pad*2,44}}
self.label:setFont(objc.UIFont:fontWithName_size('HelveticaNeue', 16))
self.label:setTextColor(COLOR(0xffffff8d))
self.label:setBackgroundColor(objc.UIColor:clearColor())
self.label:setText(title)
y = y + self.label:frame().size.height
self.slider = objc.EQEOBSlider:alloc():initWithFrame{{pad,y},{width - pad*2,44}}
self.slider:setThumbImage_forState(thumbImg, UIControlStateNormal)
self.slider:setMinimumTrackTintColor(COLOR(0xffffff80))
self.slider:setMaximumTrackTintColor(COLOR(0xffffff50))
local target = ns.target:new()
function target.onaction()
self.onfinish()
end
self.slider:addTarget_action_forControlEvents(target.m, target.sel, bit.bor(UIControlEventTouchUpInside, UIControlEventTouchUpOutside))
local target = ns.target:new()
function target.onaction()
self.onchange()
end
self.slider:addTarget_action_forControlEvents(target.m, target.sel, bit.bor(UIControlEventValueChanged))
local target = ns.target:new()
self.slider:addTarget_action_forControlEvents(target.m, target.sel, UIControlEventValueChanged)
function self.updatetext()
self.label:setText(title..': '..self.slider:value())
end
function target.onaction()
self.updatetext()
end
y = y + self.slider:frame().size.height
m:view():addSubview(self.slider)
m:view():addSubview(self.label)
return self, y
end
local function create_button(x, y, canvaswidth, title, m)
local height = 34
local width = canvaswidth*2/3
local button = ui.button:new()
button.m:setFrame{{x + (canvaswidth-width)/2, y},{width,height}}
button.m:layer():setCornerRadius(8)
button:setFont('HelveticaNeue', 16)
button:setColor(COLOR(0xff, 0xff, 0xff, 0xff*0.7))
button.m:setBackgroundColor(COLOR(0xff, 0xff, 0xff, 0xff*0.07))
button:setTitle(title)
m:view():addSubview(button.m)
return button, y + height
end
local function create_switch(y, canvaswidth, title, m)
local target = ns.target:new()
target.switch = objc.UISwitch:alloc():init()
local s = target.switch:frame().size
local x = canvaswidth - s.width - pad*3
target.switch:setFrame{{x, y},s}
target.switch:setOnTintColor(COLOR(0x4bc2ffaa))
target.switch:setTintColor(COLOR(0xffffff55))
target.switch:addTarget_action_forControlEvents(target.m, target.sel, UIControlEventValueChanged)
target.label = objc.UILabel:alloc():init()
target.label:setFont(objc.UIFont:fontWithName_size('HelveticaNeue', 16))
target.label:setTextColor(COLOR(0xffffff8d))
target.label:setBackgroundColor(objc.UIColor:clearColor())
target.label:setText(title)
target.label:sizeToFit()
local switchS = s
local s = target.label:frame().size
target.label:setFrame{{x - s.width - pad, y + (switchS.height - s.height)/2},s}
m:view():addSubview(target.label)
m:view():addSubview(target.switch)
return target, y + target.switch:frame().size.height
end
function page:init()
local vc = VIEWCONTROLLER(function(m)
local frame = m:view():frame()
local width = frame.size.width
local intensity, y = create_slider(64, width, 'Intensity', m)
intensity.slider:setMinimumValue(0)
intensity.slider:setMaximumValue(1)
function intensity.onchange()
ipc('crossfeed.intensity('..intensity.slider:value()..')')
end
function intensity.onfinish()
intensity.onchange()
ipc('crossfeed.save()')
end
self.intensity = intensity
local delay, y = create_slider(y, width, 'Delay (ms)', m)
delay.slider:setMinimumValue(0)
delay.slider:setMaximumValue(4)
function delay.onchange()
ipc('crossfeed.delay('..delay.slider:value()..')')
end
function delay.onfinish()
delay.onchange()
ipc('crossfeed.save()')
end
self.delay = delay
--[[
y = y + pad*2
self.save = create_button(0, y, width/2, 'Save', m)
local load, y = create_button(width/2, y, width/2, 'Load', m)
self.load = load
function self.save.ontoggle()
ipc('crossfeed.save()')
end
function self.load.ontoggle()
ipc('crossfeed.load()')
self:refresh()
end
]]
y = y + pad*4
local enable, y = create_switch(y, width, 'Enabled', m)
function enable.onaction()
local on = objc.weirdbool(enable.switch:isOn())
ipc('crossfeed.enabled('..tostring(on)..')')
end
self.enable = enable
end)
self.view = vc:view()
end
function page:refresh()
local intensity = tonumber(ipc('return crossfeed.intensity()'))
local delay = tonumber(ipc('return crossfeed.delay()'))
local enabled = ipc('return crossfeed.enabled()') == 'true'
self.intensity.slider:setValue(intensity)
self.intensity.updatetext()
self.delay.slider:setValue(delay)
self.delay.updatetext()
self.enable.switch:setOn(enabled)
end
function page:hide(hiding)
if not hiding then
self:refresh()
end
end
Page.crossfeed = page
ADD_NAV_PAGE(page)
-- put this into /var/tweak/com.r333d.eqe/lua/autorun/raw/
-- load by either restarting mediaserverd or running this command:
-- eqe raw < /var/tweak/com.r333d.eqe/lua/autorun/raw/crossfeed.lua
package.path = '/var/tweak/com.r333d.eqe/lua/autorun/raw/crossfeed/?.lua;'
..'/var/tweak/com.r333d.eqe/lua/core/?.lua;'
..'/var/tweak/com.r333d.eqe/lua/core/?/init.lua;'
..package.path
local ffi = require 'ffi'
lib = ffi.load(LIB_PATH..'/com.r333d.eqe.plugin.crossfeed.dylib')
-- DSP Def
ffi.cdef[[
void crossfeedSetDelayMS(float delayMS);
float crossfeedGetDelayMS();
void crossfeedSetIntensity(float intensity);
float crossfeedGetIntensity();
void crossfeedSetEnabled(bool enabled);
bool crossfeedGetEnabled();
void crossfeedFilter(float **audio, int numSamples, int numChannels, float sampleRate);
void crossfeedSetCoefs(float **allCoefs, size_t numCoefs);
]]
local prefspath = '/var/tweak/com.r333d.eqe/db/crossfeed.lua'
_G.crossfeed = {}
crossfeed.filters = {
{
type = 'lowpass',
Q = 0.048578,
frequency = 9604.148,
},
}
local function initFilters()
crossfeed.allcoefficients = ffi.new('float *[?]', #crossfeed.filters)
for i=1, #crossfeed.filters do
local v = crossfeed.filters[i]
local f = require('filter.'..v.type):new()
f.type = v.type
f.Q = v.Q
f.frequency = v.frequency
f.gain = v.gain
f.coefficients = ffi.new('float[5]', f:get_coefs(44100))
f.keepIn = ffi.new('float[2][2]')
f.keepOut = ffi.new('float[2][2]')
-- overwrite the spec
crossfeed.filters[i] = f
crossfeed.allcoefficients[i-1] = f.coefficients
end
lib.crossfeedSetCoefs(crossfeed.allcoefficients, #crossfeed.filters)
end
initFilters()
-- Start getter/setter functions
function crossfeed.delay(val)
if val == nil then
return lib.crossfeedGetDelayMS()
else
lib.crossfeedSetDelayMS(val)
end
end
-- The intensity of the crossfeed
function crossfeed.intensity(val)
if val == nil then
return lib.crossfeedGetIntensity()
else
lib.crossfeedSetIntensity(val)
end
end
-- Enable/disable the crossfeed. Does not affect the mono audio
function crossfeed.enabled(val)
if val == nil then
return lib.crossfeedGetEnabled()
else
lib.crossfeedSetEnabled(val)
end
end
function crossfeed.save()
--/var/mobile/Media/DCIM
DAEMON_IPC([[
local path = ']]..prefspath..[['
local f = assert(io.open(path, 'w'))
f:write([=[
return {
enabled = ]]..tostring(crossfeed.enabled())..[[,
delay = ]]..tostring(crossfeed.delay())..[[,
intensity = ]]..tostring(crossfeed.intensity())..[[,
}
]=])
f:close()
os.execute('chown mobile:mobile '..path)
]])
end
function crossfeed.load()
local success, prefs = pcall(dofile, prefspath)
if not success then
crossfeed.save()
return crossfeed.load()
end
crossfeed.enabled(prefs.enabled)
crossfeed.delay(prefs.delay)
crossfeed.intensity(prefs.intensity)
return true
end
crossfeed.enabled(false)
crossfeed.delay(0.3)
crossfeed.intensity(0.4)
crossfeed.load()
raw.c.crossfeed = lib.crossfeedFilter
//
// TPCircularBuffer.c
// Circular/Ring buffer implementation
//
// https://github.com/michaeltyson/TPCircularBuffer
//
// Created by Michael Tyson on 10/12/2011.
//
// Copyright (C) 2012-2013 A Tasty Pixel
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
#include "TPCircularBuffer.h"
#include <mach/mach.h>
#include <stdio.h>
#include <stdlib.h>
#define reportResult(result,operation) (_reportResult((result),(operation),strrchr(__FILE__, '/')+1,__LINE__))
static inline bool _reportResult(kern_return_t result, const char *operation, const char* file, int line) {
if ( result != ERR_SUCCESS ) {
printf("%s:%d: %s: %s\n", file, line, operation, mach_error_string(result));
return false;
}
return true;
}
bool _TPCircularBufferInit(TPCircularBuffer *buffer, uint32_t length, size_t structSize) {
assert(length > 0);
if ( structSize != sizeof(TPCircularBuffer) ) {
fprintf(stderr, "TPCircularBuffer: Header version mismatch. Check for old versions of TPCircularBuffer in your project\n");
abort();
}
// Keep trying until we get our buffer, needed to handle race conditions
int retries = 3;
while ( true ) {
buffer->length = (uint32_t)round_page(length); // We need whole page sizes
// Temporarily allocate twice the length, so we have the contiguous address space to
// support a second instance of the buffer directly after
vm_address_t bufferAddress;
kern_return_t result = vm_allocate(mach_task_self(),
&bufferAddress,
buffer->length * 2,
VM_FLAGS_ANYWHERE); // allocate anywhere it'll fit
if ( result != ERR_SUCCESS ) {
if ( retries-- == 0 ) {
reportResult(result, "Buffer allocation");
return false;
}
// Try again if we fail
continue;
}
// Now replace the second half of the allocation with a virtual copy of the first half. Deallocate the second half...
result = vm_deallocate(mach_task_self(),
bufferAddress + buffer->length,
buffer->length);
if ( result != ERR_SUCCESS ) {
if ( retries-- == 0 ) {
reportResult(result, "Buffer deallocation");
return false;
}
// If this fails somehow, deallocate the whole region and try again
vm_deallocate(mach_task_self(), bufferAddress, buffer->length);
continue;
}
// Re-map the buffer to the address space immediately after the buffer
vm_address_t virtualAddress = bufferAddress + buffer->length;
vm_prot_t cur_prot, max_prot;
result = vm_remap(mach_task_self(),
&virtualAddress, // mirror target
buffer->length, // size of mirror
0, // auto alignment
0, // force remapping to virtualAddress
mach_task_self(), // same task
bufferAddress, // mirror source
0, // MAP READ-WRITE, NOT COPY
&cur_prot, // unused protection struct
&max_prot, // unused protection struct
VM_INHERIT_DEFAULT);
if ( result != ERR_SUCCESS ) {
if ( retries-- == 0 ) {
reportResult(result, "Remap buffer memory");
return false;
}
// If this remap failed, we hit a race condition, so deallocate and try again
vm_deallocate(mach_task_self(), bufferAddress, buffer->length);
continue;
}
if ( virtualAddress != bufferAddress+buffer->length ) {
// If the memory is not contiguous, clean up both allocated buffers and try again
if ( retries-- == 0 ) {
printf("Couldn't map buffer memory to end of buffer\n");
return false;
}
vm_deallocate(mach_task_self(), virtualAddress, buffer->length);
vm_deallocate(mach_task_self(), bufferAddress, buffer->length);
continue;
}
buffer->buffer = (void*)bufferAddress;
buffer->fillCount = 0;
buffer->head = buffer->tail = 0;
buffer->atomic = true;
return true;
}
return false;
}
void TPCircularBufferCleanup(TPCircularBuffer *buffer) {
vm_deallocate(mach_task_self(), (vm_address_t)buffer->buffer, buffer->length * 2);
memset(buffer, 0, sizeof(TPCircularBuffer));
}
void TPCircularBufferClear(TPCircularBuffer *buffer) {
uint32_t fillCount;
if ( TPCircularBufferTail(buffer, &fillCount) ) {
TPCircularBufferConsume(buffer, fillCount);
}
}
void TPCircularBufferSetAtomic(TPCircularBuffer *buffer, bool atomic) {
buffer->atomic = atomic;
}
//
// TPCircularBuffer.h
// Circular/Ring buffer implementation
//
// https://github.com/michaeltyson/TPCircularBuffer
//
// Created by Michael Tyson on 10/12/2011.
//
//
// This implementation makes use of a virtual memory mapping technique that inserts a virtual copy
// of the buffer memory directly after the buffer's end, negating the need for any buffer wrap-around
// logic. Clients can simply use the returned memory address as if it were contiguous space.
//
// The implementation is thread-safe in the case of a single producer and single consumer.
//
// Virtual memory technique originally proposed by Philip Howard (http://vrb.slashusr.org/), and
// adapted to Darwin by Kurt Revis (http://www.snoize.com,
// http://www.snoize.com/Code/PlayBufferedSoundFile.tar.gz)
//
//
// Copyright (C) 2012-2013 A Tasty Pixel
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
#ifndef TPCircularBuffer_h
#define TPCircularBuffer_h
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#ifdef __cplusplus
extern "C++" {
#include <atomic>
typedef std::atomic_int atomicInt;
#define atomicFetchAdd(a,b) std::atomic_fetch_add(a,b)
}
#else
#include <stdatomic.h>
typedef atomic_int atomicInt;
#define atomicFetchAdd(a,b) atomic_fetch_add(a,b)
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
void *buffer;
uint32_t length;
uint32_t tail;
uint32_t head;
volatile atomicInt fillCount;
bool atomic;
} TPCircularBuffer;
/*!
* Initialise buffer
*
* Note that the length is advisory only: Because of the way the
* memory mirroring technique works, the true buffer length will
* be multiples of the device page size (e.g. 4096 bytes)
*
* If you intend to use the AudioBufferList utilities, you should
* always allocate a bit more space than you need for pure audio
* data, so there's room for the metadata. How much extra is required
* depends on how many AudioBufferList structures are used, which is
* a function of how many audio frames each buffer holds. A good rule
* of thumb is to add 15%, or at least another 2048 bytes or so.
*
* @param buffer Circular buffer
* @param length Length of buffer
*/
#define TPCircularBufferInit(buffer, length) \
_TPCircularBufferInit(buffer, length, sizeof(*buffer))
bool _TPCircularBufferInit(TPCircularBuffer *buffer, uint32_t length, size_t structSize);
/*!
* Cleanup buffer
*
* Releases buffer resources.
*/
void TPCircularBufferCleanup(TPCircularBuffer *buffer);
/*!
* Clear buffer
*
* Resets buffer to original, empty state.
*
* This is safe for use by consumer while producer is accessing
* buffer.
*/
void TPCircularBufferClear(TPCircularBuffer *buffer);
/*!
* Set the atomicity
*
* If you set the atomiticy to false using this method, the buffer will
* not use atomic operations. This can be used to give the compiler a little
* more optimisation opportunities when the buffer is only used on one thread.
*
* Important note: Only set this to false if you know what you're doing!
*
* The default value is true (the buffer will use atomic operations)
*
* @param buffer Circular buffer
* @param atomic Whether the buffer is atomic (default true)
*/
void TPCircularBufferSetAtomic(TPCircularBuffer *buffer, bool atomic);
// Reading (consuming)
/*!
* Access end of buffer
*
* This gives you a pointer to the end of the buffer, ready
* for reading, and the number of available bytes to read.
*
* @param buffer Circular buffer
* @param availableBytes On output, the number of bytes ready for reading
* @return Pointer to the first bytes ready for reading, or NULL if buffer is empty
*/
static __inline__ __attribute__((always_inline)) void* TPCircularBufferTail(TPCircularBuffer *buffer, uint32_t* availableBytes) {
*availableBytes = buffer->fillCount;
if ( *availableBytes == 0 ) return NULL;
return (void*)((char*)buffer->buffer + buffer->tail);
}
/*!
* Consume bytes in buffer
*
* This frees up the just-read bytes, ready for writing again.
*
* @param buffer Circular buffer
* @param amount Number of bytes to consume
*/
static __inline__ __attribute__((always_inline)) void TPCircularBufferConsume(TPCircularBuffer *buffer, uint32_t amount) {
buffer->tail = (buffer->tail + amount) % buffer->length;
if ( buffer->atomic ) {
atomicFetchAdd(&buffer->fillCount, -amount);
} else {
buffer->fillCount -= amount;
}
assert(buffer->fillCount >= 0);
}
/*!
* Access front of buffer
*
* This gives you a pointer to the front of the buffer, ready
* for writing, and the number of available bytes to write.
*
* @param buffer Circular buffer
* @param availableBytes On output, the number of bytes ready for writing
* @return Pointer to the first bytes ready for writing, or NULL if buffer is full
*/
static __inline__ __attribute__((always_inline)) void* TPCircularBufferHead(TPCircularBuffer *buffer, uint32_t* availableBytes) {
*availableBytes = (buffer->length - buffer->fillCount);
if ( *availableBytes == 0 ) return NULL;
return (void*)((char*)buffer->buffer + buffer->head);
}
// Writing (producing)
/*!
* Produce bytes in buffer
*
* This marks the given section of the buffer ready for reading.
*
* @param buffer Circular buffer
* @param amount Number of bytes to produce
*/
static __inline__ __attribute__((always_inline)) void TPCircularBufferProduce(TPCircularBuffer *buffer, uint32_t amount) {
buffer->head = (buffer->head + amount) % buffer->length;
if ( buffer->atomic ) {
atomicFetchAdd(&buffer->fillCount, amount);
} else {
buffer->fillCount += amount;
}
assert(buffer->fillCount <= buffer->length);
}
/*!
* Helper routine to copy bytes to buffer
*
* This copies the given bytes to the buffer, and marks them ready for reading.
*
* @param buffer Circular buffer
* @param src Source buffer
* @param len Number of bytes in source buffer
* @return true if bytes copied, false if there was insufficient space
*/
static __inline__ __attribute__((always_inline)) bool TPCircularBufferProduceBytes(TPCircularBuffer *buffer, const void* src, uint32_t len) {
uint32_t space;
void *ptr = TPCircularBufferHead(buffer, &space);
if ( space < len ) return false;
memcpy(ptr, src, len);
TPCircularBufferProduce(buffer, len);
return true;
}
/*!
* Deprecated method
*/
static __inline__ __attribute__((always_inline)) __deprecated_msg("use TPCircularBufferSetAtomic(false) and TPCircularBufferConsume instead")
void TPCircularBufferConsumeNoBarrier(TPCircularBuffer *buffer, uint32_t amount) {
buffer->tail = (buffer->tail + amount) % buffer->length;
buffer->fillCount -= amount;
assert(buffer->fillCount >= 0);
}
/*!
* Deprecated method
*/
static __inline__ __attribute__((always_inline)) __deprecated_msg("use TPCircularBufferSetAtomic(false) and TPCircularBufferProduce instead")
void TPCircularBufferProduceNoBarrier(TPCircularBuffer *buffer, uint32_t amount) {
buffer->head = (buffer->head + amount) % buffer->length;
buffer->fillCount += amount;
assert(buffer->fillCount <= buffer->length);
}
#ifdef __cplusplus
}
#endif
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment