summaryrefslogtreecommitdiff
path: root/src/audio/ring_buffer.cc
blob: ab51d6b068bad850c20a6d8f3894060e563eefb3 (plain)
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
155
156
157
158
159
160
161
162
163
164
165
166
167
// 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 <algorithm>
#include <cstring>
#include <cstdlib>  // for abort()
#include <cstdio>   // 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_));
}