pub trait Processor: HasParameters {
type Descriptor: Descriptor<Processor = Self, Parameters = Self::Parameters>;
// Required method
fn process(
&mut self,
buffer: &mut Buffer<'_>,
aux: &mut AuxiliaryBuffers<'_>,
context: &ProcessContext<'_>,
);
// Provided methods
fn unprepare(self) -> Self::Descriptor
where Self: Sized { ... }
fn set_active(&mut self, _active: bool) { ... }
fn tail_samples(&self) -> u32 { ... }
fn latency_samples(&self) -> u32 { ... }
fn bypass_ramp_samples(&self) -> u32 { ... }
fn supports_double_precision(&self) -> bool { ... }
fn process_f64(
&mut self,
buffer: &mut Buffer<'_, f64>,
_aux: &mut AuxiliaryBuffers<'_, f64>,
context: &ProcessContext<'_>,
) { ... }
fn save_state(&self) -> PluginResult<Vec<u8>> { ... }
fn load_state(&mut self, data: &[u8]) -> PluginResult<()> { ... }
fn process_midi(&mut self, input: &[MidiEvent], output: &mut MidiBuffer) { ... }
fn wants_midi(&self) -> bool { ... }
}Expand description
The prepared processor - ready for audio processing.
This trait defines the DSP (Digital Signal Processing) interface that plugin implementations must provide. It is designed to be format-agnostic, meaning the same implementation can be wrapped for AU, VST3 or other plugin formats.
A Processor is created by calling Descriptor::prepare() with the
audio configuration. Unlike the old design where setup() was called
after construction, here the processor is created with valid configuration
from the start - no placeholder values.
§Lifecycle
Descriptor::default() -> Descriptor (unprepared, holds parameters)
|
v Descriptor::prepare(config)
|
v
Processor (prepared, ready for audio)
|
v Processor::unprepare()
|
v
Descriptor (unprepared, parameters preserved)§Thread Safety
Implementors must be Send because the plugin may be moved between threads.
The process method is called on the audio thread and must be real-time safe:
- No allocations
- No locks (use lock-free structures)
- No syscalls
- No unbounded loops
§Note on HasParameters
The Processor trait requires HasParameters as a supertrait, which provides
the parameters() and parameters_mut() methods. Use #[derive(HasParameters)] with a
#[parameters] field annotation to implement this automatically.
Required Associated Types§
Sourcetype Descriptor: Descriptor<Processor = Self, Parameters = Self::Parameters>
type Descriptor: Descriptor<Processor = Self, Parameters = Self::Parameters>
The unprepared definition type that created this processor.
Used by Processor::unprepare() to return to the unprepared state.
The Parameters type must match the definition’s Parameters type.
Required Methods§
Sourcefn process(
&mut self,
buffer: &mut Buffer<'_>,
aux: &mut AuxiliaryBuffers<'_>,
context: &ProcessContext<'_>,
)
fn process( &mut self, buffer: &mut Buffer<'_>, aux: &mut AuxiliaryBuffers<'_>, context: &ProcessContext<'_>, )
Process an audio buffer with transport context.
This is the main DSP entry point, called on the audio thread for each block of audio. The buffer provides input samples and mutable output buffers for the main bus.
§Arguments
buffer- Main audio bus (stereo/surround input and output)aux- Auxiliary buses (sidechain, aux sends) - ignore if not neededcontext- Processing context with sample rate, buffer size, and transport info
§Real-Time Safety
This method must be real-time safe. Do not allocate, lock mutexes, or perform any operation with unbounded execution time.
§Example: Simple Gain
fn process(&mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, _context: &ProcessContext) {
let gain = self.parameters.gain();
for (input, output) in buffer.zip_channels() {
for (i, o) in input.iter().zip(output.iter_mut()) {
*o = *i * gain;
}
}
}§Example: Tempo-Synced LFO
fn process(&mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, context: &ProcessContext) {
// Calculate LFO rate synced to host tempo
let lfo_hz = context.transport.tempo
.map(|tempo| tempo / 60.0 / 4.0) // 1 cycle per 4 beats
.unwrap_or(2.0); // Fallback: 2 Hz
let increment = (lfo_hz * 2.0 * std::f32::consts::PI) / context.sample_rate as f32;
for (input, output) in buffer.zip_channels() {
for (i, o) in input.iter().zip(output.iter_mut()) {
let lfo = self.phase.sin();
*o = *i * (1.0 + lfo * 0.5);
self.phase += increment;
}
}
}§Example: Sidechain Ducker
fn process(&mut self, buffer: &mut Buffer, aux: &mut AuxiliaryBuffers, _context: &ProcessContext) {
let duck = aux.sidechain()
.map(|sc| (sc.rms(0) * 4.0).min(1.0))
.unwrap_or(0.0);
buffer.copy_to_output();
buffer.apply_output_gain(1.0 - duck * 0.8);
}Provided Methods§
Sourcefn unprepare(self) -> Self::Descriptorwhere
Self: Sized,
fn unprepare(self) -> Self::Descriptorwhere
Self: Sized,
Return to the unprepared definition state.
This is used when sample rate or buffer configuration changes.
The processor is consumed and returns the original definition with
parameters preserved. The wrapper can then call prepare() again
with the new configuration.
§Default Implementation
The default implementation creates a new Descriptor::default() and
transfers the parameters from this processor to it. This works for
most plugins where the Descriptor struct only holds parameters.
Override this method if your Descriptor has additional state beyond parameters that needs to be preserved across unprepare/prepare cycles.
§Example (custom implementation)
impl Processor for DelayProcessor {
type Descriptor = DelayPlugin;
fn unprepare(self) -> DelayPlugin {
DelayPlugin {
parameters: self.parameters,
// DSP state (delay_lines, etc.) is discarded
}
}
}Sourcefn set_active(&mut self, _active: bool)
fn set_active(&mut self, _active: bool)
Called when the plugin is activated or deactivated.
Activation typically happens when the user inserts the plugin into a track or opens a project. Deactivation happens when removed or project is closed.
Important: When active == true, you should reset your DSP state
(clear delay lines, reset filter histories, zero envelopes, etc.).
Hosts call setActive(false) followed by setActive(true) to request
a full state reset.
§Example
fn set_active(&mut self, active: bool) {
if active {
// Reset DSP state on activation
self.delay_line.clear();
self.envelope.reset();
self.filter_state = FilterState::default();
}
}Default implementation does nothing.
Sourcefn tail_samples(&self) -> u32
fn tail_samples(&self) -> u32
Get the tail length in samples.
This indicates how many samples of audio “tail” the plugin produces
after input stops (e.g., reverb decay). Return 0 for no tail, or
u32::MAX for infinite tail.
Default returns 0 (no tail).
Sourcefn latency_samples(&self) -> u32
fn latency_samples(&self) -> u32
Get the latency in samples.
If the plugin introduces processing latency (e.g., lookahead limiters), return the latency in samples here. The host can use this for delay compensation.
Default returns 0 (no latency).
Sourcefn bypass_ramp_samples(&self) -> u32
fn bypass_ramp_samples(&self) -> u32
Get the bypass ramp length in samples.
When bypass is engaged or disengaged, this defines the crossfade
duration to avoid clicks. The host uses this value (combined with
tail_samples()) to determine how long to continue calling process()
after input stops.
Return 0 for instant bypass (no crossfade), or a sample count for smooth crossfading. Typical values:
- 64 samples (~1.3ms at 48kHz) - fast, suitable for most effects
- 256 samples (~5.3ms at 48kHz) - smoother, for sensitive material
- 512+ samples - very smooth, for reverbs/delays with long tails
Default returns 64 samples.
§Example
fn bypass_ramp_samples(&self) -> u32 {
// Use 10ms crossfade based on current sample rate
(self.sample_rate * 0.01) as u32
}Sourcefn supports_double_precision(&self) -> bool
fn supports_double_precision(&self) -> bool
Returns true if the plugin supports native 64-bit (double precision) processing.
Override this to return true if your plugin implements process_f64() natively.
When false (default), the framework will automatically convert 64-bit host buffers
to 32-bit, call process(), and convert back.
§Performance Considerations
- For most plugins, f32 is sufficient and the default conversion is fine
- Implement native f64 only if your DSP algorithm benefits from double precision (e.g., IIR filters with long decay, precision-sensitive synthesis)
- The conversion overhead is minimal (~few microseconds per buffer)
Default returns false.
Sourcefn process_f64(
&mut self,
buffer: &mut Buffer<'_, f64>,
_aux: &mut AuxiliaryBuffers<'_, f64>,
context: &ProcessContext<'_>,
)
fn process_f64( &mut self, buffer: &mut Buffer<'_, f64>, _aux: &mut AuxiliaryBuffers<'_, f64>, context: &ProcessContext<'_>, )
Process an audio buffer at 64-bit (double) precision.
This is the f64 equivalent of process(). Override this method AND
return true from supports_double_precision() to enable native
64-bit processing.
If supports_double_precision() returns false, this method is never
called - the framework converts to f32 and calls process() instead.
§Default Implementation
The default implementation converts f64→f32, calls process(), then
converts f32→f64. This allows any plugin to work in a 64-bit host
without modification.
§Example: Native f64 Plugin
fn supports_double_precision(&self) -> bool {
true
}
fn process_f64(
&mut self,
buffer: &mut Buffer<f64>,
aux: &mut AuxiliaryBuffers<f64>,
context: &ProcessContext,
) {
let gain = self.parameters.gain_linear() as f64;
for (input, output) in buffer.zip_channels() {
for (i, o) in input.iter().zip(output.iter_mut()) {
*o = *i * gain;
}
}
}Sourcefn save_state(&self) -> PluginResult<Vec<u8>>
fn save_state(&self) -> PluginResult<Vec<u8>>
Save the plugin state to bytes.
This is called when the DAW saves a project or preset. The returned bytes should contain all state needed to restore the plugin to its current configuration.
The default implementation delegates to Parameters::save_state(),
which serializes all parameter values. Override this method if you
need to save additional state beyond parameters.
Sourcefn load_state(&mut self, data: &[u8]) -> PluginResult<()>
fn load_state(&mut self, data: &[u8]) -> PluginResult<()>
Load the plugin state from bytes.
This is called when the DAW loads a project or preset. The data is
the same bytes returned from a previous save_state call.
The default implementation delegates to Parameters::load_state(),
which restores all parameter values. Override this method if you
need to load additional state beyond parameters.
Sourcefn process_midi(&mut self, input: &[MidiEvent], output: &mut MidiBuffer)
fn process_midi(&mut self, input: &[MidiEvent], output: &mut MidiBuffer)
Process MIDI events.
Called during processing with any incoming MIDI events. Plugins can transform events and add them to the output buffer, pass them through unchanged, or consume them entirely.
§Arguments
input- Slice of incoming MIDI events (sorted by sample_offset)output- Buffer to write output MIDI events to
§Real-Time Safety
This method must be real-time safe. Do not allocate, lock mutexes, or perform any operation with unbounded execution time.
Note: Cloning a SysEx event allocates due to Box<SysEx>. SysEx
events are rare in typical use cases. If strict real-time safety is
required, override this method to handle SysEx specially.
§Default Implementation
The default implementation passes all events through unchanged.
Sourcefn wants_midi(&self) -> bool
fn wants_midi(&self) -> bool
Returns whether this plugin processes MIDI events.
Override to return true if your plugin needs MIDI input/output.
This is used by the host to determine event bus configuration.
Default returns false.