// SPDX-License-Identifier: Apache-2.0 #pragma once #include #include #include #include #include // Single producer / single consumer. The SDL callback is a native thread: it must never // allocate, block, log, or call FreeRTOS. Unsigned counters also work across wraparound. class SdlAudioBuffer { static constexpr uint32_t CAPACITY = 16384; // samples; ~171 ms of 48 kHz stereo std::array samples {}; std::atomic read_position { 0 }; std::atomic write_position { 0 }; public: size_t write(const void* source, size_t count) { const uint32_t write = write_position.load(std::memory_order_relaxed); const uint32_t read = read_position.load(std::memory_order_acquire); count = std::min(count, static_cast(CAPACITY - (write - read))); const size_t first = std::min(count, static_cast(CAPACITY - write % CAPACITY)); const auto* bytes = static_cast(source); std::memcpy(samples.data() + write % CAPACITY, bytes, first * sizeof(int16_t)); std::memcpy(samples.data(), bytes + first * sizeof(int16_t), (count - first) * sizeof(int16_t)); write_position.store(write + count, std::memory_order_release); return count; } size_t read(void* destination, size_t count) { const uint32_t read = read_position.load(std::memory_order_relaxed); const uint32_t write = write_position.load(std::memory_order_acquire); count = std::min(count, static_cast(write - read)); const size_t first = std::min(count, static_cast(CAPACITY - read % CAPACITY)); auto* bytes = static_cast(destination); std::memcpy(bytes, samples.data() + read % CAPACITY, first * sizeof(int16_t)); std::memcpy(bytes + first * sizeof(int16_t), samples.data(), (count - first) * sizeof(int16_t)); read_position.store(read + count, std::memory_order_release); return count; } bool empty() const { return read_position.load(std::memory_order_acquire) == write_position.load(std::memory_order_acquire); } // Only while the SDL device is closed and no caller is doing I/O. void reset() { read_position.store(0); write_position.store(0); } };