-
Notifications
You must be signed in to change notification settings - Fork 197
Description
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