Sound Byte Libs 0.5.1-121-g3358a44
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
event_queue.hpp
Go to the documentation of this file.
1// sbl/control/event_queue.hpp — Events from another context into the control tick (FDP-073)
2//
3// One producing context pushes (the main loop, which parses USB MIDI); the
4// control tick drains in Stage::Drain. Events reach the control context and
5// only the control context touches control state, so the cross-context
6// writes RPT-026 §3d found cannot happen.
7//
8// Lock-free SPSC over RingBuffer. A full queue drops the newest event: the
9// push is rejected and counted. Only the consumer may move the tail, so
10// dropping the oldest would need a lock (FDP-073 addendum 2026-09-11). Size
11// the queue so overflow means a stalled tick — 64 events drained every 1 ms
12// is about 60x full-rate DIN MIDI.
13//
14// drain() pops at most capacity() events per call, so a producer that keeps
15// pushing cannot stretch the tick.
16//
17// Usage:
18// sbl::control::EventQueue<sbl::midi::MidiEvent, 64> s_midi_q;
19//
20// // main loop (producer)
21// s_midi_q.push(event);
22//
23// // Stage::Drain subscriber (consumer)
24// s_midi_q.drain([](const sbl::midi::MidiEvent& e) { handle(e); });
25
26#ifndef SBL_CONTROL_EVENT_QUEUE_HPP_
27#define SBL_CONTROL_EVENT_QUEUE_HPP_
28
29#include <atomic>
30#include <cstdint>
31
33
34namespace sbl::control {
35
36template<typename T, uint32_t Size>
38public:
39 /// Producer side. Returns false (and counts a drop) if the queue is full.
40 bool push(const T& event) {
41 if (ring_.push(event)) return true;
42 dropped_.store(dropped_.load(std::memory_order_relaxed) + 1,
43 std::memory_order_relaxed);
44 return false;
45 }
46
47 /// Consumer side. Calls fn(event) for up to capacity() queued events,
48 /// oldest first. Returns the number handled.
49 template<typename Fn>
51 T event{};
52 uint32_t handled = 0;
53 while (handled < capacity() && ring_.pop(event)) {
54 fn(static_cast<const T&>(event));
55 ++handled;
56 }
57 return handled;
58 }
59
60 /// Consumer side. Pop one event; false if empty.
61 bool pop(T& event) { return ring_.pop(event); }
62
63 bool empty() const { return ring_.empty(); }
64
65 /// Pushes rejected because the queue was full, since boot. Any context.
66 uint32_t dropped() const { return dropped_.load(std::memory_order_relaxed); }
67
68 static constexpr uint32_t capacity() { return Size - 1; }
69
70private:
72 std::atomic<uint32_t> dropped_{0}; // producer writes, anyone reads
73};
74
75} // namespace sbl::control
76
77#endif // SBL_CONTROL_EVENT_QUEUE_HPP_
static constexpr uint32_t capacity()
uint32_t drain(Fn &&fn)
bool pop(T &event)
Consumer side. Pop one event; false if empty.
uint32_t dropped() const
Pushes rejected because the queue was full, since boot. Any context.
bool push(const T &event)
Producer side. Returns false (and counts a drop) if the queue is full.
Control Stack v2: scheduler, event queue, merge, CC mapping, calibration, presets.
Lock-free single-producer, single-consumer ring buffer.