Last active
March 29, 2018 02:11
-
-
Save rweichler/ecdc3ca4cc372306f458a31dbd3928aa to your computer and use it in GitHub Desktop.
crossfeed v2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // 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; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // 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