Tabula Sonora 0.1.0
A native C++20 implementation of the Roland Sound Canvas VA synth voice
Loading...
Searching...
No Matches
ts::FrameRing Class Reference

A lock-free single-producer, single-consumer ring of interleaved stereo frames. More...

#include <tabulasonora/frame_ring.hpp>

Public Member Functions

 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.

Detailed Description

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.

Constructor & Destructor Documentation

◆ FrameRing()

ts::FrameRing::FrameRing ( std::size_t frames)
explicit

Creates a ring holding at least this many frames, rounded up to a power of two.

Member Function Documentation

◆ capacity()

std::size_t ts::FrameRing::capacity ( ) const
inlinenodiscardnoexcept

Frames the ring can hold.

◆ queued()

std::size_t ts::FrameRing::queued ( ) const
inlinenodiscardnoexcept

Frames currently queued, as the producer sees it.

◆ writable()

std::size_t ts::FrameRing::writable ( ) const
inlinenodiscardnoexcept

Frames the producer can write right now.

◆ write()

void ts::FrameRing::write ( std::span< const float > interleaved)
noexcept

Writes interleaved stereo frames. The caller must not exceed writable.

◆ request_flush()

void ts::FrameRing::request_flush ( )
inlinenoexcept

Asks the consumer to drop everything queued.

The producer cannot rewind write_ itself without racing the consumer's reads, so the drop is delegated: this bumps a counter, and the consumer – which owns read_ – performs it. The producer must then stop writing until flush_pending goes false, or the frames it wrote in the meantime would be dropped along with the stale ones.

◆ flush_pending()

bool ts::FrameRing::flush_pending ( ) const
inlinenodiscardnoexcept

Whether a requested flush has yet to be performed by the consumer.

◆ set_starvation_expected()

void ts::FrameRing::set_starvation_expected ( bool expected)
inlinenoexcept

Whether the consumer should treat an empty ring as intended rather than as an underrun.

◆ read()

std::size_t ts::FrameRing::read ( std::span< float > interleaved)
noexcept

Fills interleaved stereo frames, zero-padding whatever the ring could not supply.

Called from the audio callback. Returns the frames actually supplied.

◆ underruns()

std::int64_t ts::FrameRing::underruns ( ) const
inlinenodiscardnoexcept

How many times the consumer has come up short.

Underruns are the one thing a player should never hide: a glitch the listener hears but the display does not mention looks like a fault in the engine.

◆ peak()

std::pair< float, float > ts::FrameRing::peak ( ) const
inlinenodiscardnoexcept

Peak absolute level of the last block the consumer handed to the device.


The documentation for this class was generated from the following file: