Skip to main content

RealtimePluginRunner

Struct RealtimePluginRunner 

Source
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 AudioBuffers sized 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

Source

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.

Source

pub fn start(&mut self) -> Result<()>

Begin processing. Call once before the first process.

Source

pub fn stop(&mut self) -> Result<()>

Stop processing.

Source

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.

Source

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.

Source

pub fn into_plugin(self) -> Plugin

Recover the owned plugin, consuming the runner.

Trait Implementations§

Source§

impl Drop for RealtimePluginRunner

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<S> FromSample<S> for S

Source§

fn from_sample_(s: S) -> S

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

Source§

fn to_sample_(self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.