1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
// 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 "util/fatal_error.h"
#include <algorithm>
#include <cstring>
AudioRingBuffer::AudioRingBuffer()
: capacity_(RING_BUFFER_CAPACITY_SAMPLES), write_pos_(0), read_pos_(0),
total_read_(0), total_written_(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
FATAL_CHECK(write < 0 || write >= capacity_,
"write_pos out of bounds! write=%d, capacity=%d\n", write,
capacity_);
const int space_to_end = capacity_ - write;
if (to_write <= space_to_end) {
// Write in one chunk
// BOUNDS CHECK
FATAL_CHECK(write < 0 || write + to_write > capacity_,
"BOUNDS ERROR in write(): write=%d, to_write=%d, capacity=%d\n",
write, to_write, capacity_);
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
FATAL_CHECK(write < 0 || write + space_to_end > capacity_,
"BOUNDS ERROR in write() chunk1: write=%d, space_to_end=%d, "
"capacity=%d\n",
write, space_to_end, capacity_);
memcpy(&buffer_[write], samples, space_to_end * sizeof(float));
const int remainder = to_write - space_to_end;
// BOUNDS CHECK - second chunk
FATAL_CHECK(remainder < 0 || remainder > capacity_,
"BOUNDS ERROR in write() chunk2: remainder=%d, capacity=%d\n",
remainder, capacity_);
memcpy(&buffer_[0], samples + space_to_end, remainder * sizeof(float));
write_pos_.store(remainder, std::memory_order_release);
}
// Track total samples written for render timing
total_written_.fetch_add(to_write, 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
FATAL_CHECK(read < 0 || read >= capacity_,
"read_pos out of bounds! read=%d, capacity=%d\n", read,
capacity_);
const int space_to_end = capacity_ - read;
if (to_read <= space_to_end) {
// Read in one chunk
// BOUNDS CHECK
FATAL_CHECK(read < 0 || read + to_read > capacity_,
"BOUNDS ERROR in read(): read=%d, to_read=%d, capacity=%d\n",
read, to_read, capacity_);
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
FATAL_CHECK(read < 0 || read + space_to_end > capacity_,
"BOUNDS ERROR in read() chunk1: read=%d, space_to_end=%d, "
"capacity=%d\n",
read, space_to_end, capacity_);
memcpy(samples, &buffer_[read], space_to_end * sizeof(float));
const int remainder = to_read - space_to_end;
// BOUNDS CHECK - second chunk
FATAL_CHECK(remainder < 0 || remainder > capacity_,
"BOUNDS ERROR in read() chunk2: remainder=%d, capacity=%d\n",
remainder, capacity_);
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_));
}
|