pub trait PluginLogic: Send + 'static {
type Params: Params;
// Required methods
fn reset(&mut self, sample_rate: f64, max_block_size: usize);
fn process(
&mut self,
buffer: &mut AudioBuffer<'_>,
events: &EventList,
context: &mut ProcessContext<'_>,
) -> ProcessStatus;
fn editor(params: Arc<Self::Params>) -> Box<dyn Editor>;
// Provided methods
fn supports_in_place() -> bool
where Self: Sized { ... }
fn bus_layouts() -> Vec<BusLayout>
where Self: Sized { ... }
fn save_state(&self) -> Vec<u8> ⓘ { ... }
fn snapshot_into(&self, buf: &mut Vec<u8>) -> bool { ... }
fn load_state(&mut self, _data: &[u8]) -> Result<(), StateLoadError> { ... }
fn state_changed(&mut self) { ... }
fn migrate_state(_foreign: &ForeignState<'_>) -> Option<MigratedState>
where Self: Sized { ... }
fn latency(&self) -> u32 { ... }
fn tail(&self) -> u32 { ... }
}Expand description
The f32-buffer user-facing plugin trait.
Plugin authors implement this in a single impl block when
their audio path is f32 end-to-end (the default - matches
the host wire format for nearly all DAWs and formats).
truce::prelude and truce::prelude32 re-export this name
directly; truce::prelude64m does too (the m mixed-precision
prelude keeps the audio buffer at f32 and only switches the
param.read() precision).
Required: Self::reset, Self::process, Self::editor.
Everything else has a default. The editor is constructed
explicitly - layout-only plugins typically call
truce_gui::default_editor(params, layout()) (where layout()
is a plain inherent method on the plugin struct, not part of the
trait).
§Params vs. DSP state
The struct you implement this on holds two different kinds of data, and the method receivers reflect the split:
- Params - the user-facing values in your
#[derive(Params)]struct, held asArc<Self::Params>. Atomic-backed andSync, shared lock-free with the host and the editor. - DSP state - everything else on the struct: filter memory, phase accumulators, voice buffers, delay lines. Plain and non-atomic, mutated every sample, exclusive to the audio thread.
process / reset / load_state take &mut self because they
mutate DSP state; save_state / snapshot_into take &self
because they read it. editor takes neither - it is an
associated function over the param store, because a GUI is a
view that binds only params (plus lock-free meters / transport)
and never touches DSP state, so it can be built without the
plugin lock. DSP state can’t move into params: making per-sample
filter memory atomic-shared would put a synchronized access on
the hottest path, and it isn’t a “parameter” anyway.
Required Associated Types§
Sourcetype Params: Params
type Params: Params
The plugin’s parameter struct (#[derive(Params)]). Named
here so the editor can be built from an Arc<Self::Params>
without borrowing the plugin - see Self::editor.
Required Methods§
Sourcefn reset(&mut self, sample_rate: f64, max_block_size: usize)
fn reset(&mut self, sample_rate: f64, max_block_size: usize)
Reset for a new sample rate / block size. Called before
the first process and any time the host reconfigures.
Sourcefn process(
&mut self,
buffer: &mut AudioBuffer<'_>,
events: &EventList,
context: &mut ProcessContext<'_>,
) -> ProcessStatus
fn process( &mut self, buffer: &mut AudioBuffer<'_>, events: &EventList, context: &mut ProcessContext<'_>, ) -> ProcessStatus
Process one block of audio. Real-time - no allocations, locks, or I/O.
Sourcefn editor(params: Arc<Self::Params>) -> Box<dyn Editor>
fn editor(params: Arc<Self::Params>) -> Box<dyn Editor>
Construct the editor for this plugin. Required.
There is no auto-fallback - every plugin explicitly
names which renderer it wants. For the built-in
widget layout, call
truce_gui::default_editor(params, layout); for
custom renderers, construct an EguiEditor /
IcedEditor / SlintEditor / hand-rolled Editor
here. The choice of renderer crate the plugin’s
Cargo.toml pulls IS the choice of editor.
An associated function, not a method: it receives the
lock-free Arc<Self::Params> store the wrapper already
holds, so the host can open the editor while audio is
running without ever taking the plugin lock. Editors bind
only to the param store (plus meters / transport, all
lock-free); custom DSP state is read at runtime through
the editor bridge, not at construction.
Provided Methods§
Sourcefn supports_in_place() -> boolwhere
Self: Sized,
fn supports_in_place() -> boolwhere
Self: Sized,
Opt into zero-copy in-place I/O. When this returns true,
the format wrapper skips its safety memcpy on host-aliased
buffers and hands the plugin the raw shared memory through
AudioBuffer::in_out_mut(ch). The plugin must check
AudioBuffer::is_in_place(ch) per channel before reading
input(ch).
Default false: the wrapper copies aliased inputs into
scratch so input(ch) and output(ch) are always
disjoint. Costs one memcpy per aliased channel per block.
Sourcefn bus_layouts() -> Vec<BusLayout>where
Self: Sized,
fn bus_layouts() -> Vec<BusLayout>where
Self: Sized,
Supported audio bus configurations. The host picks one;
the others are rejected at bus-config time before
process is ever called. Default: stereo in, stereo out.
Sourcefn save_state(&self) -> Vec<u8> ⓘ
fn save_state(&self) -> Vec<u8> ⓘ
Serialize plugin-specific state (DSP state, not params -
those are saved automatically). Default: delegates to
Self::snapshot_into (empty when neither is
overridden).
Runs on a host or GUI thread while the audio thread is
paused at a block boundary (the wrapper’s plugin lock),
so reading any field is safe - but an audio block that
arrives mid-save waits for this to return. Keep it
cheap: copy bytes out, don’t compute or compress here.
To take this off the plugin lock entirely, override
Self::snapshot_into instead.
Sourcefn snapshot_into(&self, buf: &mut Vec<u8>) -> bool
fn snapshot_into(&self, buf: &mut Vec<u8>) -> bool
Opt into lock-free state save. Serialize the same bytes
Self::save_state would into buf (cleared first;
capacity is retained across calls so a steady state is
allocation-free).
The return value is a static capability, not a
per-block flag: true means “this plugin publishes
snapshots”, false means “it never does” (the default).
Once you return true you must return true for the
plugin’s whole lifetime - if the custom state empties out,
clear buf and still return true (an empty blob), don’t
return false. The shell latches the opt-in on the first
published block; a later false is a contract violation
that would otherwise leave the host reading a stale
snapshot forever.
Called on the audio thread after each process block,
under the same real-time rules as process - bounded, no
unbounded allocation. The wrapper publishes the result
into a lock-free slot the host reads without ever taking
the plugin lock, so saving state while audio runs never
stalls the audio thread. Overriding this is the
preferred way to serialize custom state; the default
Self::save_state delegates here.
Sourcefn load_state(&mut self, _data: &[u8]) -> Result<(), StateLoadError>
fn load_state(&mut self, _data: &[u8]) -> Result<(), StateLoadError>
Restore plugin-specific state.
Runs on the audio thread between blocks, with the same
exclusive access process() has - writing any field
is safe.
§Errors
Return Err(StateLoadError) when the blob is malformed
or otherwise can’t be interpreted - the format wrapper
logs the failure (and on hosts that support it, surfaces
it to the DAW).
Sourcefn state_changed(&mut self)
fn state_changed(&mut self)
Called on the audio thread immediately after
Self::load_state returns. Invalidate or recompute any
caches the next process() reads. Default: no-op.
Sourcefn migrate_state(_foreign: &ForeignState<'_>) -> Option<MigratedState>where
Self: Sized,
fn migrate_state(_foreign: &ForeignState<'_>) -> Option<MigratedState>where
Self: Sized,
Translate foreign state - a previous framework’s blob,
or a truce envelope saved under a different plugin id -
into truce params + extra, so a plugin ported to truce
keeps its users’ old sessions and presets. Runs on the
host thread; receiverless so it can’t touch (or alias)
the live instance. Return None for bytes you don’t
recognize - the wrapper then reports load failure to
the host, exactly as if this hook didn’t exist.
One-shot by construction: the next save writes a normal
truce envelope, so this never becomes a permanent
dual-format reader. Keyed formats (AU / LV2 / AAX) only
see foreign bytes when truce.toml declares the legacy
keys to probe ([plugin.legacy_state]).
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".