Sound Byte Libs 29c5ff3
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
ring_buffer.hpp
Go to the documentation of this file.
1#ifndef SBL_PRIMITIVES_BUFFERS_RING_BUFFER_HPP_
2#define SBL_PRIMITIVES_BUFFERS_RING_BUFFER_HPP_
3
4/**
5 * @file ring_buffer.hpp
6 * @brief Lock-free ring buffer for ISR-safe communication
7 * @ingroup primitives
8 *
9 * Provides single-producer-single-consumer (SPSC) ring buffer for safe
10 * communication between interrupt service routines and main thread without
11 * blocking or corruption.
12 *
13 * Essential for audio applications where data must flow between interrupts
14 * and processing threads without blocking the interrupt handler.
15 */
16
17#include <cstdint>
18
19namespace sbl {
20namespace primitives {
21namespace buffers {
22
23/**
24 * @brief Lock-free single-producer-single-consumer ring buffer
25 *
26 * Thread-safe ring buffer designed for communication between ISR and main
27 * contexts. Uses volatile indices and memory barriers to ensure data
28 * coherency without locks or blocking operations.
29 *
30 * On single-core Cortex-M, volatile guarantees that reads/writes are not
31 * optimized away or reordered by the compiler. The memory barrier (provided
32 * via template parameter) ensures data is committed before the index update
33 * becomes visible.
34 *
35 * Key features:
36 * - Lock-free operation - never blocks ISR
37 * - Power-of-2 sizing for fast modulo operations
38 * - Memory barriers for correct ordering
39 * - Bounded execution time in ISR context
40 * - No dynamic allocation - all storage static
41 *
42 * Usage:
43 * @code
44 * // Create ring buffer for audio samples
45 * RingBuffer<int16_t, 256, MemoryBarrier> audio_buffer;
46 *
47 * // In ISR (producer)
48 * ISR(ADC_COMPLETE) {
49 * int16_t sample = ADC_read();
50 * if (!audio_buffer.push(sample)) {
51 * // Buffer full - handle overflow
52 * }
53 * }
54 *
55 * // In main loop (consumer)
56 * void process_audio() {
57 * int16_t sample;
58 * while (audio_buffer.pop(sample)) {
59 * // Process sample
60 * apply_filter(sample);
61 * }
62 * }
63 * @endcode
64 *
65 * @tparam T Element type to store in buffer
66 * @tparam Size Buffer size (must be power of 2)
67 * @tparam MemoryBarrierImpl Platform-specific memory barrier implementation
68 *
69 * @note All public methods are ISR-safe — lock-free SPSC, bounded computation, no blocking.
70 */
71template<typename T, uint32_t Size, typename MemoryBarrierImpl>
73 static_assert((Size & (Size - 1)) == 0, "Size must be power of 2");
74 static_assert(Size > 1, "Size must be greater than 1");
75 static_assert(Size <= 65536, "Size must fit in practical memory constraints");
76
77public:
78 /**
79 * @brief Construct empty ring buffer
80 */
81 RingBuffer() : head_(0), tail_(0) {}
82
83 /**
84 * @brief Push element to buffer (producer side)
85 *
86 * Adds element to the buffer if space is available. This method is
87 * ISR-safe and designed to be called from the producer context
88 * (typically an interrupt handler).
89 *
90 * @param item Element to add to buffer
91 * @return true if element was added, false if buffer is full
92 *
93 * @note This method has bounded execution time suitable for ISR use
94 */
95 bool push(const T& item) {
96 const uint32_t current_head = head_;
97 const uint32_t next_head = (current_head + 1) & (Size - 1);
98
99 // Check if buffer is full
100 if (next_head == tail_) {
101 return false;
102 }
103
104 // Store data
105 buffer_[current_head] = item;
106
107 // Memory barrier to ensure data is written before updating head
108 MemoryBarrierImpl::full_barrier();
109
110 // Update head pointer
111 head_ = next_head;
112
113 return true;
114 }
115
116 /**
117 * @brief Pop element from buffer (consumer side)
118 *
119 * Removes and returns the oldest element from the buffer if available.
120 * This method is designed to be called from the consumer context
121 * (typically the main thread).
122 *
123 * @param item Reference to store popped element
124 * @return true if element was popped, false if buffer is empty
125 */
126 bool pop(T& item) {
127 const uint32_t current_tail = tail_;
128
129 // Check if buffer is empty
130 if (current_tail == head_) {
131 return false;
132 }
133
134 // Load data
135 item = buffer_[current_tail];
136
137 // Memory barrier to ensure data is read before updating tail
138 MemoryBarrierImpl::full_barrier();
139
140 // Update tail pointer
141 tail_ = (current_tail + 1) & (Size - 1);
142
143 return true;
144 }
145
146 /**
147 * @brief Check if buffer is empty
148 *
149 * @return true if buffer contains no elements
150 */
151 bool empty() const {
152 return head_ == tail_;
153 }
154
155 /**
156 * @brief Check if buffer is full
157 *
158 * @return true if buffer cannot accept more elements
159 */
160 bool full() const {
161 return ((head_ + 1) & (Size - 1)) == tail_;
162 }
163
164 /**
165 * @brief Get number of elements in buffer
166 *
167 * @return Current number of elements
168 *
169 * @note This is a snapshot - value may change immediately after call
170 */
171 uint32_t size() const {
172 return (head_ - tail_) & (Size - 1);
173 }
174
175 /**
176 * @brief Get maximum buffer capacity
177 *
178 * @return Maximum number of elements buffer can hold
179 */
180 constexpr uint32_t capacity() const {
181 return Size - 1; // One slot reserved to distinguish full from empty
182 }
183
184 /**
185 * @brief Clear all elements from buffer
186 *
187 * Resets buffer to empty state. Should only be called when both
188 * producer and consumer are stopped to avoid data races.
189 */
190 void clear() {
191 head_ = 0;
192 tail_ = 0;
193 }
194
195private:
196 // Buffer storage
197 T buffer_[Size];
198
199 // Volatile indices for ISR/main thread visibility.
200 // On single-core Cortex-M, volatile ensures the compiler doesn't
201 // cache these in registers across barrier boundaries.
202 volatile uint32_t head_; // Producer writes, consumer reads
203 volatile uint32_t tail_; // Consumer writes, producer reads
204};
205
206/**
207 * @brief Type alias for common audio sample buffer
208 * @note Requires platform to provide MemoryBarrierImpl type
209 */
210template<typename MemoryBarrierImpl>
212
213/**
214 * @brief Type alias for MIDI event buffer
215 * @note Requires platform to provide MemoryBarrierImpl type
216 */
217template<typename MemoryBarrierImpl>
219
220/**
221 * @brief Type alias for general event buffer
222 * @note Requires platform to provide MemoryBarrierImpl type
223 */
224template<typename EventType, typename MemoryBarrierImpl>
226
227} // namespace buffers
228} // namespace primitives
229} // namespace sbl
230
231#endif // SBL_PRIMITIVES_BUFFERS_RING_BUFFER_HPP_
Lock-free single-producer-single-consumer ring buffer.
void clear()
Clear all elements from buffer.
bool pop(T &item)
Pop element from buffer (consumer side)
bool full() const
Check if buffer is full.
RingBuffer()
Construct empty ring buffer.
bool empty() const
Check if buffer is empty.
constexpr uint32_t capacity() const
Get maximum buffer capacity.
uint32_t size() const
Get number of elements in buffer.
bool push(const T &item)
Push element to buffer (producer side)
Root namespace for all Sound Byte Libs functionality.
Definition aliases.hpp:24