pub struct RealtimePluginRunner { /* private fields */ }Expand description
Owns a Plugin on the audio thread and applies queued control commands before each
process block. Pair with an RtControl (returned from Self::new) to drive it from
other threads.
§Real-time safety
In steady state process is allocation-free and Drop-free: once
warmed up it performs no heap allocation, reallocation, or free per block, even while
parameter changes and MIDI (in and out) are flowing. This holds under two conditions:
- Fixed buffer size — pass an
AudioBufferssized to the configured block size and don’t resize it between calls (a smaller block is fine; growth reallocates). - In-process — the runner hosts the plugin in-process; the process-isolation path marshals audio over IPC and is not allocation-free.
This is verified by tests/alloc_tests.rs (a counting global allocator asserts zero
alloc/realloc/free over a steady-state run driving parameters and MIDI). The host cannot
guarantee the plugin’s own process() is allocation-free — that is the plugin’s
responsibility; the guarantee is about the host code around it.
§Threading model
Queued parameter and mapped-MIDI commands populate the processor’s input parameter queues on
the audio thread and park the same values for IEditController, which is a main-thread-domain
interface: the plugin applies them when a control thread next touches it (see
RtControl::set_parameter). No controller call is ever made from this runner.
It is not yet fully lock-free: process still takes a few short, uncontended mutexes
per block (the parameter-change and event queues, and the level meter). They are uncontended
while the runner owns the plugin, but a hard-real-time deployment should treat lock removal
as pending work. Output MIDI is already lock-free, though: take a
OutputMidiConsumer via
Plugin::output_midi_handle before moving the plugin
into the runner, then drain emitted events from your UI thread while the audio thread pushes.
Implementations§
Source§impl RealtimePluginRunner
impl RealtimePluginRunner
Sourcepub fn new(plugin: Plugin, command_capacity: usize) -> (Self, RtControl)
pub fn new(plugin: Plugin, command_capacity: usize) -> (Self, RtControl)
Build a runner that owns plugin, plus the RtControl handle to drive it.
command_capacity is the maximum number of MIDI/parameter commands that can be
queued between two process calls; pushes beyond it are dropped
(reported by the RtControl methods returning false). Size it for your block rate
and worst-case control burst (e.g. 1024).
Call this on the same control thread that loaded the plugin. If the runner is later
dropped on an audio thread, the plugin is handed back to the returned RtControl for
destruction on this thread. Call RtControl::service_teardown after the runner has
stopped, or drop the control on this thread.
Sourcepub fn process(&mut self, buffers: &mut AudioBuffers) -> Result<()>
pub fn process(&mut self, buffers: &mut AudioBuffers) -> Result<()>
Drain queued control commands and render one block.
Call this from the audio thread (e.g. inside your device callback). It performs only the lock-free queue drain plus the plugin’s own processing — it never blocks on a lock a control thread could hold.
The drain is bounded by the command queue’s capacity. A control thread pushing in a tight loop refills the queue as fast as this drains it, so an unbounded drain would pin the audio callback; anything still queued is applied on the next block instead.
Sourcepub fn plugin(&self) -> &Plugin
pub fn plugin(&self) -> &Plugin
Borrow the underlying plugin (e.g. to read parameters or info). Do not call this from the audio thread while another thread might also touch the plugin.
Sourcepub fn into_plugin(self) -> Plugin
pub fn into_plugin(self) -> Plugin
Recover the owned plugin, consuming the runner.