Programming ⇝ Creating a Ring Buffer

This is an example of a ring buffer class in C++. A ring buffer is one of the basic building blocks of any audio engine.

Since audio streams tend to be far too large to load into memory all at once, they are typically loaded a little at a time. The speed of different computer systems and sound hardware varies considerably and there is no guarantee that a system will need to read the same amount of data every time and at precise intervals. Because of this, there needs to be a way for the sound engine to grab what it needs when it needs it.

That is why we need a ring buffer. A ring buffer is a good solution because it gives you a specific size and length of time that you can work with - you don't have to worry about with allocating, deallocating, and reallocating memory. You have one single buffer that loops around to the beginning once you've reached the end, so as long as you keep track of where you are in the buffer when reading and writing, things will run a lot more smoothly.

This code is not specific to any platform and has been compiled into programs on Linux, MacOS, and Windows. We're assuming you know how to operate a compiler and know what to do with C++ code when you see it.

Here is the header file for our ringbuffer, RingBuffer.h:

class RingBuffer
{
public:
	RingBuffer( int sizeBytes );
	~RingBuffer();
	int Read( unsigned char* dataPtr, int numBytes );
	int Write( unsigned char *dataPtr, int numBytes );
	bool Empty( void );
	int GetSize( );
	int GetWriteAvail( );
	int GetReadAvail( );
private:
	unsigned char * _data;
	int _size;
	int _readPtr;
	int _writePtr;
	int _writeBytesAvail;
};

For this particular buffer, we're setting it up so you can only read as many bytes as you've written.

We have your basic read and write functions, along with data members to keep track of the actual data, size or the data, read position, write position, and how full the buffer is.

One thing we have not done is decide how many bytes there are in an audio frame, and in fact there's nothing specific about this buffer that would require that it be used for audio. We have also not done any locking, so any thread-safety issues will need to be handled by the programmer.

#include "RingBuffer.h"

RingBuffer::RingBuffer( int sizeBytes )
{
	_data = new unsigned char[sizeBytes];
	memset( _data, 0, sizeBytes );
	_size = sizeBytes;
	_readPtr = 0;
	_writePtr = 0;
	_writeBytesAvail = sizeBytes;
}

RingBuffer::~RingBuffer( )
{
	delete[] _data;
}

// Set all data to 0 and flag buffer as empty.
bool RingBuffer::Empty( void )
{
    memset( _data, 0, _size );
    _readPtr = 0;
    _writePtr = 0;
    _writeBytesAvail = _size;
    return true;
}

int RingBuffer::Read( unsigned char *dataPtr, int numBytes )
{
	// If there's nothing to read or no data available, then we can't read anything.
	if( dataPtr == 0 || numBytes <= 0 || _writeBytesAvail == _size )
	{
    		return 0;
	}

	int readBytesAvail = _size - _writeBytesAvail;

	// Cap our read at the number of bytes available to be read.
	if( numBytes > readBytesAvail )
	{
    		numBytes = readBytesAvail;
	}

	// Simultaneously keep track of how many bytes we've read and our position in the outgoing buffer
	if(numBytes > _size - _readPtr)
	{
    		int len = _size-_readPtr;
    		memcpy(dataPtr,_data+_readPtr,len);
    		memcpy(dataPtr+len, _data, numBytes-len);
	}
	else
	{
    		memcpy(dataPtr, _data+_readPtr, numBytes);
	}

	_readPtr = (_readPtr + numBytes) % _size;
	_writeBytesAvail += numBytes;

	return numBytes;
}

// Write to the ring buffer.  Do not overwrite data that has not yet
// been read.
int RingBuffer::Write( unsigned char *dataPtr, int numBytes )
{
	// If there's nothing to write or no room available, we can't write anything.
	if( dataPtr == 0 || numBytes <= 0 || _writeBytesAvail == 0 )
	{
    		return 0;
	}

	// Cap our write at the number of bytes available to be written.
	if( numBytes > _writeBytesAvail )
	{
    		numBytes = _writeBytesAvail;
	}

	// Simultaneously keep track of how many bytes we've written and our position in the incoming buffer
	if(numBytes > _size - _writePtr)
	{
    		int len = _size-_writePtr;
    		memcpy(_data+_writePtr,dataPtr,len);
    		memcpy(_data, dataPtr+len, numBytes-len);
	}
	else
	{
    		memcpy(_data+_writePtr, dataPtr, numBytes);
	}

	_writePtr = (_writePtr + numBytes) % _size;
	_writeBytesAvail -= numBytes;

	return numBytes;
}

int RingBuffer::GetSize( void )
{
	return _size;
}

int RingBuffer::GetWriteAvail( void )
{
	return _writeBytesAvail;
}

int RingBuffer::GetReadAvail( void )
{
	return _size - _writeBytesAvail;
}

As you can see, this code is not very complicated, so if you should want to extend or modify this class it should be easy to do so. For example, you may want to change it so that you don't have to continue to write data in order to read it (for looping sound effects), or you may want to modify it to work in samples rather than bytes (typically a signed short or float data type.)