AudioProcessor

Trait AudioProcessor 

Source
pub trait AudioProcessor: HasParams {
    type Plugin: Plugin<Processor = Self, Params = Self::Params>;

    // Required methods
    fn process(
        &mut self,
        buffer: &mut Buffer<'_>,
        aux: &mut AuxiliaryBuffers<'_>,
        context: &ProcessContext<'_>,
    );
    fn unprepare(self) -> Self::Plugin
       where Self: Sized;

    // Provided methods
    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 VST3, CLAP, or other plugin formats.

An AudioProcessor is created by calling Plugin::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

Plugin::default() -> Plugin (unprepared, holds params)
                     |
                     v  Plugin::prepare(config)
                     |
                     v
               AudioProcessor (prepared, ready for audio)
                     |
                     v  AudioProcessor::unprepare()
                     |
                     v
                Plugin (unprepared, params 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 HasParams

The AudioProcessor trait requires HasParams as a supertrait, which provides the params() and params_mut() methods. Use #[derive(HasParams)] with a #[params] field annotation to implement this automatically.

Required Associated Types§

Source

type Plugin: Plugin<Processor = Self, Params = Self::Params>

The unprepared plugin type that created this processor.

Used by AudioProcessor::unprepare() to return to the unprepared state. The Params type must match the plugin’s Params type.

Required Methods§

Source

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 needed
  • context - 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.params.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);
}
Source

fn unprepare(self) -> Self::Plugin
where Self: Sized,

Return to the unprepared plugin state.

This is used when sample rate or buffer configuration changes. The processor is consumed and returns the original plugin with parameters preserved. The wrapper can then call prepare() again with the new configuration.

§Example
impl AudioProcessor for DelayProcessor {
    type Plugin = DelayPlugin;

    fn unprepare(self) -> DelayPlugin {
        DelayPlugin {
            params: self.params,
            // DSP state (delay_lines, etc.) is discarded
        }
    }
}

Provided Methods§

Source

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.

Source

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).

Source

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).

Source

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
}
Source

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.

Source

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.params.gain_linear() as f64;
    for (input, output) in buffer.zip_channels() {
        for (i, o) in input.iter().zip(output.iter_mut()) {
            *o = *i * gain;
        }
    }
}
Source

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.

Default returns an empty vector.

Source

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.

Default does nothing.

Source

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.

Source

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.

Implementors§