Skip to content

Reduce RAM usage and audio latency with smaller CircularBuffer size #281

@Meebleeps

Description

@Meebleeps

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementoptimizationFor anything improving the performances without changing the actual behavior of the library

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions