Unlike the other software from Teragon Audio, MissWatson is not open source on account of an external licensing agreement. I get lots of emails asking about the source code for MissWatson, to which I must unfortunately reply with this exact statement. However, it is not so difficult to write your own VST host in C++, and this tutorial will teach you how to do just that.
The most difficult part of writing a VST host is loading in the plugin and returning the correct data from your host callback function. Loading the actual plugin from disk is a bit tricky, and documentation on this front is a bit sparse. However, the VST communication protocol itself is well-documented and not so difficult to figure out. After you have established basic communication from your host to the plugin, there will be other hurdles to overcome, but the architecture of these challenges will vary greatly depending on your requirements and platform.
This guide will only cover basic plugin loading and communication, and the language of choice here is C++. C# programmers should consider using theVST.NET framework, and I'm not sure what frameworks exist for other languages.
vstsdk2.4/pluginterfaces/vst2.x
directory:
#include "aeffectx.h"
// C callbacks
extern "C" {
// Main host callback
VstIntPtr VSTCALLBACK hostCallback(AEffect *effect, VstInt32 opcode, VstInt32 index,
VstInt32 value, void *ptr, float opt);
}
// Plugin's entry point
typedef AEffect *(*vstPluginFuncPtr)(audioMasterCallback host);
// Plugin's dispatcher function
typedef VstIntPtr (*dispatcherFuncPtr)(AEffect *effect, VstInt32 opCode, VstInt32 index,
VstInt32 value, void *ptr, float opt);
// Plugin's getParameter() method
typedef float (*getParameterFuncPtr)(AEffect *effect, VstInt32 index);
// Plugin's setParameter() method
typedef void (*setParameterFuncPtr)(AEffect *effect, VstInt32 index, float value);
// Plugin's processEvents() method
typedef VstInt32 (*processEventsFuncPtr)(VstEvents *events);
// Plugin's process() method
typedef void (*processFuncPtr)(AEffect *effect, float **inputs, float **outputs, VstInt32 sampleFrames);
Step 3a: Windows
AEffect* loadPlugin() {
AEffect *plugin = NULL;
char *vstPath = "c:\\wherever\\the\\plugin\\is\\located.vst";
modulePtr = LoadLibrary(vstPath);
if(modulePtr == NULL) {
printf("Failed trying to load VST from '%s', error %d\n", vstPath, GetLastError());
return NULL;
}
vstPluginFuncPtr mainEntryPoint = (vstPluginFuncPtr)GetProcAddress(modulePtr, "VSTPluginMain");
// Instantiate the plugin
plugin = mainEntryPoint(hostCallback);
return plugin;
}
Step 3b: Loading the VST on Mac OSX
/Library/Audio/Plug-Ins/VST
or
$HOME/Library/Audio/Plug-Ins/VST
, where
$HOME
refers to the user's home directory.
AEffect* loadPlugin() {
AEffect *plugin = NULL;
audioMasterCallback hostCallbackFuncPtr = hostCallback;
char *pluginPath = "/wherever/the/plugin/is/located.vst";
// Create a path to the bundle
CFStringRef pluginPathStringRef = CFStringCreateWithCString(NULL, pluginPath, kCFStringEncodingASCII);
CFURLRef bundleUrl = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
pluginPathStringRef, kCFURLPOSIXPathStyle, true);
if(bundleUrl == NULL) {
printf("Couldn't make URL reference for plugin\n");
return NULL;
}
// Open the bundle
CFBundleRef bundle;
bundle = CFBundleCreate(kCFAllocatorDefault, bundleUrl);
if(bundle == NULL) {
printf("Couldn't create bundle reference\n");
CFRelease(pluginPathStringRef);
CFRelease(bundleUrl);
return NULL;
}
vstPluginFuncPtr mainEntryPoint = NULL;
mainEntryPoint = (vstPluginFuncPtr)CFBundleGetFunctionPointerForName(bundle, CFSTR("VSTPluginMain"));
// VST plugins previous to the 2.4 SDK used main_macho for the entry point name
if(mainEntryPoint == NULL) {
mainEntryPoint = (vstPluginFuncPtr)CFBundleGetFunctionPointerForName(bundle, CFSTR("main_macho"));
}
if(mainEntryPoint == NULL) {
printf("Couldn't get a pointer to plugin's main()\n");
CFBundleUnloadExecutable(bundle);
CFRelease(bundle);
return NULL;
}
plugin = mainEntryPoint(hostCallback);
if(plugin == NULL) {
printf("Plugin's main() returns null\n");
CFBundleUnloadExecutable(bundle);
CFRelease(bundle);
return NULL;
}
// Clean up
CFRelease(pluginPathStringRef);
CFRelease(bundleUrl);
return plugin;
}
CFBundleUnloadExecutable
and then
CFRelease
on the bundle's reference.
int initPlugin(AEffect *plugin) {
// Check plugin's magic number
// If incorrect, then the file either was not loaded properly, is not a real
// VST plugin, or is otherwise corrupt.
if(plugin->magic != kEffectMagic) {
printf("Plugin's magic number is bad\n");
return -1;
}
// Create dispatcher handle
dispatcherFuncPtr dispatcher = (dispatcherFuncPtr)(plugin->dispatcher);
// Set up plugin callback functions
plugin->getParameter = (getParameterFuncPtr)plugin->getParameter;
plugin->processReplacing = (processFuncPtr)plugin->processReplacing;
plugin->setParameter = (setParameterFuncPtr)plugin->setParameter;
return plugin;
}
void initPlugin(AEffect *plugin) {
dispatcher(plugin, effOpen, 0, 0, NULL, 0.0f);
// Set some default properties
float sampleRate = 44100.0f;
dispatcher(plugin, effSetSampleRate, 0, 0, NULL, sampleRate);
int blocksize = 512;
dispatcher(plugin, effSetBlockSize, 0, blocksize, NULL, 0.0f);
resume();
}
Step 5: Suspending and resuming
void resumePlugin(AEffect *plugin) {
dispatcher(plugin, effMainsChanged, 0, 1, NULL, 0.0f);
}
void suspendPlugin(AEffect *plugin) {
dispatcher(plugin, effMainsChanged, 0, 0, NULL, 0.0f);
}
Step 6: Plugin capabilities
audioeffectx.cpp
in the PlugCanDos namespace near the top of the file. To ask a plugin if it supports one of these capabilities, make the following dispatcher call:
bool canPluginDo(char *canDoString) {
return (dispatcher(plugin, effCanDo, 0, 0, (void*)canDoString, 0.0f) > 0);
}
Step 7: Host capabilities
extern "C" {
VstIntPtr VSTCALLBACK hostCallback(AEffect *effect, VstInt32 opcode, VstInt32 index,
VstInt32 value, void *ptr, float opt) {
switch(opcode) {
case audioMasterVersion:
return 2400;
case audioMasterIdle:
effect->dispatcher(effect, effEditIdle, 0, 0, 0, 0);
// Handle other opcodes here... there will be lots of them
default:
printf("Plugin requested value of opcode %d\n", opcode);
break;
}
}
}
The full list of opcodes is defined in
aeffect.h
(for the VST 1.x protocol) and
aeffectx.h
(for VST 2.x protocol). There are a lot of opcodes, and your application doesn't need to support them all, but you will soon figure out which ones are the most important through trial and error. Depending on the nature of the opcall, you will either be required to return a given integer value, call a method in the plugin's dispatcher, or fill the
*ptr
pointer with some type of data. The VST SDK header files have fairly good documentation specifying what you need to do depending on the opcode.
void processAudio(AEffect *plugin, float **inputs, float **outputs, long numFrames) {
// Note: If you are processing an instrument, you should probably zero out the input
// channels first to avoid any accidental noise. If you are processing an effect, you
// should probably zero the values in the output channels. See the silenceChannel()
// method below.
plugin->processReplacing(plugin, inputs, outputs, numFrames);
}
void silenceChannel(float **channelData, int numChannels, long numFrames) {
for(int channel = 0; channels < numChannels; ++channel) {
for(long frame = 0; frame < numFrames; ++frame) {
channelData[channel][frame] = 0.0f;
}
}
}
Note that you need to properly allocate the arrays for the audio inputs and outputs depending on your blocksize and channel count. Like a regular VST plugin, this structure is simply a de-interlaced array ordered by channel of the sample block data, with the left channel being the first one. You should also take care to properly initialize the data in both the inputs and outputs array to zero, or else you can get static or other random noise in the processed signal.
void processMidi(AEffect *plugin, VstEvents *events) {
dispatcher(plugin, effProcessEvents, 0, 0, events, 0.0f);
}
aeffectx.h
, and there you will also find the respective VstEvent types, all of which are deprecated except for
kVstMidiType
and
kVstSysExType
.