Loading...
Searching...
No Matches
SampleRing.hpp
1#pragma once
2#include <algorithm>
3#include <array>
4#include <atomic>
5#include <bit>
6#include <chrono>
7#include <cstdint>
8#include <limits>
9#include <memory>
10#include <span>
11
12namespace Process
13{
35{
36public:
37 static constexpr int max_channels = 64;
38
39 explicit SampleRing(int capacity = 1 << 16)
40 : m_capacity{std::bit_ceil((uint64_t)std::max(capacity, 1024))}
41 , m_mask{m_capacity - 1}
42 {
43 }
44
45 SampleRing(const SampleRing&) = delete;
46 SampleRing& operator=(const SampleRing&) = delete;
47
49 {
50 for(auto& b : m_buffers)
51 delete[] b.load(std::memory_order_relaxed);
52 }
53
54 uint64_t capacity() const noexcept { return m_capacity; }
55
59 template <typename F>
60 void write(int channel_count, int frames, F&& channel) noexcept
61 {
62 if(frames <= 0)
63 return;
64 channel_count = std::clamp(channel_count, 0, max_channels);
65 m_channels.store(channel_count, std::memory_order_relaxed);
66
67 const uint64_t w = m_written.load(std::memory_order_relaxed);
68 const auto now = clock::now();
69 const double rate = m_rate.load(std::memory_order_relaxed);
70 const auto block = std::chrono::duration<double>(frames / std::max(rate, 1.));
71 if(w > 0 && now - m_last_write > std::max(4 * block, std::chrono::duration<double>(0.05)))
72 m_from.store(w, std::memory_order_relaxed);
73 m_last_write = now;
74
75 // Announce the block before touching the data
76 m_writing.store(w + frames, std::memory_order_relaxed);
77 std::atomic_thread_fence(std::memory_order_release);
78
79 for(int c = 0; c < channel_count; c++)
80 {
81 float* buf = m_buffers[c].load(std::memory_order_acquire);
82 if(!buf)
83 continue;
84 // First block in a newly provisioned buffer, or after the channel was
85 // skipped: what is before it is not this channel's signal
86 if(m_since[c].load(std::memory_order_relaxed) == unwritten)
87 m_since[c].store(w, std::memory_order_relaxed);
88 const auto* src = channel(c);
89 for(int i = 0; i < frames; i++)
90 std::atomic_ref<float>{buf[(w + i) & m_mask]}.store(
91 float(src[i]), std::memory_order_relaxed);
92 }
93 for(int c = channel_count; c < m_written_channels; c++)
94 m_since[c].store(unwritten, std::memory_order_relaxed);
95 m_written_channels = channel_count;
96
97 m_written.store(w + frames, std::memory_order_release);
98 }
99
101 void restart() noexcept
102 {
103 m_from.store(m_written.load(std::memory_order_relaxed), std::memory_order_relaxed);
104 }
105
106 void set_rate(double rate) noexcept { m_rate.store(rate, std::memory_order_relaxed); }
107
110 {
111 const int n = m_channels.load(std::memory_order_relaxed);
112 for(int c = 0; c < n; c++)
113 {
114 if(!m_buffers[c].load(std::memory_order_relaxed))
115 {
116 m_since[c].store(unwritten, std::memory_order_relaxed);
117 m_buffers[c].store(new float[m_capacity]{}, std::memory_order_release);
118 }
119 }
120 }
121
122 int channels() const noexcept { return m_channels.load(std::memory_order_relaxed); }
123 double rate() const noexcept { return m_rate.load(std::memory_order_relaxed); }
124
126 uint64_t written() const noexcept { return m_written.load(std::memory_order_acquire); }
127
131 bool read(int c, uint64_t start, std::span<float> dst) const noexcept
132 {
133 if(c < 0 || c >= max_channels || dst.size() > m_capacity / 2)
134 return false;
135 float* buf = m_buffers[c].load(std::memory_order_acquire);
136 if(!buf)
137 return false;
138
139 const uint64_t end = start + dst.size();
140 if(end > m_written.load(std::memory_order_acquire))
141 return false;
142 if(start < m_from.load(std::memory_order_relaxed)
143 || start < m_since[c].load(std::memory_order_relaxed))
144 return false;
145 if(!untouched(start))
146 return false;
147
148 for(std::size_t i = 0; i < dst.size(); i++)
149 dst[i] = std::atomic_ref<float>{buf[(start + i) & m_mask]}.load(
150 std::memory_order_relaxed);
151
152 // Pairs with the writer's fence: a block that overwrote part of the copy
153 // was announced before, so it is seen here.
154 std::atomic_thread_fence(std::memory_order_acquire);
155 return untouched(start);
156 }
157
160 bool read_latest(int c, std::span<float> dst, uint64_t& first) const noexcept
161 {
162 const uint64_t w = written();
163 if(w < dst.size())
164 return false;
165 first = w - dst.size();
166 return read(c, first, dst);
167 }
168
169private:
170 using clock = std::chrono::steady_clock;
171 static constexpr uint64_t unwritten = std::numeric_limits<uint64_t>::max();
172
174 bool untouched(uint64_t start) const noexcept
175 {
176 return m_writing.load(std::memory_order_relaxed) <= start + m_capacity;
177 }
178
179 const uint64_t m_capacity;
180 const uint64_t m_mask;
181 std::array<std::atomic<float*>, max_channels> m_buffers{};
183 std::array<std::atomic<uint64_t>, max_channels> m_since{};
184 std::atomic<uint64_t> m_written{0};
185 std::atomic<uint64_t> m_writing{0};
187 std::atomic<uint64_t> m_from{0};
188 std::atomic<int> m_channels{0};
189 std::atomic<double> m_rate{48000.};
190
191 // Writer only
192 clock::time_point m_last_write{};
193 int m_written_channels{};
194};
195}
Definition SampleRing.hpp:35
bool read_latest(int c, std::span< float > dst, uint64_t &first) const noexcept
Definition SampleRing.hpp:160
void write(int channel_count, int frames, F &&channel) noexcept
Definition SampleRing.hpp:60
bool read(int c, uint64_t start, std::span< float > dst) const noexcept
Definition SampleRing.hpp:131
void provision()
Reader: allocates the buffers of the channels the writer reported.
Definition SampleRing.hpp:109
uint64_t written() const noexcept
Absolute index one past the last frame written so far.
Definition SampleRing.hpp:126
void restart() noexcept
Writer: what was written so far is not continuous with what comes next.
Definition SampleRing.hpp:101
Base classes and tools to implement processes and layers.
Definition JSONVisitor.hpp:1115