I have implemented changes to my local CircularBuffer.h which enable use of smaller sizes than 256, currently works with 256/128/64/32. This reduces memory usage (useful on small memory devices like atmega328p) and can greatly reduce audio latency
Upsides
- With a buffer size of 64 - a saving 192*sizeof(AudioOutput_t) = 384 bytes (when audio samples are 16bit ints) which is 18% of total RAM on an atmega328p!
- More importantly audio latency can be greatly reduced. at 32KHz sample rate each sample is 0.0305ms. a 256 size buffer = 7.8ms latency, which when syncing the audio with an external input (eg a sync pulse or a trigger/gate input) can add up to loose timing. Reducing to 64 bytes reduces latency to <2ms.
- can be implemented at compile time option so developers can decide tradeoffs at compile time
Downside
- Less resilience to buffer under-runs.
- Less efficient read and write code as we can't rely on just wrapping the uint8_t start/end index
- 32 bytes was too small for my implementation. 64 was the sweet spot with minimum latency & RAM usages with zero under-runs
Example
The Wirehead Basilisk and next version of FreaqFM firmware uses Mozzi 1.3 with a buffer size of 64 without any noticeable performance impact or buffer under-runs
Changes
#define CIRCULAR_BUFFER_SIZE 64 //note this has changed in mozzi2.0 - now there is a dedicated precompiler #define for this
...
// for buffer sizes < 256. note this function is used by audioticks() so these changes are required to ensure that audioticks returns the correct value
inline unsigned long count()
{
#if CIRCULAR_BUFFER_SIZE == 256
return (num_buffers_read << 8) + start;
#elif CIRCULAR_BUFFER_SIZE == 128
return (num_buffers_read << 7) + start;
#elif CIRCULAR_BUFFER_SIZE == 64
return (num_buffers_read << 6) + start;
#elif CIRCULAR_BUFFER_SIZE == 32
return (num_buffers_read << 5) + start;
#else
static_assert(false); // non standard buffer size
#endif
}
...
private:
ITEM_TYPE items[CIRCULAR_BUFFER_SIZE];
...
// if circular buffer length is 256, default behaviour can take advantage of 255+1 = 0 8-bit unsigned int for efficient start/end wrapping. this uses the existing code:
#if CIRCULAR_BUFFER_SIZE == 256
inline void cbIncrStart()
{
start++;
if (start == 0) {
s_msb ^= 1;
num_buffers_read++;
}
}
inline void cbIncrEnd()
{
end++;
if (end == 0) e_msb ^= 1;
}
#else // if circular buffer length is != 256, use less efficient version for to manage start/end index buffer index
inline void cbIncrStart()
{
start++;
if (start == CIRCULAR_BUFFER_SIZE)
{
start = 0;
s_msb ^= 1;
num_buffers_read++;
}
}
inline void cbIncrEnd()
{
end++;
if (end == CIRCULAR_BUFFER_SIZE)
{
end = 0;
e_msb ^= 1;
}
}
#endif
I have implemented changes to my local CircularBuffer.h which enable use of smaller sizes than 256, currently works with 256/128/64/32. This reduces memory usage (useful on small memory devices like atmega328p) and can greatly reduce audio latency
Upsides
Downside
Example
The Wirehead Basilisk and next version of FreaqFM firmware uses Mozzi 1.3 with a buffer size of 64 without any noticeable performance impact or buffer under-runs
Changes
#define CIRCULAR_BUFFER_SIZE 64 //note this has changed in mozzi2.0 - now there is a dedicated precompiler #define for this
...
// for buffer sizes < 256. note this function is used by audioticks() so these changes are required to ensure that audioticks returns the correct value
inline unsigned long count()
{
#if CIRCULAR_BUFFER_SIZE == 256
return (num_buffers_read << 8) + start;
#elif CIRCULAR_BUFFER_SIZE == 128
return (num_buffers_read << 7) + start;
#elif CIRCULAR_BUFFER_SIZE == 64
return (num_buffers_read << 6) + start;
#elif CIRCULAR_BUFFER_SIZE == 32
return (num_buffers_read << 5) + start;
#else
static_assert(false); // non standard buffer size
#endif
}
...
private:
ITEM_TYPE items[CIRCULAR_BUFFER_SIZE];
...
// if circular buffer length is 256, default behaviour can take advantage of 255+1 = 0 8-bit unsigned int for efficient start/end wrapping. this uses the existing code:
#if CIRCULAR_BUFFER_SIZE == 256
inline void cbIncrStart()
{
start++;
if (start == 0) {
s_msb ^= 1;
num_buffers_read++;
}
}
inline void cbIncrEnd()
{
end++;
if (end == 0) e_msb ^= 1;
}
#else // if circular buffer length is != 256, use less efficient version for to manage start/end index buffer index
inline void cbIncrStart()
{
start++;
if (start == CIRCULAR_BUFFER_SIZE)
{
start = 0;
s_msb ^= 1;
num_buffers_read++;
}
}
inline void cbIncrEnd()
{
end++;
if (end == CIRCULAR_BUFFER_SIZE)
{
end = 0;
e_msb ^= 1;
}
}
#endif