diff --git a/Makefile b/Makefile index 44daeb6..7fd70ed 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -20,4 +25,3 @@ ringbuffer: $(OBJ) clean: rm -f *.o rm -f `find . -perm /111 -type f` - diff --git a/ringbuffer.cpp b/ringbuffer.cpp index b301e4a..6b94657 100644 --- a/ringbuffer.cpp +++ b/ringbuffer.cpp @@ -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 #include "ringbuffer.h" #include -#include // memcpy +#include + +// 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 +RingBuffer::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 +RingBuffer::~RingBuffer() { - delete [] buffer; -} // ~RingBuffer() + delete[] buffer; +} -unsigned long RingBuffer::items_available_for_write() +template +auto RingBuffer::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 +auto RingBuffer::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 +auto RingBuffer::pushMayBlock(bool block) -> void { - this->blockingPush=block; -} // pushMayBlock() + blockingPush = block; +} -void RingBuffer::popMayBlock(bool block) +template +auto RingBuffer::popMayBlock(bool block) -> void { - this->blockingPop=block; -} // popMayBlock() + blockingPop = block; +} -void RingBuffer::setBlockingNap(unsigned long blockingNap) +template +auto RingBuffer::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 +auto RingBuffer::push(FloatType* data, const uint64 numSamples) -> uint64 { - unsigned long space=size; - - if(blockingPush){ - while((space=items_available_for_write())(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 +auto RingBuffer::pop(FloatType* data, const uint64 numSamples) -> uint64 { - unsigned long space=size; - - if(blockingPop){ - while((space=items_available_for_read())(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 +auto RingBuffer::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; +template class RingBuffer; +template class RingBuffer; diff --git a/ringbuffer.h b/ringbuffer.h index e8bc8cc..cc118ae 100644 --- a/ringbuffer.h +++ b/ringbuffer.h @@ -1,32 +1,44 @@ -/* - * ringbuffer.h - */ + +//ringbuffer.h #include #include + +template 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 tail; // write pointer - std::atomic 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 tail; // write index + std::atomic head; // read index + + static constexpr uint64 itemSize { sizeof(FloatType) }; + + const std::string name; + + bool blockingPush; + bool blockingPop; + + uint64 blockingNap { 500 }; +}; diff --git a/ringbuffer_main.cpp b/ringbuffer_main.cpp index 2ea0d1e..a449e57 100644 --- a/ringbuffer_main.cpp +++ b/ringbuffer_main.cpp @@ -1,58 +1,80 @@ #include #include "ringbuffer.h" +//this makes the whole thing need C++17, but the RingBuffer class +//on its own should also be able to work with C++14 +template +auto print(Args... args){ + ([](auto&& arg){ std::cout << arg << ' '; }(args), ...); + std::cout << '\n'; +} -int main() +auto main() -> int { -RingBuffer buffer(10,"Buffer"); -float inputdata[8]={1,2,3,4,5,6,7,8}; -float anadata[8]; - - if(buffer.isLockFree()) std::cout << "Lock free\n"; - else std::cout << "Not lock free\n"; - - std::cout << "Avail for write: " << buffer.items_available_for_write() << std::endl; - std::cout << "Avail for read: " << buffer.items_available_for_read() << std::endl; - buffer.push(inputdata,1); - buffer.push(inputdata,5); - buffer.pop(anadata,5); - std::cout << "Avail for write: " << buffer.items_available_for_write() << std::endl; - std::cout << "Avail for read: " << buffer.items_available_for_read() << std::endl; - - buffer.push(inputdata,5); - std::cout << "Avail for write: " << buffer.items_available_for_write() << std::endl; - std::cout << "Avail for read: " << buffer.items_available_for_read() << std::endl; - buffer.push(inputdata,1); - std::cout << "Avail for write: " << buffer.items_available_for_write() << std::endl; - std::cout << "Avail for read: " << buffer.items_available_for_read() << std::endl; - buffer.push(inputdata,1); - std::cout << "Avail for write: " << buffer.items_available_for_write() << std::endl; - std::cout << "Avail for read: " << buffer.items_available_for_read() << std::endl; - - if(buffer.items_available_for_read() >= 5){ - buffer.pop(anadata,5); - std::cout << "Avail for write: " << buffer.items_available_for_write() << std::endl; - std::cout << "Avail for read: " << buffer.items_available_for_read() << std::endl; - for(unsigned long i=0; i<5; i++) std::cout << anadata[i] << " "; - } - else std::cout << "Not enough data" << std::endl; - - buffer.push(inputdata,1); - std::cout << "Avail for write: " << buffer.items_available_for_write() << std::endl; - std::cout << "Avail for read: " << buffer.items_available_for_read() << std::endl; - buffer.push(inputdata,1); - std::cout << "Avail for write: " << buffer.items_available_for_write() << std::endl; - std::cout << "Avail for read: " << buffer.items_available_for_read() << std::endl; - - if(buffer.items_available_for_read() >= 5){ - buffer.pop(anadata,5); - std::cout << "Avail for write: " << buffer.items_available_for_write() << std::endl; - std::cout << "Avail for read: " << buffer.items_available_for_read() << std::endl; - for(unsigned long i=0; i<5; i++) std::cout << anadata[i] << " "; - } - else std::cout << "Not enough data" << std::endl; - - std::cout << std::endl; - return 0; -} + //could also be RingBuffer or RingBuffer for more precision, + //To test this, change the using FloatType to double or long double + using FloatType = float; + + RingBuffer buffer { 10, "Buffer" }; + + FloatType inputdata[] = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0 }; + FloatType anadata[8]; + + buffer.isLockFree() ? print("Lock free") : print("Not lock free"); + + print("Available for write:", buffer.numItemsAvailableForWrite()); + print("Available for read:", buffer.numItemsAvailableForRead()); + + buffer.push(inputdata, 1); + buffer.push(inputdata, 5); + buffer.pop(anadata, 5); + + print("Available for write:", buffer.numItemsAvailableForWrite()); + print("Available for read:", buffer.numItemsAvailableForRead()); + + buffer.push(inputdata, 5); + + print("Available for write:", buffer.numItemsAvailableForWrite()); + print("Available for read:", buffer.numItemsAvailableForRead()); + + buffer.push(inputdata, 1); + print("Available for write:", buffer.numItemsAvailableForWrite()); + print("Available for read:", buffer.numItemsAvailableForRead()); + + buffer.push(inputdata, 1); + + print("Available for write:", buffer.numItemsAvailableForWrite()); + print("Available for read:", buffer.numItemsAvailableForRead()); + + if(buffer.numItemsAvailableForRead() >= 5) + { + buffer.pop(anadata, 5); + print("Available for write:", buffer.numItemsAvailableForWrite()); + print("Available for read:", buffer.numItemsAvailableForRead()); + for(int i = 0; i < 5; ++i) print("Anadata: i =", i, ":", anadata[i]); + } + else print("Not enough data"); + + buffer.push(inputdata, 1); + + print("Available for write:", buffer.numItemsAvailableForWrite()); + print("Available for read:", buffer.numItemsAvailableForRead()); + + buffer.push(inputdata, 1); + + print("Available for write:", buffer.numItemsAvailableForWrite()); + print("Available for read:", buffer.numItemsAvailableForRead()); + + if(buffer.numItemsAvailableForRead() >= 5) + { + buffer.pop(anadata, 5); + print("Available for write:", buffer.numItemsAvailableForWrite()); + print("Available for read:", buffer.numItemsAvailableForRead()); + for(int i = 0; i < 5; ++i) print("Anadata: i =", i, ":", anadata[i]); + } + else print("Not enough data\n"); + + + return 0; +}