Sound Byte Libs 1ee2ca6
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_CORE_PRIMITIVES_BUFFERS_RING_BUFFER_HPP_
2#define SBL_CORE_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
19// ARM-native types for optimal performance
20namespace sbl::common::types {
21 using BufferIndex = uint32_t;
22 using BufferSize = uint32_t;
23}
24
25namespace sbl {
26namespace core {
27namespace primitives {
28namespace buffers {
29
30/**
31 * @brief Lock-free single-producer-single-consumer ring buffer
32 *
33 * Thread-safe ring buffer designed for communication between ISR and main
34 * contexts. Uses atomic operations and memory barriers to ensure data
35 * coherency without locks or blocking operations.
36 *
37 * Key features:
38 * - Lock-free operation - never blocks ISR
39 * - Power-of-2 sizing for fast modulo operations
40 * - Proper memory barriers for multi-core safety
41 * - Bounded execution time in ISR context
42 * - No dynamic allocation - all storage static
43 *
44 * Typical use cases:
45 * - Audio sample transfer between ISR and processing
46 * - Event capture (triggers, gates, clock edges)
47 * - Parameter changes from UI to audio engine
48 * - CV recording and analysis
49 *
50 * Usage:
51 * @code
52 * // Create ring buffer for audio samples
53 * // (assumes platform provides MemoryBarrier type)
54 * RingBuffer<int16_t, 256, MemoryBarrier> audio_buffer;
55 *
56 * // In ISR (producer)
57 * ISR(ADC_COMPLETE) {
58 * int16_t sample = ADC_read();
59 * if (!audio_buffer.push(sample)) {
60 * // Buffer full - handle overflow
61 * }
62 * }
63 *
64 * // In main loop (consumer)
65 * void process_audio() {
66 * int16_t sample;
67 * while (audio_buffer.pop(sample)) {
68 * // Process sample
69 * apply_filter(sample);
70 * }
71 * }
72 * @endcode
73 *
74 * @tparam T Element type to store in buffer
75 * @tparam Size Buffer size (must be power of 2)
76 * @tparam MemoryBarrierImpl Platform-specific memory barrier implementation
77 */
78template<typename T, common::types::BufferSize Size, typename MemoryBarrierImpl>
80 static_assert((Size & (Size - 1)) == 0, "Size must be power of 2");
81 static_assert(Size > 1, "Size must be greater than 1");
82 static_assert(Size <= 65536, "Size must fit in practical memory constraints");
83
84public:
85 /**
86 * @brief Construct empty ring buffer
87 */
88 RingBuffer() : head_(0), tail_(0) {}
89
90 /**
91 * @brief Push element to buffer (producer side)
92 *
93 * Adds element to the buffer if space is available. This method is
94 * ISR-safe and designed to be called from the producer context
95 * (typically an interrupt handler).
96 *
97 * @param item Element to add to buffer
98 * @return true if element was added, false if buffer is full
99 *
100 * @note This method has bounded execution time suitable for ISR use
101 */
102 bool push(const T& item) {
103 const common::types::BufferIndex current_head = head_.load();
104 const common::types::BufferIndex next_head = (current_head + 1) & (Size - 1);
105
106 // Check if buffer is full
107 if (next_head == tail_.load()) {
108 return false;
109 }
110
111 // Store data
112 buffer_[current_head] = item;
113
114 // Memory barrier to ensure data is written before updating head
115 memory_barrier();
116
117 // Update head pointer
118 head_.store(next_head);
119
120 return true;
121 }
122
123 /**
124 * @brief Pop element from buffer (consumer side)
125 *
126 * Removes and returns the oldest element from the buffer if available.
127 * This method is designed to be called from the consumer context
128 * (typically the main thread).
129 *
130 * @param item Reference to store popped element
131 * @return true if element was popped, false if buffer is empty
132 */
133 bool pop(T& item) {
134 const common::types::BufferIndex current_tail = tail_.load();
135
136 // Check if buffer is empty
137 if (current_tail == head_.load()) {
138 return false;
139 }
140
141 // Load data
142 item = buffer_[current_tail];
143
144 // Memory barrier to ensure data is read before updating tail
145 memory_barrier();
146
147 // Update tail pointer
148 const common::types::BufferIndex next_tail = (current_tail + 1) & (Size - 1);
149 tail_.store(next_tail);
150
151 return true;
152 }
153
154 /**
155 * @brief Check if buffer is empty
156 *
157 * @return true if buffer contains no elements
158 */
159 bool empty() const {
160 return head_.load() == tail_.load();
161 }
162
163 /**
164 * @brief Check if buffer is full
165 *
166 * @return true if buffer cannot accept more elements
167 */
168 bool full() const {
169 const common::types::BufferIndex next_head = (head_.load() + 1) & (Size - 1);
170 return next_head == tail_.load();
171 }
172
173 /**
174 * @brief Get number of elements in buffer
175 *
176 * @return Current number of elements
177 *
178 * @note This is a snapshot - value may change immediately after call
179 */
181 const common::types::BufferIndex h = head_.load();
182 const common::types::BufferIndex t = tail_.load();
183 return (h - t) & (Size - 1);
184 }
185
186 /**
187 * @brief Get maximum buffer capacity
188 *
189 * @return Maximum number of elements buffer can hold
190 */
192 return Size - 1; // One slot reserved to distinguish full from empty
193 }
194
195 /**
196 * @brief Clear all elements from buffer
197 *
198 * Resets buffer to empty state. Should only be called when both
199 * producer and consumer are stopped to avoid data races.
200 */
201 void clear() {
202 head_.store(0);
203 tail_.store(0);
204 }
205
206private:
207 // Atomic types for lock-free operation - ARM-native 32-bit for optimal performance
208 using AtomicIndex = volatile common::types::BufferIndex;
209
210 /**
211 * @brief Platform-specific memory barrier
212 *
213 * Ensures memory operations complete in order. Uses platform-specific
214 * implementation provided via template parameter.
215 */
216 static void memory_barrier() {
217 MemoryBarrierImpl::full_barrier();
218 }
219
220 // Buffer storage
221 T buffer_[Size];
222
223 // Lock-free atomic indices
224 // Separate cache lines to avoid false sharing on multi-core systems
225 alignas(64) AtomicIndex head_; // Producer writes, consumer reads
226 alignas(64) AtomicIndex tail_; // Consumer writes, producer reads
227};
228
229/**
230 * @brief Type alias for common audio sample buffer - ARM-optimized sizing
231 * @note Requires platform to provide MemoryBarrierImpl type
232 */
233template<typename MemoryBarrierImpl>
235
236/**
237 * @brief Type alias for MIDI event buffer - ARM-optimized sizing
238 * @note Requires platform to provide MemoryBarrierImpl type
239 */
240template<typename MemoryBarrierImpl>
242
243/**
244 * @brief Type alias for general event buffer - ARM-optimized sizing
245 * @note Requires platform to provide MemoryBarrierImpl type
246 */
247template<typename EventType, typename MemoryBarrierImpl>
249
250} // namespace buffers
251} // namespace primitives
252} // namespace core
253} // namespace sbl
254
255#endif // SBL_CORE_PRIMITIVES_BUFFERS_RING_BUFFER_HPP_
Lock-free single-producer-single-consumer ring buffer.
void clear()
Clear all elements from buffer.
bool push(const T &item)
Push element to buffer (producer side)
RingBuffer()
Construct empty ring buffer.
bool pop(T &item)
Pop element from buffer (consumer side)
common::types::BufferIndex size() const
Get number of elements in buffer.
bool empty() const
Check if buffer is empty.
bool full() const
Check if buffer is full.
constexpr common::types::BufferIndex capacity() const
Get maximum buffer capacity.
uint32_t BufferIndex
ARM-native buffer index type.
Definition types.hpp:58
uint32_t BufferSize
ARM-native buffer size type.
Definition types.hpp:66
Root namespace for all Sound Byte Libs functionality.
Definition aliases.hpp:24