A lock-free single-producer, single-consumer ring of interleaved stereo frames.
More...
|
| | FrameRing (std::size_t frames) |
| | Creates a ring holding at least this many frames, rounded up to a power of two.
|
| std::size_t | capacity () const noexcept |
| | Frames the ring can hold.
|
| std::size_t | queued () const noexcept |
| | Frames currently queued, as the producer sees it.
|
| std::size_t | writable () const noexcept |
| | Frames the producer can write right now.
|
| void | write (std::span< const float > interleaved) noexcept |
| | Writes interleaved stereo frames. The caller must not exceed writable.
|
| void | request_flush () noexcept |
| | Asks the consumer to drop everything queued.
|
| bool | flush_pending () const noexcept |
| | Whether a requested flush has yet to be performed by the consumer.
|
| void | set_starvation_expected (bool expected) noexcept |
| | Whether the consumer should treat an empty ring as intended rather than as an underrun.
|
| std::size_t | read (std::span< float > interleaved) noexcept |
| | Fills interleaved stereo frames, zero-padding whatever the ring could not supply.
|
| std::int64_t | underruns () const noexcept |
| | How many times the consumer has come up short.
|
| std::pair< float, float > | peak () const noexcept |
| | Peak absolute level of the last block the consumer handed to the device.
|
A lock-free single-producer, single-consumer ring of interleaved stereo frames.
Not part of the engine, and not in the reference build: this is the piece a host needs. The engine renders whole blocks on whatever thread drives it, and every real-time host – the terminal player here, a plugin later – has to get those blocks to a callback it does not own. It lives in the library rather than beside the player because that problem is the host's, not the player's, and because here it is covered by the sanitiser suite.
The producing thread renders; the consuming thread is the audio callback. Nothing here allocates, locks or blocks after construction, which is the whole point: the callback runs on a real-time thread where a page fault or a contended mutex is an audible glitch.
The producer owns write_ and the consumer owns read_; each only ever increases its own counter, so no compare-and-swap is needed. Counters are 64-bit frame totals rather than wrapped indices, so write_ - read_ is the fill level directly and there is no ambiguity between full and empty.