Plays audio that a worker thread renders, at an exact, fixed latency, for a host whose blocks vary in size.
More...
|
| | AsyncBlockWorker ()=default |
| | ~AsyncBlockWorker () |
| | AsyncBlockWorker (const AsyncBlockWorker &)=delete |
| AsyncBlockWorker & | operator= (const AsyncBlockWorker &)=delete |
| bool | prepare (const AsyncBlockWorkerConfig &config, Job job) |
| | Control thread.
|
| void | release () noexcept |
| | Control thread. Stops and joins the worker thread.
|
| bool | is_prepared () const noexcept |
| const AsyncBlockWorkerConfig & | config () const noexcept |
| std::uint32_t | latency_samples () const noexcept |
| | The latency to report to the host: the block latency plus the worker's own.
|
| bool | request_latency_blocks (std::uint32_t blocks) noexcept |
| | Any thread.
|
| void | set_worker_latency_samples (std::uint32_t samples) noexcept |
| | Any thread (typically the job, when its own latency moves).
|
| void | set_latency_listener (LatencyListener listener, void *context) noexcept |
| | Called on the audio thread, inside process(), when the reported latency changed.
|
| template<typename Emit> |
| 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.
|
| 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).
|
| AsyncBlockWorkerStats | stats () const noexcept |
template<typename Event = std::uint8_t>
class pulp::audio::AsyncBlockWorker< Event >
Plays audio that a worker thread renders, at an exact, fixed latency, for a host whose blocks vary in size.
Every frame has a stream position. process() writes the host block's input (and timed events) at positions [S, S + n), then plays the output of positions [D - L, D - L + n), where D counts the frames delivered so far and L = latency_blocks * max_block. The worker renders the positions in order, in chunks of its own size, into a ring addressed by position. Because latency is defined on positions, not on calls, it is exact for any block-size sequence (1 frame, 4096 frames, alternating) and cannot drift: a frame the worker has not finished in time plays as silence (counted as a miss) and its late output is never played, rather than shifting what follows. Positions before the first frame play as silence (pre-roll).
Events go to the worker with their absolute position and come back the same way, so an event the job emits at chunk offset k is delivered in the output callback that plays position start + k, at the matching offset. An output event whose frame has already been played is delivered at offset 0 of the next call (counted late) rather than dropped: a note-off must not be lost.
Threads:
- prepare() / release(): control thread, no process() or pump() running.
- process(): the audio thread. No allocation and no lock; it posts the worker through runtime::WakeSignal (a system call only while the worker sleeps). It blocks only in Offline mode, in Auto while the host pulls faster than real time, or at zero latency, and then never longer than wait_deadline.
- The job: the worker thread (or the pump() caller), one chunk at a time.
- request_latency_blocks() / set_worker_latency_samples(): any thread. A change takes effect at the start of the next process() call, which then calls the latency listener on the audio thread (a processor's flag_latency_changed() is the intended listener; it is realtime safe). A larger latency never replays audio: the frames the window moves back over were already played and play as silence once; a smaller one skips frames. Hosts expect a glitch when a plug-in's latency changes.
GpuAudioTransport keeps the same timeline for the special case of one fixed block size: its primed output ring is pre-roll, its latency_blocks is L / max_block, and its resync after a miss is the position rule above.