Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tweaked RingBuffer to be generic #1

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# Makefile for ringbuffer
#
CPP = g++ --std=c++11
CFLAGS = -Wall
LDFLAGS= -lpthread -ljack
# Makefile for RingBuffer


ifeq ($(OS),Linux)
LDFLAGS= -lpthread -ljack
CPP = g++ --std=c++17
else
CPP = clang++ --std=c++17
endif

CFLAGS = -Wall

OBJ = ringbuffer.o ringbuffer_main.o

Expand All @@ -20,4 +25,3 @@ ringbuffer: $(OBJ)
clean:
rm -f *.o
rm -f `find . -perm /111 -type f`

235 changes: 124 additions & 111 deletions ringbuffer.cpp
Original file line number Diff line number Diff line change
@@ -1,142 +1,155 @@
/*
* Block-free ringbuffer for synchronisation of producer and consumer threads.
*
* Audio callback delivers data, the other thread reads data.
*
* Size must be a multiple of the real-time buffer size (e.g. JACK buffer).
* For the non-realtime thread this is not an issue as long as it's large
* enough to hold at least two consumer frames (theoretical minimum)
*
* Caveats: if consumer threads waits too long, producer can overrun the
* buffer. This may not be a problem, for this we have resync() that puts
* the consumer readpointer right on top of the consumer writepointer.
*/


#include <iostream>
#include "ringbuffer.h"
#include <unistd.h>
#include <string.h> // memcpy
#include <string.h>


// Size is specified as #items, not bytes.

/*
* Size is specified as #items, not bytes. Item type is now float and will
* eventually be set in template form
*/
RingBuffer::RingBuffer(unsigned long size,std::string name)
template<typename FloatType>
RingBuffer<FloatType>::RingBuffer(const uint64 size, const std::string& name) :
size(size),
buffer(new FloatType[size]),
tail(0),
head(0),
name(name),
blockingPush(false),
blockingPop(false)
{
tail=0;
head=0;
this->size=size;
itemsize=sizeof(float);
buffer = new float [size]; // allocate storage
this->name=name;
blockingPush=false;
blockingPop=false;
} // RingBuffer()


RingBuffer::~RingBuffer()

}


template<typename FloatType>
RingBuffer<FloatType>::~RingBuffer()
{
delete [] buffer;
} // ~RingBuffer()
delete[] buffer;
}


unsigned long RingBuffer::items_available_for_write()
template<typename FloatType>
auto RingBuffer<FloatType>::numItemsAvailableForWrite() const -> uint64
{
long pointerspace=head.load()-tail.load(); // signed
// signed space between head and tail index
const long pointerSpace = head.load() - tail.load();

if(pointerspace > 0) return pointerspace; // NB: > 0 so NOT including 0
else return pointerspace+size;
} // items_available_for_write()
// NB: > 0 so NOT including 0
return pointerSpace > 0 ? pointerSpace : pointerSpace + size;
}


unsigned long RingBuffer::items_available_for_read()
template<typename FloatType>
auto RingBuffer<FloatType>::numItemsAvailableForRead() const -> uint64
{
long pointerspace=tail.load()-head.load(); // signed
// signed space between tail and head index
const long pointerSpace = tail.load() - head.load();

if(pointerspace >= 0) return pointerspace; // NB: >= 0 so including 0
else return pointerspace+size;
} // items_available_for_read()
// NB: >= 0 so including 0
return pointerSpace >= 0 ? pointerSpace : pointerSpace + size;
}


void RingBuffer::pushMayBlock(bool block)
template<typename FloatType>
auto RingBuffer<FloatType>::pushMayBlock(bool block) -> void
{
this->blockingPush=block;
} // pushMayBlock()
blockingPush = block;
}


void RingBuffer::popMayBlock(bool block)
template<typename FloatType>
auto RingBuffer<FloatType>::popMayBlock(bool block) -> void
{
this->blockingPop=block;
} // popMayBlock()
blockingPop = block;
}


void RingBuffer::setBlockingNap(unsigned long blockingNap)
template<typename FloatType>
auto RingBuffer<FloatType>::setBlockingNap(const uint64 newBlockingNap) -> void
{
this->blockingNap=blockingNap;
} // setBlockingNap()
blockingNap = newBlockingNap;
}


// Try to write as many items as possible and return the number actually written

/*
* Try to write as many items as possible and return the number actually written
*/
unsigned long RingBuffer::push(float *data,unsigned long n)
template<typename FloatType>
auto RingBuffer<FloatType>::push(FloatType* data, const uint64 numSamples) -> uint64
{
unsigned long space=size;

if(blockingPush){
while((space=items_available_for_write())<n){ // blocking
usleep(blockingNap);
} // while
} // if
if(space==0) return 0;
unsigned long n_to_write = n<=space ? n : space; // limit

const auto current_tail = tail.load();
if(current_tail + n_to_write <= size){ // chunk fits without wrapping
memcpy(buffer+current_tail,data,n_to_write*itemsize);
}
else {
unsigned long first_chunk=size-current_tail;
memcpy(buffer+current_tail,data,first_chunk*itemsize);
memcpy(buffer,data+first_chunk,(n_to_write-first_chunk)*itemsize);
}
tail.store((current_tail+n_to_write)%size);
return n_to_write;
} // push()


/*
* Try to read as many items as possible and return the number actually read
*/
unsigned long RingBuffer::pop(float *data,unsigned long n)
auto space = size;

if(blockingPush)
while((space = numItemsAvailableForWrite()) < numSamples)
usleep(static_cast<useconds_t>(blockingNap));

if(space == 0) return 0;

const auto numToWrite = numSamples <= space ? numSamples : space;

const auto currentTail = tail.load();

// wrap if needed
if(currentTail + numToWrite <= size)
{
memcpy(buffer + currentTail, data, numToWrite * itemSize);
}
else
{
const auto firstChunk = size - currentTail;
memcpy(buffer + currentTail, data, firstChunk * itemSize);
memcpy(buffer, data + firstChunk, (numToWrite - firstChunk) * itemSize);
}

tail.store((currentTail + numToWrite) % size);

return numToWrite;
}



// Try to read as many items as possible and return the number actually read

template<typename FloatType>
auto RingBuffer<FloatType>::pop(FloatType* data, const uint64 numSamples) -> uint64
{
unsigned long space=size;

if(blockingPop){
while((space=items_available_for_read())<n){ // blocking
usleep(blockingNap);
} // while
} // if
if(space==0) return 0;
unsigned long n_to_read = n<=space ? n : space; // limit

const auto current_head = head.load();
if(current_head + n_to_read <= size){ // no wrapping necessary
memcpy(data,buffer+current_head,n_to_read*itemsize);
}
else {
unsigned long first_chunk=size-current_head;
memcpy(data,buffer+current_head,first_chunk*itemsize);
memcpy(data+first_chunk,buffer,(n_to_read-first_chunk)*itemsize);
}
head.store((current_head+n_to_read)%size); // zo ongeveer
return n_to_read;
} // pop()


bool RingBuffer::isLockFree()
auto space = size;

if(blockingPop)
while((space = numItemsAvailableForRead()) < numSamples)
usleep(static_cast<useconds_t>(blockingNap));

if(space == 0) return 0;

const auto numToRead = numSamples <= space ? numSamples : space;

const auto currentHead = head.load();

//wrap if needed
if(currentHead + numToRead <= size)
{
memcpy(data, buffer + currentHead, numToRead * itemSize);
}
else
{
const auto firstChunk = size - currentHead;
memcpy(data, buffer + currentHead, firstChunk * itemSize);
memcpy(data + firstChunk, buffer, (numToRead - firstChunk) * itemSize);
}

head.store((currentHead + numToRead) % size);

return numToRead;
}


template<typename FloatType>
auto RingBuffer<FloatType>::isLockFree() const -> bool
{
return (tail.is_lock_free() && head.is_lock_free());
} // isLockFree()
return (tail.is_lock_free() && head.is_lock_free());
}

//three available types as of now...
template class RingBuffer<float>;
template class RingBuffer<double>;
template class RingBuffer<long double>;
58 changes: 35 additions & 23 deletions ringbuffer.h
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
/*
* ringbuffer.h
*/

//ringbuffer.h

#include <atomic>
#include <string>


template<typename FloatType>
class RingBuffer
{
public:
RingBuffer(unsigned long size,std::string name);
~RingBuffer();
unsigned long push(float *data,unsigned long n);
unsigned long pop(float *data,unsigned long n);
unsigned long items_available_for_write();
unsigned long items_available_for_read();
bool isLockFree();
void pushMayBlock(bool block);
void popMayBlock(bool block);
void setBlockingNap(unsigned long blockingNap);

using uint64 = unsigned long;

RingBuffer(uint64 size, const std::string& name);
~RingBuffer();

auto push(FloatType* data, uint64 numSamples) -> uint64;
auto pop(FloatType* data, uint64 numSamples) -> uint64;
auto numItemsAvailableForWrite() const -> uint64;
auto numItemsAvailableForRead() const -> uint64;

auto isLockFree() const -> bool;
auto pushMayBlock(bool block) -> void;
auto popMayBlock(bool block) -> void;
auto setBlockingNap(uint64 blockingNap) -> void;

private:
unsigned long size;
float *buffer;
std::atomic<unsigned long> tail; // write pointer
std::atomic<unsigned long> head; // read pointer
unsigned long itemsize; // also depends on #channels
std::string name;
bool blockingPush;
bool blockingPop;
unsigned long blockingNap=500;
}; // RingBuffer{}

uint64 size;
FloatType* buffer;

std::atomic<uint64> tail; // write index
std::atomic<uint64> head; // read index

static constexpr uint64 itemSize { sizeof(FloatType) };

const std::string name;

bool blockingPush;
bool blockingPop;

uint64 blockingNap { 500 };
};
Loading