Class Vst3Plugin
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
ClassInfo
The class descriptor this plug-in was instantiated from.
public Vst3ClassInfo ClassInfo { get; }
Property Value
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
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
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
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
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
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
MaxBlockSize
Maximum samples per Process(ReadOnlySpan<float>, Span<float>, int) block.
public int MaxBlockSize { get; }
Property Value
OutputChannelCount
Number of output channels negotiated with the plug-in via setBusArrangements. See
InputChannelCount for the negotiation strategy.
public int OutputChannelCount { get; }
Property Value
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
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
SampleRate
Sample rate configured at setupProcessing time.
public int SampleRate { get; }
Property Value
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
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
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
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
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
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
normalizedValuedouble
Returns
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
Returns
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
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
pitchintMIDI note number (0–127).
velocityfloatNormalised velocity in [0, 1].
channelintEvent-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
normalizedValuedouble
Returns
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
programint
Returns
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
dataReadOnlySpan<byte>
LoadPreset(Stream)
Loads a .vstpreset from the given seekable stream and applies it.
public void LoadPreset(Stream stream)
Parameters
streamStream
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
filePathstring
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
stateBytesReadOnlySpan<byte>
Exceptions
- ArgumentException
When the blob does not start with the
V3STmagic, 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
interleavedInputReadOnlySpan<float>Input audio,
numSamples * InputChannelCountfloats.interleavedOutputSpan<float>Output buffer,
numSamples * OutputChannelCountfloats.numSamplesintNumber 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
numSamplesis negative or exceeds MaxBlockSize.- ArgumentException
An input or output buffer is too small for the channel count.
- InvalidOperationException
IAudioProcessor::processreturned 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
streamStream
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
filePathstring
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
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
sampleTimelongAbsolute sample position at which the note starts.
pitchintMIDI note number (0–127).
velocityfloatNormalised velocity in [0, 1].
channelintEvent-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
Returns
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
controllerNumberintMIDI controller number (0–127), e.g. 1 = mod wheel, 64 = sustain.
normalizedValuedoubleThe value normalised to [0, 1] (e.g. raw CC value / 127).
arrivalTickslongOptional 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
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
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
pitchintMIDI note number (0–127).
velocityfloatNormalised velocity in [0, 1].
channelintEvent-bus channel (0-based).
arrivalTickslongOptional 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
Returns
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
programintMIDI program number (0–127).
arrivalTickslongOptional GetTimestamp() arrival tick; 0 captures it inside.
Returns
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
dataReadOnlySpan<byte>arrivalTickslong
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
contextVst3MusicalContext
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