arduino-audio-tools
Loading...
Searching...
No Matches
RingBufferSPSC.h
Go to the documentation of this file.
1#pragma once
2#include <atomic>
3#include <cstring>
4
6
7namespace audio_tools {
8
39template <typename T = uint8_t>
40class RingBufferSPSC : public BaseBuffer<T> {
41 public:
42 RingBufferSPSC() = default;
43
44 explicit RingBufferSPSC(size_t capacity) { resize(capacity); }
45
46 // ── BaseBuffer interface ────────────────────────────────────────────────
47
48 bool write(T data) override { return writeArray(&data, 1) == 1; }
49
50 bool read(T& result) override { return readArray(&result, 1) == 1; }
51
52 bool peek(T& result) override {
53 size_t h = head_.load(std::memory_order_acquire);
54 size_t t = tail_.load(std::memory_order_relaxed);
55 if (h == t) return false;
56 result = buf_[t & mask_];
57 return true;
58 }
59
60 // Bulk write — called only by the producer.
61 int writeArray(const T data[], int len) override {
62 if (capacity_ == 0 || len <= 0) return 0;
63 size_t t = tail_.load(std::memory_order_acquire);
64 size_t h = head_.load(std::memory_order_relaxed);
65 size_t space = capacity_ - (h - t);
66 size_t n = (size_t)len < space ? (size_t)len : space;
67 if (n == 0) return 0;
68
69 size_t h_idx = h & mask_;
70 size_t first = capacity_ - h_idx; // bytes until end of physical buffer
71 if (first > n) first = n;
72 memcpy(buf_ + h_idx, data, first * sizeof(T));
73 memcpy(buf_, data + first, (n - first) * sizeof(T));
74
75 head_.store(h + n, std::memory_order_release);
76 return (int)n;
77 }
78
79 // Bulk read — called only by the consumer.
80 int readArray(T data[], int len) override {
81 if (capacity_ == 0 || len <= 0) return 0;
82 size_t h = head_.load(std::memory_order_acquire);
83 size_t t = tail_.load(std::memory_order_relaxed);
84 size_t avail = h - t;
85 size_t n = (size_t)len < avail ? (size_t)len : avail;
86 if (n == 0) return 0;
87
88 size_t t_idx = t & mask_;
89 size_t first = capacity_ - t_idx; // bytes until end of physical buffer
90 if (first > n) first = n;
91 memcpy(data, buf_ + t_idx, first * sizeof(T));
92 memcpy(data + first, buf_, (n - first) * sizeof(T));
93
94 tail_.store(t + n, std::memory_order_release);
95 return (int)n;
96 }
97
98 // available() is called from both sides (consumer AND feedback ISR on
99 // producer core), so we use acquire on both loads for a consistent
100 // snapshot regardless of which core is calling.
101 int available() override {
102 size_t h = head_.load(std::memory_order_acquire);
103 size_t t = tail_.load(std::memory_order_acquire);
104 return (int)(h - t);
105 }
106
107 int availableForWrite() override {
108 size_t h = head_.load(std::memory_order_acquire);
109 size_t t = tail_.load(std::memory_order_acquire);
110 return (int)(capacity_ - (h - t));
111 }
112
113 // reset() is only safe when neither producer nor consumer is active
114 // (e.g. USB bus reset). It is not atomic.
115 void reset() override {
116 head_.store(0, std::memory_order_relaxed);
117 tail_.store(0, std::memory_order_relaxed);
118 }
119
120 bool resize(size_t capacity) override {
121 // Round up to the next power of two so masking replaces modulo.
122 size_t pow2 = 1;
123 while (pow2 < capacity) pow2 <<= 1;
124 delete[] buf_;
125 buf_ = (capacity > 0) ? new T[pow2] : nullptr;
126 capacity_ = (capacity > 0) ? pow2 : 0;
127 mask_ = capacity_ > 0 ? capacity_ - 1 : 0;
128 reset();
129 return true;
130 }
131
132 T* address() override { return buf_; }
133 size_t size() override { return capacity_; }
134
135 ~RingBufferSPSC() override { delete[] buf_; }
136
137 // Non-copyable: the buffer owns heap memory and the atomics are not
138 // copyable.
141
142 private:
143 T* buf_ = nullptr;
144 size_t capacity_ = 0;
145 size_t mask_ = 0;
146
147 // Separate the two hot atomics onto different 32-byte regions to prevent
148 // false sharing on multi-core targets that have a data cache (e.g. ESP32).
149 // On Cortex-M0+ (RP2040, no cache) this is free.
150 alignas(32) std::atomic<size_t> head_{0}; // written by producer only
151 alignas(32) std::atomic<size_t> tail_{0}; // written by consumer only
152};
153
154} // namespace audio_tools
Shared functionality of all buffers.
Definition Buffers.h:23
Lock-free Single-Producer Single-Consumer ring buffer.
Definition RingBufferSPSC.h:40
RingBufferSPSC(const RingBufferSPSC &)=delete
size_t size() override
Definition RingBufferSPSC.h:133
bool peek(T &result) override
peeks the actual entry from the buffer
Definition RingBufferSPSC.h:52
bool read(T &result) override
reads a single value
Definition RingBufferSPSC.h:50
~RingBufferSPSC() override
Definition RingBufferSPSC.h:135
RingBufferSPSC(size_t capacity)
Definition RingBufferSPSC.h:44
bool write(T data) override
write add an entry to the buffer
Definition RingBufferSPSC.h:48
int available() override
provides the number of entries that are available to read
Definition RingBufferSPSC.h:101
int availableForWrite() override
provides the number of entries that are available to write
Definition RingBufferSPSC.h:107
T * address() override
returns the address of the start of the physical read buffer
Definition RingBufferSPSC.h:132
int writeArray(const T data[], int len) override
Fills the buffer data.
Definition RingBufferSPSC.h:61
bool resize(size_t capacity) override
Resizes the buffer if supported: returns false if not supported.
Definition RingBufferSPSC.h:120
RingBufferSPSC & operator=(const RingBufferSPSC &)=delete
void reset() override
clears the buffer
Definition RingBufferSPSC.h:115
int readArray(T data[], int len) override
reads multiple values
Definition RingBufferSPSC.h:80
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
size_t writeData(Print *p_out, T *data, int samples, int maxSamples=512)
Definition AudioTypes.h:508