Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
async_block_worker.hpp
Go to the documentation of this file.
1#pragma once
2
6
10
11#include <algorithm>
12#include <atomic>
13#include <chrono>
14#include <cstdint>
15#include <functional>
16#include <span>
17#include <thread>
18#include <type_traits>
19#include <vector>
20
21namespace pulp::audio {
22
25enum class AsyncBlockMode : std::uint8_t {
35};
36
39template <typename Event> struct AsyncBlockEvent {
40 std::uint32_t offset = 0;
41 Event event{};
42};
43
45 double sample_rate = 48000.0;
46 std::uint32_t input_channels = 0;
47 std::uint32_t output_channels = 2;
49 std::uint32_t max_block = 512;
52 std::uint32_t latency_blocks = 1;
54 std::uint32_t max_latency_blocks = 8;
56 std::uint32_t max_chunk = 0;
58 std::uint32_t max_events_in = 256;
59 std::uint32_t max_events_out = 256;
62 std::uint32_t worker_latency_samples = 0;
64 std::chrono::microseconds wait_deadline{500000};
68 bool run_worker_thread = true;
69};
70
72 std::uint64_t produced_frames = 0;
73 std::uint64_t miss_frames = 0;
74 std::uint64_t overrun_frames =
75 0;
76 std::uint64_t waits = 0;
77 std::uint64_t wait_timeouts = 0;
78 std::uint64_t events_in_dropped = 0;
79 std::uint64_t events_out_dropped = 0;
80 std::uint64_t late_events = 0;
81 std::uint64_t latency_changes = 0;
82 bool fast_pull = false;
83};
84
125template <typename Event = std::uint8_t> class AsyncBlockWorker {
126 static_assert(std::is_default_constructible_v<Event> &&
127 std::is_nothrow_copy_assignable_v<Event>,
128 "AsyncBlockWorker events are copied into fixed rings");
129
130 public:
132
135 class Chunk {
136 public:
137 std::uint64_t start = 0;
138 std::uint32_t frames = 0;
141 std::span<const TimedEvent> events;
142
145 bool emit(std::uint32_t offset, const Event& event) noexcept {
146 return owner_->push_out_event(start + std::min(offset, frames ? frames - 1 : 0u),
147 event);
148 }
149
150 private:
151 friend class AsyncBlockWorker;
152 AsyncBlockWorker* owner_ = nullptr;
153 };
154
155 using Job = std::function<void(Chunk&)>;
156 using LatencyListener = void (*)(void* context) noexcept;
157
158 AsyncBlockWorker() = default;
160 release();
161 }
164
168 release();
169 if (!job || config.max_block == 0 || config.output_channels == 0 ||
170 config.latency_blocks > config.max_latency_blocks || !(config.sample_rate > 0.0))
171 return false;
172 config_ = config;
173 if (config_.max_chunk == 0 || config_.max_chunk > config_.max_block)
174 config_.max_chunk = config_.max_block;
175 job_ = std::move(job);
176 // The worker writes no further ahead than the frames already
177 // submitted, and the callback reads no further back than one block
178 // plus the largest latency, so this span is never overwritten while
179 // it can still be played. The extra blocks give a lagging worker room
180 // before it overruns.
181 const std::uint64_t needed =
182 (static_cast<std::uint64_t>(config_.max_latency_blocks) + 4) * config_.max_block;
183 capacity_ = 1;
184 while (capacity_ < needed)
185 capacity_ <<= 1;
186 input_.assign(static_cast<std::size_t>(config_.input_channels) * capacity_, 0.0f);
187 output_.assign(static_cast<std::size_t>(config_.output_channels) * capacity_, 0.0f);
188 input_ptrs_.assign(config_.input_channels, nullptr);
189 output_ptrs_.assign(config_.output_channels, nullptr);
190 events_in_.assign(round_up(config_.max_events_in), Stamped{});
191 events_out_.assign(round_up(config_.max_events_out), Stamped{});
192 chunk_events_.assign(events_in_.size(), TimedEvent{});
193 reset_counters();
194 latency_blocks_ = config_.latency_blocks;
195 requested_latency_blocks_.store(config_.latency_blocks, std::memory_order_relaxed);
196 worker_latency_.store(config_.worker_latency_samples, std::memory_order_relaxed);
197 applied_worker_latency_ = config_.worker_latency_samples;
198 detector_.reset();
199 prepared_ = true;
200 if (config_.run_worker_thread) {
201 stop_.store(false, std::memory_order_relaxed);
202 thread_ = std::thread([this] { run(); });
203 }
204 return true;
205 }
206
208 void release() noexcept {
209 if (thread_.joinable()) {
210 stop_.store(true, std::memory_order_release);
211 work_.notify();
212 thread_.join();
213 }
214 prepared_ = false;
215 }
216
217 bool is_prepared() const noexcept {
218 return prepared_;
219 }
220 const AsyncBlockWorkerConfig& config() const noexcept {
221 return config_;
222 }
223
226 std::uint32_t latency_samples() const noexcept {
227 return requested_latency_blocks_.load(std::memory_order_acquire) * config_.max_block +
228 worker_latency_.load(std::memory_order_acquire);
229 }
230
233 bool request_latency_blocks(std::uint32_t blocks) noexcept {
234 if (blocks > config_.max_latency_blocks)
235 return false;
236 requested_latency_blocks_.store(blocks, std::memory_order_release);
237 return true;
238 }
239
241 void set_worker_latency_samples(std::uint32_t samples) noexcept {
242 worker_latency_.store(samples, std::memory_order_release);
243 }
244
247 void set_latency_listener(LatencyListener listener, void* context) noexcept {
248 listener_ = listener;
249 listener_context_ = context;
250 }
251
256 template <typename Emit>
258 std::uint32_t frames, std::span<const TimedEvent> events, Emit&& emit,
260 for (std::size_t c = 0; c < output.num_channels(); ++c)
261 std::fill_n(output.channel_ptr(c), std::min<std::size_t>(frames, output.num_samples()),
262 0.0f);
263 if (!prepared_ || frames == 0 || frames > config_.max_block)
264 return;
265 apply_latency_changes();
266
267 // Input: positions [submitted, submitted + frames).
268 const std::uint64_t submitted = submitted_.load(std::memory_order_relaxed);
269 const std::uint64_t produced_now = produced_.load(std::memory_order_acquire);
270 if (submitted + frames - produced_now > capacity_ - config_.max_block) {
271 // The worker fell a ring behind: let it skip to here so the
272 // timeline holds; what it skips plays as silence.
273 skip_to_.store(submitted, std::memory_order_release);
274 }
275 for (std::uint32_t c = 0; c < config_.input_channels; ++c) {
276 float* ring = input_.data() + static_cast<std::size_t>(c) * capacity_;
277 const float* source = c < input.num_channels() && input.num_samples() >= frames
278 ? input.channel_ptr(c)
279 : nullptr;
280 for (std::uint32_t i = 0; i < frames; ++i)
281 ring[(submitted + i) & (capacity_ - 1)] = source ? source[i] : 0.0f;
282 }
283 for (const auto& timed : events)
284 push_in_event(submitted + std::min(timed.offset, frames - 1), timed.event);
285 submitted_.store(submitted + frames, std::memory_order_release);
286 work_.notify();
287
288 // Output: positions [window_start, window_end).
289 const std::int64_t window_start =
290 static_cast<std::int64_t>(delivered_) -
291 static_cast<std::int64_t>(latency_blocks_) * config_.max_block;
292 const std::int64_t window_end = window_start + frames;
293 delivered_ += frames;
294
295 bool wait = latency_blocks_ == 0 || mode == AsyncBlockMode::Offline;
296 if (mode == AsyncBlockMode::Auto) {
297 const bool fast =
298 detector_.observe(frames, config_.sample_rate, PullRateDetector::Clock::now());
299 fast_pull_.store(fast, std::memory_order_relaxed);
300 wait = wait || fast;
301 }
302 if (wait && window_end > 0)
303 wait_until_produced(static_cast<std::uint64_t>(window_end));
304
305 const std::uint64_t produced = produced_.load(std::memory_order_acquire);
306 const std::uint64_t valid_from = valid_from_.load(std::memory_order_acquire);
307 std::uint64_t missed = 0;
308 for (std::int64_t p = std::max<std::int64_t>(window_start, 0); p < window_end; ++p) {
309 const auto position = static_cast<std::uint64_t>(p);
310 const auto target = static_cast<std::size_t>(p - window_start);
311 if (position < played_until_)
312 continue; // already played before a latency increase
313 if (position >= produced || position < valid_from) {
314 ++missed;
315 continue;
316 }
317 const std::size_t index = position & (capacity_ - 1);
318 for (std::size_t c = 0; c < output.num_channels() && c < config_.output_channels; ++c)
319 output.channel_ptr(c)[target] = output_[c * capacity_ + index];
320 }
321 if (missed)
322 miss_frames_.fetch_add(missed, std::memory_order_relaxed);
323
324 drain_out_events(window_start, window_end, emit);
325 }
326
331 std::uint32_t pump(std::uint32_t max_chunks = 0) noexcept {
332 if (!prepared_)
333 return 0;
334 std::uint32_t chunks = 0;
335 for (;;) {
336 if (max_chunks && chunks >= max_chunks)
337 break;
338 std::uint64_t produced = produced_.load(std::memory_order_relaxed);
339 const std::uint64_t skip = skip_to_.load(std::memory_order_acquire);
340 if (skip > produced) {
341 // Overrun: drop the input events of the skipped span and mark
342 // its output invalid instead of rendering stale ring data.
343 overrun_frames_.fetch_add(skip - produced, std::memory_order_relaxed);
344 discard_in_events_before(skip);
345 valid_from_.store(skip, std::memory_order_release);
346 produced_.store(skip, std::memory_order_release);
347 produced = skip;
348 }
349 const std::uint64_t submitted = submitted_.load(std::memory_order_acquire);
350 if (submitted <= produced)
351 break;
352 std::uint64_t frames = std::min<std::uint64_t>(submitted - produced, config_.max_chunk);
353 const std::uint64_t index = produced & (capacity_ - 1);
354 frames = std::min<std::uint64_t>(frames, capacity_ - index); // no wrap inside a chunk
355 render_chunk(produced, static_cast<std::uint32_t>(frames),
356 static_cast<std::size_t>(index));
357 produced_.store(produced + frames, std::memory_order_release);
358 produced_frames_.fetch_add(frames, std::memory_order_relaxed);
359 done_.notify();
360 ++chunks;
361 }
362 return chunks;
363 }
364
365 AsyncBlockWorkerStats stats() const noexcept {
367 s.produced_frames = produced_frames_.load(std::memory_order_relaxed);
368 s.miss_frames = miss_frames_.load(std::memory_order_relaxed);
369 s.overrun_frames = overrun_frames_.load(std::memory_order_relaxed);
370 s.waits = waits_.load(std::memory_order_relaxed);
371 s.wait_timeouts = wait_timeouts_.load(std::memory_order_relaxed);
372 s.events_in_dropped = events_in_dropped_.load(std::memory_order_relaxed);
373 s.events_out_dropped = events_out_dropped_.load(std::memory_order_relaxed);
374 s.late_events = late_events_.load(std::memory_order_relaxed);
375 s.latency_changes = latency_changes_.load(std::memory_order_relaxed);
376 s.fast_pull = fast_pull_.load(std::memory_order_relaxed);
377 return s;
378 }
379
380 private:
381 struct Stamped {
382 std::uint64_t position = 0;
383 Event event{};
384 };
385
386 static std::size_t round_up(std::uint32_t n) {
387 std::size_t size = 1;
388 while (size < std::max<std::uint32_t>(n, 1))
389 size <<= 1;
390 return size;
391 }
392
393 void reset_counters() noexcept {
394 submitted_.store(0, std::memory_order_relaxed);
395 produced_.store(0, std::memory_order_relaxed);
396 skip_to_.store(0, std::memory_order_relaxed);
397 valid_from_.store(0, std::memory_order_relaxed);
398 delivered_ = 0;
399 played_until_ = 0;
400 in_write_.store(0, std::memory_order_relaxed);
401 in_read_.store(0, std::memory_order_relaxed);
402 out_write_.store(0, std::memory_order_relaxed);
403 out_read_.store(0, std::memory_order_relaxed);
404 for (auto* counter :
405 {&produced_frames_, &miss_frames_, &overrun_frames_, &waits_, &wait_timeouts_,
406 &events_in_dropped_, &events_out_dropped_, &late_events_, &latency_changes_})
407 counter->store(0, std::memory_order_relaxed);
408 fast_pull_.store(false, std::memory_order_relaxed);
409 }
410
411 void apply_latency_changes() noexcept {
412 const auto blocks = requested_latency_blocks_.load(std::memory_order_acquire);
413 const auto worker = worker_latency_.load(std::memory_order_acquire);
414 if (blocks == latency_blocks_ && worker == applied_worker_latency_)
415 return;
416 if (blocks > latency_blocks_) {
417 // The window moves back over frames already played: play those
418 // as silence instead of a second time.
419 const auto played = static_cast<std::int64_t>(delivered_) -
420 static_cast<std::int64_t>(latency_blocks_) * config_.max_block;
421 if (played > 0)
422 played_until_ = std::max(played_until_, static_cast<std::uint64_t>(played));
423 }
424 latency_blocks_ = blocks;
425 applied_worker_latency_ = worker;
426 latency_changes_.fetch_add(1, std::memory_order_relaxed);
427 if (listener_)
428 listener_(listener_context_);
429 }
430
431 void wait_until_produced(std::uint64_t position) noexcept {
432 if (produced_.load(std::memory_order_acquire) >= position)
433 return;
434 waits_.fetch_add(1, std::memory_order_relaxed);
435 if (!config_.run_worker_thread) {
436 pump();
437 } else {
438 const bool done = done_.wait_for(
439 [&] { return produced_.load(std::memory_order_acquire) >= position; },
440 std::chrono::duration_cast<runtime::WakeSignal::Clock::duration>(
441 config_.wait_deadline));
442 if (!done)
443 wait_timeouts_.fetch_add(1, std::memory_order_relaxed);
444 }
445 }
446
447 void push_in_event(std::uint64_t position, const Event& event) noexcept {
448 const auto write = in_write_.load(std::memory_order_relaxed);
449 if (write - in_read_.load(std::memory_order_acquire) >= events_in_.size()) {
450 events_in_dropped_.fetch_add(1, std::memory_order_relaxed);
451 return;
452 }
453 events_in_[write & (events_in_.size() - 1)] = Stamped{position, event};
454 in_write_.store(write + 1, std::memory_order_release);
455 }
456
457 void discard_in_events_before(std::uint64_t position) noexcept {
458 auto read = in_read_.load(std::memory_order_relaxed);
459 const auto write = in_write_.load(std::memory_order_acquire);
460 while (read != write && events_in_[read & (events_in_.size() - 1)].position < position)
461 ++read;
462 in_read_.store(read, std::memory_order_release);
463 }
464
465 bool push_out_event(std::uint64_t position, const Event& event) noexcept {
466 const auto write = out_write_.load(std::memory_order_relaxed);
467 if (write - out_read_.load(std::memory_order_acquire) >= events_out_.size()) {
468 events_out_dropped_.fetch_add(1, std::memory_order_relaxed);
469 return false;
470 }
471 events_out_[write & (events_out_.size() - 1)] = Stamped{position, event};
472 out_write_.store(write + 1, std::memory_order_release);
473 return true;
474 }
475
476 template <typename Emit>
477 void drain_out_events(std::int64_t window_start, std::int64_t window_end, Emit& emit) noexcept {
478 auto read = out_read_.load(std::memory_order_relaxed);
479 const auto write = out_write_.load(std::memory_order_acquire);
480 while (read != write) {
481 const auto& stamped = events_out_[read & (events_out_.size() - 1)];
482 const auto position = static_cast<std::int64_t>(stamped.position);
483 if (position >= window_end)
484 break;
485 std::uint32_t offset = 0;
486 if (position >= window_start)
487 offset = static_cast<std::uint32_t>(position - window_start);
488 else
489 late_events_.fetch_add(1, std::memory_order_relaxed);
490 emit(offset, stamped.event);
491 ++read;
492 }
493 out_read_.store(read, std::memory_order_release);
494 }
495
496 void render_chunk(std::uint64_t start, std::uint32_t frames, std::size_t index) noexcept {
497 for (std::uint32_t c = 0; c < config_.input_channels; ++c)
498 input_ptrs_[c] = input_.data() + static_cast<std::size_t>(c) * capacity_ + index;
499 for (std::uint32_t c = 0; c < config_.output_channels; ++c) {
500 output_ptrs_[c] = output_.data() + static_cast<std::size_t>(c) * capacity_ + index;
501 std::fill_n(output_ptrs_[c], frames, 0.0f);
502 }
503 // Events in [start, start + frames), in position order.
504 std::size_t count = 0;
505 auto read = in_read_.load(std::memory_order_relaxed);
506 const auto write = in_write_.load(std::memory_order_acquire);
507 while (read != write && count < chunk_events_.size()) {
508 const auto& stamped = events_in_[read & (events_in_.size() - 1)];
509 if (stamped.position >= start + frames)
510 break;
511 chunk_events_[count++] = TimedEvent{
512 static_cast<std::uint32_t>(stamped.position > start ? stamped.position - start : 0),
513 stamped.event};
514 ++read;
515 }
516 in_read_.store(read, std::memory_order_release);
517
518 Chunk chunk;
519 chunk.owner_ = this;
520 chunk.start = start;
521 chunk.frames = frames;
522 chunk.input = BufferView<const float>(input_ptrs_.data(), config_.input_channels, frames);
523 chunk.output = BufferView<float>(output_ptrs_.data(), config_.output_channels, frames);
524 chunk.events = std::span<const TimedEvent>(chunk_events_.data(), count);
525 job_(chunk);
526 }
527
528 void run() noexcept {
529 while (!stop_.load(std::memory_order_acquire)) {
530 work_.wait_for(
531 [&] {
532 return stop_.load(std::memory_order_acquire) ||
533 submitted_.load(std::memory_order_acquire) >
534 produced_.load(std::memory_order_relaxed);
535 },
536 std::chrono::seconds(1));
537 if (stop_.load(std::memory_order_acquire))
538 break;
539 pump();
540 }
541 }
542
543 AsyncBlockWorkerConfig config_{};
544 Job job_;
545 bool prepared_ = false;
546 std::uint64_t capacity_ = 0;
547 std::vector<float> input_;
548 std::vector<float> output_;
549 std::vector<const float*> input_ptrs_;
550 std::vector<float*> output_ptrs_;
551 std::vector<Stamped> events_in_;
552 std::vector<Stamped> events_out_;
553 std::vector<TimedEvent> chunk_events_;
554
555 // Timeline. submitted_: audio thread writes; produced_ / valid_from_:
556 // worker writes; skip_to_: audio thread writes; delivered_: audio only.
557 std::atomic<std::uint64_t> submitted_{0};
558 std::atomic<std::uint64_t> produced_{0};
559 std::atomic<std::uint64_t> skip_to_{0};
560 std::atomic<std::uint64_t> valid_from_{0};
561 std::uint64_t delivered_ = 0;
562 std::uint64_t played_until_ = 0; // audio thread
563 std::atomic<std::uint64_t> in_write_{0}, in_read_{0}, out_write_{0}, out_read_{0};
564
565 std::uint32_t latency_blocks_ = 0; // audio thread
566 std::uint32_t applied_worker_latency_ = 0; // audio thread
567 std::atomic<std::uint32_t> requested_latency_blocks_{0};
568 std::atomic<std::uint32_t> worker_latency_{0};
569 LatencyListener listener_ = nullptr;
570 void* listener_context_ = nullptr;
571 PullRateDetector detector_;
572
573 std::atomic<std::uint64_t> produced_frames_{0}, miss_frames_{0}, overrun_frames_{0}, waits_{0},
574 wait_timeouts_{0}, events_in_dropped_{0}, events_out_dropped_{0}, late_events_{0},
575 latency_changes_{0};
576 std::atomic<bool> fast_pull_{false};
577
578 runtime::WakeSignal work_; // audio thread -> worker
579 runtime::WakeSignal done_; // worker -> a waiting audio thread
580 std::atomic<bool> stop_{false};
581 std::thread thread_;
582};
583
584} // namespace pulp::audio
One stretch of positions the job renders.
Definition async_block_worker.hpp:135
BufferView< const float > input
Definition async_block_worker.hpp:139
bool emit(std::uint32_t offset, const Event &event) noexcept
Emits an output event at offset within this chunk.
Definition async_block_worker.hpp:145
std::uint64_t start
stream position of frame 0
Definition async_block_worker.hpp:137
std::uint32_t frames
Definition async_block_worker.hpp:138
BufferView< float > output
zeroed before the job runs
Definition async_block_worker.hpp:140
std::span< const TimedEvent > events
Definition async_block_worker.hpp:141
friend class AsyncBlockWorker
Definition async_block_worker.hpp:151
AsyncBlockWorker(const AsyncBlockWorker &)=delete
AsyncBlockWorker & operator=(const AsyncBlockWorker &)=delete
void process(const BufferView< const float > &input, BufferView< float > &output, std::uint32_t frames, std::span< const TimedEvent > events, Emit &&emit, AsyncBlockMode mode=AsyncBlockMode::Realtime) noexcept
The audio callback.
Definition async_block_worker.hpp:257
std::function< void(Chunk &)> Job
Definition async_block_worker.hpp:155
~AsyncBlockWorker()
Definition async_block_worker.hpp:159
void(*)(void *context) noexcept LatencyListener
Definition async_block_worker.hpp:156
bool prepare(const AsyncBlockWorkerConfig &config, Job job)
Control thread.
Definition async_block_worker.hpp:167
bool request_latency_blocks(std::uint32_t blocks) noexcept
Any thread.
Definition async_block_worker.hpp:233
void set_worker_latency_samples(std::uint32_t samples) noexcept
Any thread (typically the job, when its own latency moves).
Definition async_block_worker.hpp:241
std::uint32_t pump(std::uint32_t max_chunks=0) noexcept
Worker step: renders every submitted position not yet produced, at most max_chunks chunks (0: all).
Definition async_block_worker.hpp:331
void set_latency_listener(LatencyListener listener, void *context) noexcept
Called on the audio thread, inside process(), when the reported latency changed.
Definition async_block_worker.hpp:247
void release() noexcept
Control thread. Stops and joins the worker thread.
Definition async_block_worker.hpp:208
bool is_prepared() const noexcept
Definition async_block_worker.hpp:217
AsyncBlockWorkerStats stats() const noexcept
Definition async_block_worker.hpp:365
AsyncBlockEvent< Event > TimedEvent
Definition async_block_worker.hpp:131
std::uint32_t latency_samples() const noexcept
The latency to report to the host: the block latency plus the worker's own.
Definition async_block_worker.hpp:226
const AsyncBlockWorkerConfig & config() const noexcept
Definition async_block_worker.hpp:220
Non-owning view over multi-channel audio sample data.
Definition buffer.hpp:30
Definition analyzer_provider.hpp:12
AsyncBlockMode
How a process() call may treat a worker that has not finished the audio the callback is about to play...
Definition async_block_worker.hpp:25
@ Auto
Realtime, except while a PullRateDetector sees the host pulling faster than real time without declari...
Definition async_block_worker.hpp:34
@ Offline
Declared offline render: wait for the worker (bounded by the configured deadline),...
Definition async_block_worker.hpp:31
@ Realtime
Live callback: never wait.
Definition async_block_worker.hpp:28
@ count
sentinel: number of real capabilities (keep LAST)
Definition canvas_capability.hpp:38
Definition child_process_manager.hpp:18
bool emit(MidiBuffer &output, const MidiEvent &event, MidiUtilityProcessReport &report)
Definition utility_contract.hpp:67
@ c
Definition pitch.hpp:20
std::uint64_t read(std::span< const std::uint8_t > b, std::size_t p, unsigned n) noexcept
Definition spectral_frame_codec.hpp:111
void write(std::uint8_t *b, std::size_t p, std::uint64_t v, unsigned n) noexcept
Definition spectral_frame_codec.hpp:117
@ start
Definition geometry.hpp:370
Tells whether a host is pulling audio faster than real time.
A timed event handed to or produced by the worker.
Definition async_block_worker.hpp:39
std::uint32_t offset
Definition async_block_worker.hpp:40
Definition async_block_worker.hpp:44
std::uint32_t input_channels
0 for an instrument
Definition async_block_worker.hpp:46
std::uint32_t max_chunk
The longest chunk handed to the job; 0 means max_block.
Definition async_block_worker.hpp:56
std::chrono::microseconds wait_deadline
Longest a waiting process() call blocks before it plays what exists.
Definition async_block_worker.hpp:64
std::uint32_t max_latency_blocks
The largest value request_latency_blocks() may later set.
Definition async_block_worker.hpp:54
bool run_worker_thread
Run an internal worker thread.
Definition async_block_worker.hpp:68
std::uint32_t max_events_out
Definition async_block_worker.hpp:59
double sample_rate
Definition async_block_worker.hpp:45
std::uint32_t worker_latency_samples
Latency the worker's own processing adds (a resampler, a look-ahead), reported on top of the block la...
Definition async_block_worker.hpp:62
std::uint32_t max_events_in
Capacity of the event rings, in events in flight each way.
Definition async_block_worker.hpp:58
std::uint32_t latency_blocks
Latency in blocks of max_block.
Definition async_block_worker.hpp:52
std::uint32_t output_channels
Definition async_block_worker.hpp:47
std::uint32_t max_block
The largest block the host may send; also the unit of latency.
Definition async_block_worker.hpp:49
Definition async_block_worker.hpp:71
std::uint64_t late_events
output events delivered at offset 0 past their frame
Definition async_block_worker.hpp:80
bool fast_pull
Auto: the detector's last verdict.
Definition async_block_worker.hpp:82
std::uint64_t events_out_dropped
Definition async_block_worker.hpp:79
std::uint64_t miss_frames
frames played as silence because unfinished
Definition async_block_worker.hpp:73
std::uint64_t waits
process() calls that waited for the worker
Definition async_block_worker.hpp:76
std::uint64_t produced_frames
frames the worker rendered
Definition async_block_worker.hpp:72
std::uint64_t wait_timeouts
waits that hit the deadline
Definition async_block_worker.hpp:77
std::uint64_t latency_changes
reported-latency changes signalled
Definition async_block_worker.hpp:81
std::uint64_t overrun_frames
input frames the worker skipped after falling a ring behind
Definition async_block_worker.hpp:74
std::uint64_t events_in_dropped
input events lost to a full ring
Definition async_block_worker.hpp:78
Wakes a sleeping consumer thread from a realtime producer without a lock, and without a system call w...