Table of Contents

Class Vst3Plugin

Namespace
NAudio.Vst3
Assembly
NAudio.Vst3.dll

An instantiated, activated, processing-ready VST 3® audio-effect plug-in.

public sealed class Vst3Plugin : IDisposable
Inheritance
Vst3Plugin
Implements
Inherited Members

Remarks

Phase 2 surface — stereo-in / stereo-out audio effects only. Plug-ins that cannot accept a stereo bus arrangement, or that do not have at least one audio input and one audio output bus, are rejected at construction with an InvalidOperationException.

Lifecycle: instantiation runs the full SDK start-up dance — createInstance → queryInterface(IAudioProcessor) → initialize → setBusArrangements(stereo) → setupProcessing → activateBus(in) → activateBus(out) → setActive(true) → setProcessing(true). Dispose() tears down in reverse. Process(ReadOnlySpan<float>, Span<float>, int) may be called repeatedly between those two.

The owning Vst3Module must outlive every Vst3Plugin it creates.

Threading and disposal. Process(ReadOnlySpan<float>, Span<float>, int) runs on the audio thread, while the lifecycle, parameter and state calls run on the host's control thread. Dispose() is not synchronised against an in-flight Process(ReadOnlySpan<float>, Span<float>, int) — disposing while the audio thread is inside Process(ReadOnlySpan<float>, Span<float>, int) releases the native objects underneath it. Stop the plug-in and remove it from the audio graph (so no further Process(ReadOnlySpan<float>, Span<float>, int) call can run) before disposing it.

Properties

ActiveProgramList

The program list the program-change parameter selects from — resolved via that parameter's owning unit, or the sole list when there is exactly one. null when the plug-in exposes no program list (or no program-change parameter to tie one to).

public Vst3ProgramList? ActiveProgramList { get; }

Property Value

Vst3ProgramList

ClassInfo

The class descriptor this plug-in was instantiated from.

public Vst3ClassInfo ClassInfo { get; }

Property Value

Vst3ClassInfo

CurrentProgram

The currently-selected program index (the inverse of what SendProgramChange(int, long) writes), or -1 when the plug-in has no program-change parameter. Read from the parameter's current normalised value, so it reflects program changes the plug-in's own UI made.

public int CurrentProgram { get; }

Property Value

int

HasSeparateController

true when the plug-in's IEditController is a separate object from its IComponent (the two-object model). Informational only — both shapes are handled transparently by the public API.

public bool HasSeparateController { get; }

Property Value

bool

InputChannelCount

Number of input channels negotiated with the plug-in via setBusArrangements. The host first tries stereo; if the plug-in refuses, we accept its declared default and use that arrangement instead. Mono (1) and stereo (2) are the only counts currently supported.

public int InputChannelCount { get; }

Property Value

int

IsInstrument

true when this plug-in is an instrument (VSTi) — it has an event input bus and generates audio from scheduled notes rather than processing an audio input.

public bool IsInstrument { get; }

Property Value

bool

LastOutputSilenceFlags

Bitfield of the most recent block's per-channel output-silence flags as set by the plug-in (bit N = output channel N is silent). VST 3 plug-ins write this on the output bus to let the host skip dead channels; the SDK helper does this consistently, plain AudioEffect-derived plug-ins sometimes do not. Exposed as a diagnostic only — the built-in tail detector in Vst3EffectSampleProvider does not consult it (it gates on output RMS, since some plug-ins never set the flag). Treat it as an optional hint if you implement your own tail logic.

public ulong LastOutputSilenceFlags { get; }

Property Value

ulong

LatencySamples

Reported by IAudioProcessor::getLatencySamples after activation. A plug-in may change its latency at runtime (e.g. switching an EQ to linear phase, or enabling oversampling); when it does it raises restartComponent(kLatencyChanged), which re-queries this value and fires LatencyChanged. Reads are atomic (volatile).

public uint LatencySamples { get; }

Property Value

uint

MaxBlockSize

public int MaxBlockSize { get; }

Property Value

int

OutputChannelCount

Number of output channels negotiated with the plug-in via setBusArrangements. See InputChannelCount for the negotiation strategy.

public int OutputChannelCount { get; }

Property Value

int

Parameters

Every parameter the plug-in's edit controller advertises, in declaration order. The collection itself is built once at construction; individual Vst3Parameter values are live and round-trip to the controller on each property access.

public Vst3ParameterCollection Parameters { get; }

Property Value

Vst3ParameterCollection

ProgramLists

The plug-in's program lists (its factory presets, grouped), or an empty list when the plug-in does not implement IUnitInfo. Select a program with SendProgramChange(int, long) / EnqueueProgramChange(int); read the current one via CurrentProgram.

public IReadOnlyList<Vst3ProgramList> ProgramLists { get; }

Property Value

IReadOnlyList<Vst3ProgramList>

SampleRate

Sample rate configured at setupProcessing time.

public int SampleRate { get; }

Property Value

int

SupportsMidiControllers

true when the plug-in advertises MIDI-controller → parameter assignments (via IMidiMapping), i.e. SendControlChange(int, double, long) can route at least one controller.

public bool SupportsMidiControllers { get; }

Property Value

bool

SupportsProgramChange

true when the plug-in exposes a program-change parameter (one flagged IsProgramChange), i.e. SendProgramChange(int, long) / EnqueueProgramChange(int) can select a program.

public bool SupportsProgramChange { get; }

Property Value

bool

TailSamples

Reported by IAudioProcessor::getTailSamples after activation. 0 = no tail; uint.MaxValue = infinite (sustained reverb, feedback loops). Useful for offline rendering to know how much silence to feed after the input ends.

public uint TailSamples { get; }

Property Value

uint

Units

The plug-in's units (the IUnitInfo hierarchy), or an empty list when the plug-in does not implement IUnitInfo. The root unit (id 0) is present whenever any unit is.

public IReadOnlyList<Vst3Unit> Units { get; }

Property Value

IReadOnlyList<Vst3Unit>

Methods

AllNotesOff()

Sends a note-off for every note currently sounding (a "panic"). Safe to call from any thread. Use on stop, or to clear a stuck voice when a note-off was missed. Panic notes fire at offset 0 of the next block — sample-accurate timing isn't relevant for an emergency stop.

public void AllNotesOff()

ClearMusicalContext()

Reverts to the free-running default context. See SetMusicalContext(in Vst3MusicalContext).

public void ClearMusicalContext()

CreateView()

Creates the plug-in's editor view (its GUI), or returns null when the plug-in does not provide one (IEditController::createView returned a null pointer).

public Vst3PluginView? CreateView()

Returns

Vst3PluginView

Remarks

Editor hosting is a Windows-only feature in this release; the returned Vst3PluginView is attached to an HWND. Call this on the host's UI (STA) thread — the view inherits that thread's affinity.

The returned view must be disposed before this plug-in is disposed: the native view is owned by the controller, and releasing it after terminate is undefined.

Dispose()

Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.

public void Dispose()

EnqueueChannelPressure(double)

Routes channel pressure / aftertouch at offset 0 of the next block. See EnqueueControlChange(int, double).

public bool EnqueueChannelPressure(double normalizedValue)

Parameters

normalizedValue double

Returns

bool

EnqueueControlChange(int, double)

Routes a MIDI control change to its assigned parameter, to fire at offset 0 of the next block. The segment-driven counterpart to SendControlChange(int, double, long); returns false if the plug-in doesn't map that CC. See EnqueueNoteOn(int, float, int).

public bool EnqueueControlChange(int controllerNumber, double normalizedValue)

Parameters

controllerNumber int
normalizedValue double

Returns

bool

EnqueueNoteOff(int, float, int)

Queues a note-off to fire at offset 0 of the next block. See EnqueueNoteOn(int, float, int).

public void EnqueueNoteOff(int pitch, float velocity = 0, int channel = 0)

Parameters

pitch int
velocity float
channel int

EnqueueNoteOn(int, float, int)

Queues a note-on to fire at sample offset 0 of the next Process(ReadOnlySpan<float>, Span<float>, int) call, in arrival order. This is the counterpart to SendNoteOn(int, float, int, long) for segment-driven hosts — an offline renderer, or a SequencedMidiPlayer that has already split the audio block at each event's frame so every event belongs at the start of the next (sub-)block. Unlike SendNoteOn(int, float, int, long) it consults no wall clock, so it is correct under faster-than-real-time rendering. Call it on the same thread as Process(ReadOnlySpan<float>, Span<float>, int) (the audio/render thread); for cross-thread live input use SendNoteOn(int, float, int, long) instead.

public void EnqueueNoteOn(int pitch, float velocity, int channel = 0)

Parameters

pitch int

MIDI note number (0–127).

velocity float

Normalised velocity in [0, 1].

channel int

Event-bus channel (0-based).

Exceptions

InvalidOperationException

The plug-in is not an instrument.

EnqueuePitchBend(double)

Routes pitch-bend at offset 0 of the next block. See EnqueueControlChange(int, double).

public bool EnqueuePitchBend(double normalizedValue)

Parameters

normalizedValue double

Returns

bool

EnqueueProgramChange(int)

Selects a program by MIDI program number, to take effect at offset 0 of the next block — the segment-driven counterpart to SendProgramChange(int, long). Returns false if the plug-in has no program-change parameter. See EnqueueNoteOn(int, float, int).

public bool EnqueueProgramChange(int program)

Parameters

program int

Returns

bool

EnqueueSysEx(ReadOnlySpan<byte>)

Queues a System Exclusive message to fire at offset 0 of the next block — the segment-driven counterpart to SendSysEx(ReadOnlySpan<byte>, long). data is the raw SysEx message (F0…F7), copied; empty messages are ignored. See EnqueueNoteOn(int, float, int).

public void EnqueueSysEx(ReadOnlySpan<byte> data)

Parameters

data ReadOnlySpan<byte>

LoadPreset(Stream)

Loads a .vstpreset from the given seekable stream and applies it.

public void LoadPreset(Stream stream)

Parameters

stream Stream

Exceptions

InvalidOperationException

The preset belongs to a different plug-in class than this one.

InvalidDataException

The stream is not a well-formed .vstpreset.

LoadPreset(string)

Loads a Steinberg .vstpreset file from filePath and applies it.

public void LoadPreset(string filePath)

Parameters

filePath string

Exceptions

InvalidOperationException

The preset belongs to a different plug-in class than this one.

InvalidDataException

The file is not a well-formed .vstpreset.

LoadState(ReadOnlySpan<byte>)

Restores a state blob previously produced by SaveState(). The component blob is applied first (so the DSP picks up its cached values), then the controller blob (so the parameter mirror matches).

public void LoadState(ReadOnlySpan<byte> stateBytes)

Parameters

stateBytes ReadOnlySpan<byte>

Exceptions

ArgumentException

When the blob does not start with the V3ST magic, declares an unsupported version, or is truncated.

Process(ReadOnlySpan<float>, Span<float>, int)

Processes one block of audio. Buffers are interleaved per the negotiated channel count (InputChannelCount floats per input frame, OutputChannelCount floats per output frame).

public void Process(ReadOnlySpan<float> interleavedInput, Span<float> interleavedOutput, int numSamples)

Parameters

interleavedInput ReadOnlySpan<float>

Input audio, numSamples * InputChannelCount floats.

interleavedOutput Span<float>

Output buffer, numSamples * OutputChannelCount floats.

numSamples int

Number of audio frames to process. Must be ≤ MaxBlockSize.

Remarks

This is the audio-thread entry point and is normally called from a render callback. It can throw — both for an invalid request (see the exceptions below) and if the plug-in's IAudioProcessor::process returns a failure code, which indicates a broken plug-in. Because an exception escaping a render callback tears down the audio stream, a host that wants to keep playing through a misbehaving plug-in should catch around this call and substitute silence (the built-in Vst3EffectSampleProvider / Vst3InstrumentSampleProvider wrappers do not — they let the failure propagate so it surfaces rather than hiding silently).

Exceptions

ObjectDisposedException

The plug-in has been disposed.

ArgumentOutOfRangeException

numSamples is negative or exceeds MaxBlockSize.

ArgumentException

An input or output buffer is too small for the channel count.

InvalidOperationException

IAudioProcessor::process returned a failure HRESULT.

ResetEventSchedule()

Clears all scheduled events and resets the sample clock to zero.

public void ResetEventSchedule()

SavePreset(Stream)

Saves the plug-in's current state as a .vstpreset to the given seekable stream.

public void SavePreset(Stream stream)

Parameters

stream Stream

SavePreset(string)

Saves the plug-in's current state to a Steinberg .vstpreset file at filePath (overwriting any existing file).

public void SavePreset(string filePath)

Parameters

filePath string

Remarks

Captures both the component (DSP) and controller (parameter) state, the same pair persisted by SaveState(), but in the portable .vstpreset container so the file can be loaded by other VST 3 hosts (and vice-versa via LoadPreset(string)).

SaveState()

Captures the plug-in's full state — both the component (DSP) blob and the controller (UI / parameter mirror) blob — into a single self-describing byte array.

public byte[] SaveState()

Returns

byte[]

Remarks

The component and controller emit their own opaque binary representations via IComponent::getState and IEditController::getState. Most plug-ins prefer these blobs round-trip together (changing parameters in one without the other can leave the two halves inconsistent); this method always saves both.

ScheduleNoteOff(long, int, float, int)

Schedules a note-off at an absolute sample position. See ScheduleNoteOn(long, int, float, int).

public void ScheduleNoteOff(long sampleTime, int pitch, float velocity = 0, int channel = 0)

Parameters

sampleTime long
pitch int
velocity float
channel int

ScheduleNoteOn(long, int, float, int)

Schedules a note-on at an absolute position on the plug-in's sample timeline (the clock Process(ReadOnlySpan<float>, Span<float>, int) advances by numSamples each block). The event is delivered in the block whose range contains sampleTime.

public void ScheduleNoteOn(long sampleTime, int pitch, float velocity, int channel = 0)

Parameters

sampleTime long

Absolute sample position at which the note starts.

pitch int

MIDI note number (0–127).

velocity float

Normalised velocity in [0, 1].

channel int

Event-bus channel (0-based).

Exceptions

InvalidOperationException

The plug-in is not an instrument.

SendChannelPressure(double, long)

Routes channel pressure / aftertouch. See SendControlChange(int, double, long).

public bool SendChannelPressure(double normalizedValue, long arrivalTicks = 0)

Parameters

normalizedValue double
arrivalTicks long

Returns

bool

SendControlChange(int, double, long)

Routes a MIDI control change (CC 0–127) to its assigned parameter and queues it for the next block. Safe to call from any thread. Returns false if the plug-in doesn't map that CC.

public bool SendControlChange(int controllerNumber, double normalizedValue, long arrivalTicks = 0)

Parameters

controllerNumber int

MIDI controller number (0–127), e.g. 1 = mod wheel, 64 = sustain.

normalizedValue double

The value normalised to [0, 1] (e.g. raw CC value / 127).

arrivalTicks long

Optional GetTimestamp() tick of when the controller change arrived (e.g. inside a MIDI callback). When 0 / unspecified the current timestamp is captured inside. See SendNoteOn(int, float, int, long) for sample-accurate timing details.

Returns

bool

SendNoteOff(int, float, int, long)

Sends a note-off for the next block from any thread. See SendNoteOn(int, float, int, long).

public void SendNoteOff(int pitch, float velocity = 0, int channel = 0, long arrivalTicks = 0)

Parameters

pitch int
velocity float
channel int
arrivalTicks long

SendNoteOn(int, float, int, long)

Sends a note-on to be delivered at the start of the next Process(ReadOnlySpan<float>, Span<float>, int) block. Safe to call from any thread (e.g. a MIDI input callback) — the event is queued and consumed on the audio thread. This is the realtime/live counterpart to ScheduleNoteOn(long, int, float, int).

public void SendNoteOn(int pitch, float velocity, int channel = 0, long arrivalTicks = 0)

Parameters

pitch int

MIDI note number (0–127).

velocity float

Normalised velocity in [0, 1].

channel int

Event-bus channel (0-based).

arrivalTicks long

Optional GetTimestamp() tick of when the MIDI event arrived. When supplied (or left at the default 0, in which case the current timestamp is captured inside), the event is dispatched at the matching sub-block sample offset on the next Process(ReadOnlySpan<float>, Span<float>, int) call — sample-accurate timing rather than always firing at sample 0 of the next block. Pass a non-zero value when you want to use a timestamp captured earlier than the call to SendNoteOn(int, float, int, long) (e.g. inside a MIDI callback that does other work first).

Exceptions

InvalidOperationException

The plug-in is not an instrument.

SendPitchBend(double, long)

Routes pitch-bend (0 = full down, 0.5 = centre, 1 = full up). See SendControlChange(int, double, long).

public bool SendPitchBend(double normalizedValue, long arrivalTicks = 0)

Parameters

normalizedValue double
arrivalTicks long

Returns

bool

SendProgramChange(int, long)

Selects a program by MIDI program number, queued for the next block. VST 3 has no program-change event — this drives the plug-in's program-list parameter (flagged IsProgramChange) to the matching normalised value. Safe to call from any thread. Returns false if the plug-in has no such parameter. See SendNoteOn(int, float, int, long) for the arrivalTicks timing.

public bool SendProgramChange(int program, long arrivalTicks = 0)

Parameters

program int

MIDI program number (0–127).

arrivalTicks long

Optional GetTimestamp() arrival tick; 0 captures it inside.

Returns

bool

SendSysEx(ReadOnlySpan<byte>, long)

Sends a System Exclusive message to the instrument as a VST 3 DataEvent, delivered live on the next block. Safe to call from any thread. data is the raw MIDI SysEx message including the leading F0 and trailing F7; it is copied, so the caller's buffer can be reused immediately. Empty messages are ignored. Most instruments ignore SysEx; this just delivers it. See SendNoteOn(int, float, int, long) for the arrivalTicks timing.

public void SendSysEx(ReadOnlySpan<byte> data, long arrivalTicks = 0)

Parameters

data ReadOnlySpan<byte>
arrivalTicks long

SetMusicalContext(in Vst3MusicalContext)

Sets the musical/transport context (tempo, time signature, playhead, playing state) the host presents to the instrument's ProcessContext on subsequent Process(ReadOnlySpan<float>, Span<float>, int) calls, so tempo-following plug-ins lock to the host timeline. Push a fresh snapshot per block (as Vst3MidiInstrument does from a sequencer Transport). Call it on the audio thread. Until set — or after ClearMusicalContext() — the instrument gets a free-running 120-BPM, 4/4, stopped context. No effect on effects (they receive no context).

public void SetMusicalContext(in Vst3MusicalContext context)

Parameters

context Vst3MusicalContext

Events

LatencyChanged

Raised when the plug-in reports a latency change at runtime (via restartComponent(kLatencyChanged)) and LatencySamples has been updated. A host wiring this for live playback should treat it as "rebuild the processing graph": the new latency only takes audible effect once downstream delay compensation is recomputed. Fired on the thread the plug-in calls restartComponent from (typically its UI/edit thread), so a handler that touches the audio graph must marshal as needed.

public event EventHandler? LatencyChanged

Event Type

EventHandler