Line data Source code
1 : #pragma once
2 :
3 : #include <atomic>
4 : #include <cstddef>
5 : #include <type_traits>
6 : #include <vector>
7 :
8 : namespace Amplitron {
9 :
10 : // Lock-free Single-Producer Single-Consumer ring buffer.
11 : // Producer: GUI thread pushes parameter changes.
12 : // Consumer: Audio thread drains at the start of each callback.
13 : template <typename T, size_t Capacity>
14 : class SPSCQueue {
15 : static_assert(Capacity > 0 && (Capacity & (Capacity - 1)) == 0,
16 : "Capacity must be a power of two");
17 : static_assert(std::is_trivially_copyable<T>::value, "T must be trivially copyable");
18 :
19 : public:
20 50130 : SPSCQueue() : head_(0), tail_(0) {}
21 :
22 : // Called from the producer (GUI) thread.
23 : // Returns false if the queue is full (message dropped by caller).
24 666 : bool try_push(const T& item) {
25 666 : const size_t h = head_.load(std::memory_order_relaxed);
26 666 : const size_t next = (h + 1) & kMask;
27 888 : if (next == tail_.load(std::memory_order_acquire)) {
28 2 : return false; // full
29 : }
30 663 : buf_[h] = item;
31 663 : head_.store(next, std::memory_order_release);
32 663 : return true;
33 222 : }
34 :
35 : // Called from the consumer (audio) thread.
36 : // Returns false if the queue is empty.
37 4828 : bool try_pop(T& item) {
38 4828 : const size_t t = tail_.load(std::memory_order_relaxed);
39 6045 : if (t == head_.load(std::memory_order_acquire)) {
40 3513 : return false; // empty
41 : }
42 147 : item = buf_[t];
43 147 : tail_.store((t + 1) & kMask, std::memory_order_release);
44 147 : return true;
45 2394 : }
46 :
47 : // Inspect the front item without consuming it (consumer thread only).
48 : // Returns false if the queue is empty.
49 4682 : bool try_peek(T& item) const {
50 4682 : const size_t t = tail_.load(std::memory_order_relaxed);
51 5848 : if (t == head_.load(std::memory_order_acquire)) {
52 3474 : return false; // empty
53 : }
54 63 : item = buf_[t];
55 63 : return true;
56 2350 : }
57 :
58 6 : size_t try_pop_all(std::vector<T>& out_vec) {
59 6 : size_t count = 0;
60 : T item;
61 15 : while (try_pop(item)) {
62 9 : out_vec.push_back(item);
63 9 : ++count;
64 : }
65 6 : return count;
66 : }
67 :
68 12 : size_t size() const {
69 16 : return (head_.load(std::memory_order_acquire) - tail_.load(std::memory_order_acquire)) &
70 8 : kMask;
71 : }
72 :
73 2 : size_t capacity() const { return Capacity - 1; }
74 :
75 : private:
76 : static constexpr size_t kMask = Capacity - 1;
77 :
78 : // Pad to avoid false sharing between producer and consumer cache lines
79 : alignas(64) std::atomic<size_t> head_;
80 : alignas(64) std::atomic<size_t> tail_;
81 : T buf_[Capacity];
82 : };
83 :
84 : // A command that the GUI thread sends to the audio thread.
85 : struct AudioCommand {
86 : enum Type : uint8_t {
87 : SetEffectParam, // Change an effect parameter value
88 : SetEffectEnabled, // Enable/disable an effect
89 : SetEffectMix, // Change effect wet/dry mix
90 : SetMixerGain, // Change mixer input gain dynamically
91 : SetInputGain, // Change master input gain
92 : SetOutputGain, // Change master output gain
93 : AddEffect, // Signal that effect list changed (swap pointer)
94 : RemoveEffect, // Signal that effect list changed
95 : MoveEffect, // Signal that effect list changed
96 : };
97 :
98 : Type type;
99 : int effect_index; // Which effect in the chain
100 : int param_index; // Which parameter within the effect
101 : float value; // The new value
102 : };
103 :
104 : } // namespace Amplitron
|