// This file is part of the 64k demo project. // It implements a lock-free ring buffer for audio streaming. #include "ring_buffer.h" #include "util/debug.h" #include #include #include // for abort() #include // for fprintf() AudioRingBuffer::AudioRingBuffer() : capacity_(RING_BUFFER_CAPACITY_SAMPLES), write_pos_(0), read_pos_(0), total_read_(0) { memset(buffer_, 0, sizeof(buffer_)); } AudioRingBuffer::~AudioRingBuffer() { // Nothing to clean up (static buffer) } int AudioRingBuffer::available_write() const { const int write = write_pos_.load(std::memory_order_acquire); const int read = read_pos_.load(std::memory_order_acquire); if (write >= read) { return capacity_ - (write - read) - 1; // -1 to avoid full/empty ambiguity } else { return read - write - 1; } } int AudioRingBuffer::available_read() const { const int write = write_pos_.load(std::memory_order_acquire); const int read = read_pos_.load(std::memory_order_acquire); if (write >= read) { return write - read; } else { return capacity_ - (read - write); } } int AudioRingBuffer::write(const float* samples, int count) { const int avail = available_write(); const int to_write = std::min(count, avail); if (to_write <= 0) { return 0; } const int write = write_pos_.load(std::memory_order_acquire); // BOUNDS CHECK: Validate write position if (write < 0 || write >= capacity_) { fprintf(stderr, "FATAL: write_pos out of bounds! write=%d, capacity=%d\n", write, capacity_); abort(); } const int space_to_end = capacity_ - write; if (to_write <= space_to_end) { // Write in one chunk // BOUNDS CHECK if (write < 0 || write + to_write > capacity_) { fprintf(stderr, "BOUNDS ERROR in write(): write=%d, to_write=%d, capacity=%d\n", write, to_write, capacity_); abort(); } memcpy(&buffer_[write], samples, to_write * sizeof(float)); write_pos_.store((write + to_write) % capacity_, std::memory_order_release); } else { // Write in two chunks (wrap around) // BOUNDS CHECK - first chunk if (write < 0 || write + space_to_end > capacity_) { fprintf(stderr, "BOUNDS ERROR in write() chunk1: write=%d, space_to_end=%d, capacity=%d\n", write, space_to_end, capacity_); abort(); } memcpy(&buffer_[write], samples, space_to_end * sizeof(float)); const int remainder = to_write - space_to_end; // BOUNDS CHECK - second chunk if (remainder < 0 || remainder > capacity_) { fprintf(stderr, "BOUNDS ERROR in write() chunk2: remainder=%d, capacity=%d\n", remainder, capacity_); abort(); } memcpy(&buffer_[0], samples + space_to_end, remainder * sizeof(float)); write_pos_.store(remainder, std::memory_order_release); } return to_write; } int AudioRingBuffer::read(float* samples, int count) { const int avail = available_read(); const int to_read = std::min(count, avail); if (to_read > 0) { const int read = read_pos_.load(std::memory_order_acquire); // BOUNDS CHECK: Validate read position if (read < 0 || read >= capacity_) { fprintf(stderr, "FATAL: read_pos out of bounds! read=%d, capacity=%d\n", read, capacity_); abort(); } const int space_to_end = capacity_ - read; if (to_read <= space_to_end) { // Read in one chunk // BOUNDS CHECK if (read < 0 || read + to_read > capacity_) { fprintf(stderr, "BOUNDS ERROR in read(): read=%d, to_read=%d, capacity=%d\n", read, to_read, capacity_); abort(); } memcpy(samples, &buffer_[read], to_read * sizeof(float)); read_pos_.store((read + to_read) % capacity_, std::memory_order_release); } else { // Read in two chunks (wrap around) // BOUNDS CHECK - first chunk if (read < 0 || read + space_to_end > capacity_) { fprintf(stderr, "BOUNDS ERROR in read() chunk1: read=%d, space_to_end=%d, capacity=%d\n", read, space_to_end, capacity_); abort(); } memcpy(samples, &buffer_[read], space_to_end * sizeof(float)); const int remainder = to_read - space_to_end; // BOUNDS CHECK - second chunk if (remainder < 0 || remainder > capacity_) { fprintf(stderr, "BOUNDS ERROR in read() chunk2: remainder=%d, capacity=%d\n", remainder, capacity_); abort(); } memcpy(samples + space_to_end, &buffer_[0], remainder * sizeof(float)); read_pos_.store(remainder, std::memory_order_release); } total_read_.fetch_add(to_read, std::memory_order_release); } // Fill remainder with silence if not enough samples available if (to_read < count) { #if defined(DEBUG_LOG_RING_BUFFER) // UNDERRUN DETECTED static int underrun_count = 0; if (++underrun_count % 10 == 1) { // Log every 10th underrun DEBUG_RING_BUFFER("UNDERRUN #%d: requested=%d, available=%d, filling %d with silence\n", underrun_count, count, to_read, count - to_read); } #endif /* defined(DEBUG_LOG_RING_BUFFER) */ memset(samples + to_read, 0, (count - to_read) * sizeof(float)); } return to_read; } void AudioRingBuffer::clear() { write_pos_.store(0, std::memory_order_release); read_pos_.store(0, std::memory_order_release); // Note: Don't reset total_read_ - it tracks absolute playback time memset(buffer_, 0, sizeof(buffer_)); }