Michael J Petrie

Writing a lock-free ring buffer for the audio thread

Why the audio callback can never wait, and the 60 lines of C that keep it fed.

This is a placeholder post, here only to show how the site behaves with more writing in it. It will be removed. The rest of the text is filler, written to look like a real post so the layout has something realistic to hold.

The problem

Real-time audio has one rule above all the others: the callback must finish on time, every time. Miss the deadline and the listener hears it immediately, as a click or a dropout. That rule shapes everything else, from how memory is allocated to how threads talk to each other.

typedef struct {
    float *data;
    _Atomic uint32_t read, write;
    uint32_t mask;
} Ring;

static bool ring_push(Ring *r, float v) {
    uint32_t w = atomic_load_explicit(&r->write, memory_order_relaxed);
    if (w - atomic_load_explicit(&r->read, memory_order_acquire) > r->mask) return false;
    r->data[w & r->mask] = v;
    atomic_store_explicit(&r->write, w + 1, memory_order_release);
    return true;
}

What I learned

Graphics has a softer deadline but a wider surface. A frame that arrives late is a stutter rather than a click, but there are far more ways to be late: too many draw calls, too much overdraw, a texture upload at the wrong moment, or a shader that is quietly doing far more work than it needs to.

The interesting part is always the same. You start with a clean idea, you meet the hardware, and the idea changes shape. What survives is usually simpler than what you started with, and faster for it.