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,
18 : "T must be trivially copyable");
19 :
20 : public:
21 49086 : SPSCQueue() : head_(0), tail_(0) {}
22 :
23 : // Called from the producer (GUI) thread.
24 : // Returns false if the queue is full (message dropped by caller).
25 666 : bool try_push(const T& item) {
26 666 : const size_t h = head_.load(std::memory_order_relaxed);
27 666 : const size_t next = (h + 1) & kMask;
28 888 : if (next == tail_.load(std::memory_order_acquire)) {
29 2 : return false; // full
30 : }
31 663 : buf_[h] = item;
32 663 : head_.store(next, std::memory_order_release);
33 663 : return true;
34 222 : }
35 :
36 : // Called from the consumer (audio) thread.
37 : // Returns false if the queue is empty.
38 4973 : bool try_pop(T& item) {
39 4973 : const size_t t = tail_.load(std::memory_order_relaxed);
40 6190 : if (t == head_.load(std::memory_order_acquire)) {
41 3658 : return false; // empty
42 : }
43 147 : item = buf_[t];
44 147 : tail_.store((t + 1) & kMask, std::memory_order_release);
45 147 : return true;
46 2539 : }
47 :
48 : // Inspect the front item without consuming it (consumer thread only).
49 : // Returns false if the queue is empty.
50 4820 : bool try_peek(T& item) const {
51 4820 : const size_t t = tail_.load(std::memory_order_relaxed);
52 5986 : if (t == head_.load(std::memory_order_acquire)) {
53 3612 : return false; // empty
54 : }
55 63 : item = buf_[t];
56 63 : return true;
57 2488 : }
58 :
59 6 : size_t try_pop_all(std::vector<T>& out_vec) {
60 6 : size_t count = 0;
61 : T item;
62 15 : while (try_pop(item)) {
63 9 : out_vec.push_back(item);
64 9 : ++count;
65 : }
66 6 : return count;
67 : }
68 :
69 12 : size_t size() const {
70 20 : return (head_.load(std::memory_order_acquire) - tail_.load(std::memory_order_acquire)) & kMask;
71 : }
72 :
73 2 : size_t capacity() const {
74 2 : return Capacity - 1;
75 : }
76 :
77 : private:
78 : static constexpr size_t kMask = Capacity - 1;
79 :
80 : // Pad to avoid false sharing between producer and consumer cache lines
81 : alignas(64) std::atomic<size_t> head_;
82 : alignas(64) std::atomic<size_t> tail_;
83 : T buf_[Capacity];
84 : };
85 :
86 : // A command that the GUI thread sends to the audio thread.
87 : struct AudioCommand {
88 : enum Type : uint8_t {
89 : SetEffectParam, // Change an effect parameter value
90 : SetEffectEnabled, // Enable/disable an effect
91 : SetEffectMix, // Change effect wet/dry mix
92 : SetMixerGain, // Change mixer input gain dynamically
93 : SetInputGain, // Change master input gain
94 : SetOutputGain, // Change master output gain
95 : AddEffect, // Signal that effect list changed (swap pointer)
96 : RemoveEffect, // Signal that effect list changed
97 : MoveEffect, // Signal that effect list changed
98 : };
99 :
100 : Type type;
101 : int effect_index; // Which effect in the chain
102 : int param_index; // Which parameter within the effect
103 : float value; // The new value
104 : };
105 :
106 : } // namespace Amplitron
|