Lumix supports VST2 plugins through the Jacobi VST.NET library, enabling integration of both VST instruments (VSTi) and effects (VST) into your projects.
Plugin Types
VST plugins are categorized into two types:
public enum VstType
{
VST , // Effect processor
VSTi // Instrument (synthesizer)
}
VST - Audio effects that process incoming audio
VSTi - Virtual instruments that generate audio from MIDI input
Loading VST Plugins
VstPlugin Class
The VstPlugin class handles VST plugin loading and windowing:
var plugin = new VstPlugin ( pluginPath );
Constructor Process:
Load Plugin - Creates a VstPluginContext from the DLL path
Detect Type - Checks VstPluginFlags.IsSynth to determine if it’s an instrument
Open Plugin - Calls Open() on the plugin command stub
Create Editor Window - Opens a UI window if the plugin has one
Start Idle Loop - Begins UI update cycle at ~60fps
Plugin Context
The plugin context provides access to VST functionality:
public VstPluginContext PluginContext { get ; }
Key Properties:
PluginInfo - Metadata about the plugin
PluginCommandStub - Interface for sending commands to the plugin
Audio Processing
VstAudioProcessor
The VstAudioProcessor class wraps a VstPlugin with the IAudioProcessor interface:
public class VstAudioProcessor : IAudioProcessor
{
private VstPlugin _vstPlugin ;
public VstPlugin VstPlugin => _vstPlugin ;
public VstAudioProcessor ( VstPlugin vst )
{
_vstPlugin = vst ;
}
}
Buffer Management
VST processing uses circular buffers for block-based processing:
private CircularBuffer inputBuffer ;
private CircularBuffer outputBuffer ;
private VstAudioBuffer [] _inputBuffers ;
private VstAudioBuffer [] _outputBuffers ;
Processing Pipeline:
Interleaved to Planar Conversion - Lumix’s interleaved stereo is split into separate L/R buffers
Block Processing - Audio is processed in fixed-size blocks
VST Processing - Calls ProcessReplacing() on the plugin
Planar to Interleaved Conversion - Separate channels are merged back
Block Size Configuration
private void UpdateBlockSize ( int blockSize )
{
_blockSize = blockSize ;
int inputCount = _vstPlugin . PluginContext . PluginInfo . AudioInputCount ;
int outputCount = _vstPlugin . PluginContext . PluginInfo . AudioOutputCount ;
var inputMgr = new VstAudioBufferManager ( inputCount , blockSize );
var outputMgr = new VstAudioBufferManager ( outputCount , blockSize );
_vstPlugin . PluginContext . PluginCommandStub . Commands . SetBlockSize ( blockSize );
_vstPlugin . PluginContext . PluginCommandStub . Commands . SetSampleRate ( AudioSettings . SampleRate );
_vstPlugin . PluginContext . PluginCommandStub . Commands . SetProcessPrecision ( VstProcessPrecision . Process32 );
_inputBuffers = inputMgr . Buffers . ToArray ();
_outputBuffers = outputMgr . Buffers . ToArray ();
}
Block size is dynamically adjusted based on the incoming audio buffer size, and the plugin is reconfigured accordingly.
MIDI Support
VST instruments receive MIDI events through the plugin context:
Note On
public void SendNoteOn ( int channel , int note , int velocity )
{
var midiEvent = new VstMidiEvent (
deltaFrames : 0 ,
noteLength : 0 ,
noteOffset : 0 ,
midiData : new byte []
{
( byte )( 0x90 | channel & 0x0F ), // Note On status + channel
( byte )( note & 0x7F ), // Note number (0-127)
( byte )( velocity & 0x7F ) // Velocity (0-127)
},
detune : 0 ,
noteOffVelocity : 0 );
_pluginContext . PluginCommandStub . Commands . ProcessEvents ( new VstEvent [] { midiEvent });
}
Note Off
public void SendNoteOff ( int channel , int note , int velocity )
{
var midiEvent = new VstMidiEvent (
deltaFrames : 0 ,
noteLength : 0 ,
noteOffset : 0 ,
midiData : new byte []
{
( byte )( 0x80 | channel & 0x0F ), // Note Off status + channel
( byte )( note & 0x7F ), // Note number
( byte )( velocity & 0x7F ) // Release velocity
},
detune : 0 ,
noteOffVelocity : 0 );
_pluginContext . PluginCommandStub . Commands . ProcessEvents ( new VstEvent [] { midiEvent });
}
Sustain Pedal
public void SendSustainPedal ( int channel , bool isPressed )
{
var midiEvent = new VstMidiEvent (
deltaFrames : 0 ,
noteLength : 0 ,
noteOffset : 0 ,
midiData : new byte []
{
( byte )( 0xB0 | ( channel & 0x0F )), // Control Change + channel
( byte )( 64 ), // CC 64 (sustain pedal)
( byte )( isPressed ? 127 : 0 ) // Value: 127=on, 0=off
},
detune : 0 ,
noteOffVelocity : 0 );
_pluginContext . PluginCommandStub . Commands . ProcessEvents ( new VstEvent [] { midiEvent });
}
Plugin Editor Window
Window Creation
Lumix creates a native window for the plugin’s editor:
private IntPtr CreateWindow ( string title , int width , int height )
{
_pluginWindow = new Sdl2Window (
title ,
400 , 400 , // X, Y position
width , height ,
SDL_WindowFlags . AlwaysOnTop |
SDL_WindowFlags . Resizable |
SDL_WindowFlags . SkipTaskbar ,
false );
// Plugin window communicates with main window
_pluginWindow . KeyDown += VirtualKeyboard . KeyDownFromPlugin ;
_pluginWindow . KeyUp += VirtualKeyboard . KeyUpFromPlugin ;
return _pluginWindow . Handle ;
}
Window Features:
Always on top
Resizable (controlled by plugin)
Minimize/maximize buttons removed
Keyboard events forwarded to virtual keyboard
Editor Idle Loop
The editor is kept responsive with a continuous idle loop:
private void StartEditorIdle ()
{
Task . Run ( async () =>
{
while ( _pluginWindow . Exists )
{
_pluginContext ? . PluginCommandStub . Commands . EditorIdle ();
await Task . Delay ( 16 ); // ~60 FPS
}
});
}
The EditorIdle() call allows the plugin to update its UI in response to parameter changes and automation.
Plugin Lifecycle
Opening a Plugin
public void OpenPluginWindow ()
{
if ( ! _pluginWindow . Exists )
{
int x = _pluginWindow . X ;
int y = _pluginWindow . Y ;
RecreateWindow ();
_pluginWindow . X = x ;
_pluginWindow . Y = y ;
}
}
Disposing a Plugin
public void Dispose ( bool closeWindow = true )
{
if ( closeWindow )
{
_pluginWindow ? . Close ();
}
_pluginContext ? . Dispose ();
}
Always dispose of VST plugins properly to release native resources and unload plugin DLLs.
Integration with Plugin Chain
VST plugins are integrated into the plugin chain through VstAudioProcessor:
// Adding a VSTi
if ( plugin is VstAudioProcessor vstPlugin &&
vstPlugin . VstPlugin . PluginType == VstType . VSTi )
{
_pluginInstrument = plugin ;
}
else
{
_fxPlugins . Add ( plugin ); // VST effects
}
Plugin Chain Learn how VST plugins fit into the processing chain
Built-in Plugins Compare with native plugin implementation
Technical Details
Sample Rate Configuration
Sample rate is set from AudioSettings.SampleRate and configured during block size updates: Commands . SetSampleRate ( AudioSettings . SampleRate );
Lumix uses 32-bit float processing: Commands . SetProcessPrecision ( VstProcessPrecision . Process32 );