Basic Programming Examples
A simple gain-change plug-in.


You may ask why we are diving right into a programming example. Well the answer is clear, creating a VST plug-in is easy and we don’t want to cloud the issue. If the SDK is standing in the way of understanding something relatively uncomplicated, then something has gone wrong.

This example is very simple and the resulting plug-in does not really do anything interesting, but it does show the how uncomplicated creating a VST plug-in can be. The example source files can be found in the accompanying ‘examples’ folder along with project files for the various platforms.

Please note that when you start experimenting with these examples we have marked the places where you should make your changes if these examples were to be used as templates for your own plug-ins.

Before getting our programming feet wet, maybe we should cover just a couple of background points that will help with understanding the following code fragments.


  • The core of your VST plug-in code, and this 'AGain' example too, is built around a C++ class derived from a Steinberg supplied base class called AudioEffectX.

  • The constructor of your class is passed a parameter of the type audioMasterCallback. The actual mechanism in which your class gets constructed is not important right now, but effectively your class is constructed by the hosting application, and the host passes an object of type audioMasterCallback that handles the interaction with the plug-in. You pass this on to the base class’s constructor and then can forget about it.

  • A few flags, and identifiers must be set and you declare your class’s input & output requirements at construction time.

  • Finally your class gets to over-ride both of two possible member functions that actually do the work. These are repeatedly called by the host application, each time with a new block of data. These member functions have only three parameters: a pointer to the data, a pointer to where you can write modified data to, and how big the block is.


Example Code : AGain


Basic Source Files Required
AGain.cpp
AGain.hpp
AGainMain.cpp


#ifndef __AGAIN_H
#include "AGain.hpp"
#endif

//-------------------------------------------------------------------------------------------------------
AGain::AGain (audioMasterCallback audioMaster)
	: AudioEffectX (audioMaster, 1, 1)	// 1 program, 1 parameter only
{
	fGain = 1.;				// default to 0 dB
	setNumInputs (2);		// stereo in
	setNumOutputs (2);		// stereo out
	setUniqueID ('Gain');	// identify
	canMono ();				// makes sense to feed both inputs with the same signal
	canProcessReplacing ();	// supports both accumulating and replacing output
	strcpy (programName, "Default");	// default program name
}

//-------------------------------------------------------------------------------------------------------
AGain::~AGain ()
{
	// nothing to do here
}

//-------------------------------------------------------------------------------------------------------
void AGain::setProgramName (char *name)
{
	strcpy (programName, name);
}

//-----------------------------------------------------------------------------------------
void AGain::getProgramName (char *name)
{
	strcpy (name, programName);
}

//-----------------------------------------------------------------------------------------
void AGain::setParameter (long index, float value)
{
	fGain = value;
}

//-----------------------------------------------------------------------------------------
float AGain::getParameter (long index)
{
	return fGain;
}

//-----------------------------------------------------------------------------------------
void AGain::getParameterName (long index, char *label)
{
	strcpy (label, "Gain");
}

//-----------------------------------------------------------------------------------------
void AGain::getParameterDisplay (long index, char *text)
{
	dB2string (fGain, text);
}

//-----------------------------------------------------------------------------------------
void AGain::getParameterLabel(long index, char *label)
{
	strcpy (label, "dB");
}

//------------------------------------------------------------------------
bool AGain::getEffectName (char* name)
{
	strcpy (name, "Gain");
	return true;
}

//------------------------------------------------------------------------
bool AGain::getProductString (char* text)
{
	strcpy (text, "Gain");
	return true;
}

//------------------------------------------------------------------------
bool AGain::getVendorString (char* text)
{
	strcpy (text, "Steinberg Media Technologies");
	return true;
}

//-----------------------------------------------------------------------------------------
void AGain::process (float **inputs, float **outputs, long sampleFrames)
{
    float *in1  =  inputs[0];
    float *in2  =  inputs[1];
    float *out1 = outputs[0];
    float *out2 = outputs[1];

    while (--sampleFrames >= 0)
    {
        (*out1++) += (*in1++) * fGain;    // accumulating
        (*out2++) += (*in2++) * fGain;
    }
}

//-----------------------------------------------------------------------------------------
void AGain::processReplacing (float **inputs, float **outputs, long sampleFrames)
{
    float *in1  =  inputs[0];
    float *in2  =  inputs[1];
    float *out1 = outputs[0];
    float *out2 = outputs[1];

    while (--sampleFrames >= 0)
    {
        (*out1++) = (*in1++) * fGain;    // replacing
        (*out2++) = (*in2++) * fGain;
    }
}


That's all there is to it! That cannot have hurt too much. So let’s walk through all that again and explain what’s where.

To recap, apart from a very small ‘main’ function that resides in another file, which is used by the host to construct your plug-in, you really have seen the complete code for an VST plug-in, admittedly it does not really do much.

Your plug-in class is derived from AudioEffectX and provides is own processing methods, and that’s all you need to create a VST plug-in that uses the host’s default method for displaying its parameters.


How does it work?

Before proceeding with the description of the plug-in code fragment, we will take a short look at a skeleton code fragment from the host-side. That is, the application code associated with supporting VST-Plug-ins in an application. This SDK does not handle the host-side implementation of VST Plug-ins and the code here is purely for understanding purposes.

First, when the Application instanciates a plug-in, it calls the plug-ins main () function somewhat like this:

typedef AEffect *(*mainCall)(audioMasterCallback cb);
audioMasterCallback audioMaster;

void instanciatePlug (mainCall plugsMain)
{   			   
	AEffect *ce = plugsMain (&audioMaster);
	if (ce && ce->magic == AEffectMagic)
    {
    	....
    }

}


It’s all pretty elementary stuff, our instanciatePlug() function takes a typedef’d pointer to the main() function in our plug-in code. Our plug’s Main() is then called with the audioMaster data which we have already established is:


The host then gets a pointer to the plug-in, if all is well, and then can check if it was actually a VST plug-in that was actually created.
This is magic.

Returning now to the plug-in code for this example, let’s have a look at the main() function itself.
This you can find in AGainMain.cpp.



#if BEOS
#define main main_plugin
extern "C" __declspec(dllexport) AEffect *main_plugin (audioMasterCallback audioMaster);

#elif MACX
#define main main_macho
extern "C" AEffect *main_macho (audioMasterCallback audioMaster);

#else
AEffect *main (audioMasterCallback audioMaster);
#endif

//------------------------------------------------------------------------
AEffect *main (audioMasterCallback audioMaster)
{
	// Get VST Version
	if (!audioMaster (0, audioMasterVersion, 0, 0, 0, 0))
		return 0;  // old version

	// Create the AudioEffect
	ADelay* effect = new ADelay (audioMaster);
	if (!effect)
		return 0;
	
	// Check if no problem in constructor of AGain
	if (oome)
	{
		delete effect;
		return 0;
	}
	return effect->getAeffect ();
}


Firstly you can see that the audioMasterCallback audioMaster is called with the audioMasterVersion flag just to check that our plug-in is being opened in a valid VST host.

Next the constructor of our gain plug in is called. Unless something goes wrong (out of memory etc), and we return null to the VST host, then the resulting effect object has one of it’s member functions called effect->getAeffect (), and the result of this call is returned to the host.

What’s actually being returned to the host is the C interface of our plug-in: We may be writing code in C++ but deep down there is a C structure filled with fields we need to know nothing about.

Whenever the Host instanciates a plug-in, after the main() call, it also immediately informs the plug-in about important system parameters like sample rate, and sample block size. Because the audio effect object is constructed in our plug-in’s main(), before the host gets any information about the created object, you need to be careful what functions are called within the constructor of the plug-in. You may be talking but no-one is listening.

Let's look at some details of the gain example. First, the constructor:


AGain::AGain (audioMasterCallback audioMaster)
: AudioEffectX (audioMaster, 1, 1)	// 1 program, 1 parameter only
{
	fGain = 1.;				// default to 0 dB
	setNumInputs (2);		// stereo in
	setNumOutputs (2);		// stereo out
	setUniqueID ('Gain');	// identify
	canMono ();				// makes sense to feed both inputs with the same signal
	canProcessReplacing ();	// supports both accumulating and replacing output
	strcpy (programName, "Default");	// default program name
}


Our plug-in’s constructor has the callback to the host as its parameter. This is passed onto the AudioEffectX base-class where it is stored, but the constructor to the base-class also has two extra parameters that set the number of programs the effect has, and the number of parameters the host should ‘see’ as user parameters for the plug-in.


Parameters are the individual parameter settings the user can adjust.
A VST host can automate these parameters.

Programs are a complete set of parameters that refer to the current state of the plug-in.

Banks are a collection of Program objects.



Our plug-in only has one parameter, namely the gain. This is a member variable of our AGain class, and get initialized to 1.0, which translates to 0dB. This value like all VST parameters is declared as a float with a inclusive range of 0.0 to 1.0. How data is presented to the user is merely in the userinterface handling.

Parameter values are limited to the range 0.0 to 1.0
This is a convention, but still worth regarding. Maybe the VST-host’s automation system depends on this range.



Next the plug-in states the number of inputs and outputs it can process with setNumInputs(2) & setNumOutputs(2);

The plug-in next declares its unique identifier. The host uses this to identify the plug-in, for instance when it is loading effect programs and banks. Steinberg will keep a record of all these IDs so if you want to, before releasing a plug-in ask us if anyone else has already said they use to the ID. Otherwise be inventive; you can be pretty certain that someone already has used ‘DLAY’.

The call canMono() tells the host that it makes sense to use this plug-in as a 1 in / 2 out device. When the host has mono effects sends and has a stereo buss for the effects returns, then the host can check this flag to add it to the list of plug-ins that could be used in this way.

In this instance the host can check to see if a stereo plug is selected, and can decide to feed both inputs with the same signal.

For the gain plug, this makes sense (although it would be much more efficient to have a separate 1 in / 2 out version), but a stereo width enhancer, for instance, really doesn't make sense to be used with both inputs receiving the same signal; such a plug should definitely not call canMono() .



We then tell host that we support replacing processing with the call canProcessReplacing() (see ‘Basic Concepts’ for more information).

Finally, the one and only program name gets set to "Default" with strcpy(programName, "Default"). The program name is displayed in the rack, and can be edited by the user.

This gain plug-in only has one parameter so its handling of the hosts requests for parameter information are very simple, in that the index to the parameter required is simply ignored, and either the current value of our fGain member is set or returned.



void AGain::setParameter (long index, float value)
{
	fGain = value;
}

float AGain::getParameter (long index)
{
	return fGain;
}



In this case the parameter directly reflects the ‘value’ used in our processing method. When the user ‘turns the dial’ on a generic plug interface it is this value that gets swept between 0.0 & 1.0 as the setParameter()function gets called. If you want a different relationship between the parameters that are automated by the host and what is actually used in your processing method, then that’s up to you to do the mapping.



When using the default host interface to provide an interface for a plug-in, as we are doing here, it is necessary that effect object overrides the following functions to allow the default interface a chance to display useful values to the user. Note, here again we are ignoring the parameter ‘index’ while we only have one parameter.

The Default User-Interface
The VST Host provides a method of displaying parameter values. The plug-in only needs to provide character strings representing its paramter values, labels & names. The Host then is free to display and edit these parameters as it wishes.


void AGain::getParameterName (long index, char *label)
{
	strcpy (label, "Gain");
}

void AGain::getParameterDisplay (long index, char *text)
{
	dB2string (fGain, text);
}

void AGain::getParameterLabel(long index, char *label)
{
	strcpy (label, "dB");
}


Please be aware that the string lengths supported by the default VST interface are normally limited to 24 characters. If you copy too much data into the buffers provided, you will break the host application. Don’t do it.



As you can see, the AudioEffectX base-class has some default methods that are useful for parameter to value and parameter to string conversions, for instance here to display a dB value string. dB2string(fGain, text) . Please check the audioeffectX.hpp & AudioEffect.h files for the full compliment.

So that's it ? !

Yes, that’s all you need to know to create a fully working VST plug-in. If you already have an idea for an algorithm or audio process you could test it out right now.

Actually there are two different kinds of processes that you can support, so it’s probably a good idea to keep on reading a little further.

Anyway that's all there is to initialization, parameter handling, and user interfacing in its simplest form.


Now you should proceed to the ADelay example, which is similar to AGain but has more parameters, and provides the basis for a plug-in with its own graphical user interface.



Basic Programming Examples
A simple delay plug-in.

This next coding example builds on what was learnt from the previous example, the simple gain plug-in. Please read this first if you have not already done so, because only what’s new is explained. Apart from offering a somewhat more meaningful audio process, the main reason for this example is to learn more about how multiple parameters are handled and how a plug manages more than one snapshot of user settings.

Example Code : ADelay


Basic Source Files Required
ADelay.cpp
ADelay.hpp
ADelayMain.cpp


First, let's look at the simple and short declaration of the ADelay class and it’s companion class ADelayProgram:

#ifndef __audioeffectx__
#include "audioeffectx.h"
#endif

enum
{
	// Global
	kNumPrograms = 16,

	// Parameters Tags
	kDelay = 0,
	kFeedBack,
	kOut,

	kNumParams
};

class ADelay;

//------------------------------------------------------------------------
class ADelayProgram
{
friend class ADelay;
public:
	ADelayProgram ();
	~ADelayProgram () {}

private:	
	float fDelay;
	float fFeedBack;
	float fOut;
	char name[24];
};

//------------------------------------------------------------------------
class ADelay : public AudioEffectX
{
public:
	ADelay (audioMasterCallback audioMaster);
	~ADelay ();

	virtual void process (float **inputs, float **outputs, long sampleframes);
	virtual void processReplacing (float **inputs, float **outputs, long sampleFrames);

	virtual void setProgram (long program);
	virtual void setProgramName (char *name);
	virtual void getProgramName (char *name);
	virtual bool getProgramNameIndexed (long category, long index, char* text);
	
	virtual void setParameter (long index, float value);
	virtual float getParameter (long index);
	virtual void getParameterLabel (long index, char *label);
	virtual void getParameterDisplay (long index, char *text);
	virtual void getParameterName (long index, char *text);

	virtual void resume ();

	virtual bool getEffectName (char* name);
	virtual bool getVendorString (char* text);
	virtual bool getProductString (char* text);
	virtual long getVendorVersion () { return 1000; }
	
	virtual VstPlugCategory getPlugCategory () { return kPlugCategEffect; }

protected:
	void setDelay (float delay);

	ADelayProgram *programs;
	
	float *buffer;
	float fDelay, fFeedBack, fOut;
	
	
	long delay;
	long size;
	long cursor;
};


So what's new?

So after scanning through that you probably can see what’s familiar from the original gain plug-in but also what’s new. Now one more time, step by step…

enum
{
	// Global
	kNumPrograms = 16,

	// Parameters Tags
	kDelay = 0,
	kFeedBack,
	kOut,

	kNumParams
};


This enumerates all parameters that are accessible to the user. It's a good habit to use such an enumerator for any plug-in. If our final linked plug-in was dropped into the host’s VST Plug-in folder, then the parameters we would see would be representations of these, namely the delay time, the output to input feedback amount, and the output level as parameters.

While we are on the topic of good ideas, we would recommend having at least one representation of these parameters in your plug-ins main class that corresponds to the generic parameter format. In other words, for each of these parameters declared in the enumeration there should a (similarly named) parameter that holds it’s value, where the value is a float type between the values 0.0 and 1.0 inclusive.


The handling of programs, the snapshots of user settings, is going to need a container for each set of parameters. We define a class to handle this.

class ADelayProgram
{
friend class ADelay;
public:
	ADelayProgram ();
	~ADelayProgram () {}

private:	
	float fDelay;
	float fFeedBack;
	float fOut;
	char name[24];
};


The members, fDelay, fFeedBack, and fOut are the generic representations of the parameters as stored in this container. They are not themselves the parameters used in the audio process but places where they will be copied from when the user selects another program. At this point the user can edit these values using the controls provided by the host using setParameter() & getParameter().

The same is true of the name, if this program was to be selected by the user, after the plug-in has copied it from the container, the user can use the host application to edit the name, which is achieved with getProgramName() and setProgramName().

We will omit the method declarations and have a look at their implementation instead, firstly the ADelayProgram constructor:

ADelayProgram::ADelayProgram ()
{
	// default Program Values
	fDelay = 0.5;
	fFeedBack = 0.5;
	fOut = 0.75;

	strcpy (name, "Init");
}


This simply sets the default parameter values for all programs. You might want to assign different values (for instance, from a table or array) for each program, in order to provide some useful presets. That's all there is to the ADelayProgram class.

So, back to the constructor of the ADelay class…

ADelay::ADelay (audioMasterCallback audioMaster)
: AudioEffectX (audioMaster, kNumPrograms, kNumParams)
{
	// init
	size = 44100;
	buffer = new float[size];
	
	programs = new ADelayProgram[numPrograms];
	fDelay = fFeedBack = fOut = 0;
	delay = inPos = outPos = 0;

	if (programs)
		setProgram (0);

	setNumInputs (1);	// mono input
	setNumOutputs (2);	// stereo output

	hasVu ();
	canProcessReplacing ();
	setUniqueID ('ADly');

	resume ();		// flush buffer
}


When the ADelay class is constructed, firstly the base-class AudioEffectX is constructed. As you will remember from the AGain example, one of AudioEffectX’s arguments is the number of programs the plug-in has (here set to 2). Once the base class is constructed, during the construction of the ADelay object, the member variable numPrograms is valid and is used to construct a set of ADelayPrograms.

Because this is a delay process we are going to need some memory to store samples. The parameter ‘size’ is set to 44100 to provide a maximum of one second of delay, and then a buffer of floats is allocated.

Probably a statement such as size = getSampleRate() would be better, but as we don’t react to changes in sample rate in this simple example, and then anyway we would have to re-allocate the buffer accordingly etc. That’s not the point of this example.


Next all the parameters that are used in the audio process are set to zero as a matter of good behavior, but actually they will get set to an initial value when setProgram() gets called if delay programs were successfully created.


Just as in the AGain example, setUniqueID() is very important, you must set this to a value as unique as possible as the host uses this to identify the plug-in, for instance when it is loading effect programs and banks.

Finally, we state that we support vu meters, and in-place processing as with the gain example. The destructor just takes care of cleaning up (remember, we're in the Hosts' context):


ADelay::~ADelay ()
{
	if (buffer)
		delete[] buffer;
	if (programs)
		delete[] programs;
}


Changing Programs

Next we should have a look at the setProgram() method. This is called whenever the user, or the host as a result of automation activity, wants to change the current active program in the plug-in. This (re-) initializes both generic, internal parameters, and variables:

void ADelay::setProgram (long program)
{
	ADelayProgram * ap = &programs[program];

	curProgram = program;
	setParameter (kDelay, ap->fDelay);	
	setParameter (kFeedBack, ap->fFeedBack);
	setParameter (kOut, ap->fOut);
}


It reads the parameters from the selected program, makes that the current program, and calls setParameter() for each of the indexed parameters, with the new values, just as if the user would have set them by hand. It doesn’t matter why the parameters are being changed, user selection, automation, program change, the same setParameter() method gets called.


Changing Parameters

Well, while we are on the subject of setParameter() we should look at its implementation.

void ADelay::setParameter (long index, float value)
{
	ADelayProgram * ap = &programs[curProgram];

	switch (index)
	{
		case kDelay :    setDelay (value); break;
		case kFeedBack : fFeedBack = ap->fFeedBack = value; break;
		case kOut :      fOut = ap->fOut = value; break;
	}
	if (editor)
		editor->postUpdate ();
}


We'll get back to setDelay() later. The setParameter() function is also called from the Host, it's important that we store the parameters back into the current program, to maintain the currently edited state of any particular program.

Both getParameter() and setParameter() always refer to the current program.

But notice that last section referring to the editor. When a parameter gets changed you want the user’s display to be updated as well. And here is exactly where you should NOT do it. The reason is that setParameter can & will get called from interrupt and drawing graphics from an interrupt (on the Mac!) is basically impossible. And we will leave it at that. There are actually performance reasons why deferring graphic updates to the user-interface thread is a good idea. What you actually do is post a message to the user-interface that something or everything needs updating.



To complete the picture, here’s the implementation of getParameter()


float ADelay::getParameter (long index)
{
	float v = 0;

	switch (index)
	{
		case kDelay :    v = fDelay; break;
		case kFeedBack : v = fFeedBack; break;
		case kOut :      v = fOut; break;
	}
	return v;
}


That’s simple enough. Compared with the version in AGain where we had only one parameter, we use the index value to return the appropriate parameter. Alternatively we could have returned values that referred to the program index by curProgram, but because we were so diligent in how setProgram() also updated the current program, there was no need.


There's also getProgramName() and setProgramName(): Remember the size of the character arrays, and the consequences of overfilling them?


void ADelay::setProgramName (char *name)
{
	strcpy (programs[curProgram].name, name);
}

void ADelay::getProgramName (char *name)
{
	if (!strcmp (programs[curProgram].name, "Init"))
		sprintf (name, "%s %d", programs[curProgram].name, curProgram + 1);
	else
		strcpy (name, programs[curProgram].name);
}


Fairly straightforward as well. And as we have more than one parameter, we use switch statements where the Host asks us for strings. We still don’t have our own interface yet and must support the host’s efforts in providing us with the generic interface.

Even if you do provide your own editor you should still have a fully set of ‘string’ interfaces, so the host application can graphically edit your automated parameters, with their names and labels.


void ADelay::getParameterName (long index, char *label)
{
	switch (index)
	{
		case kDelay :    strcpy (label, "Delay"); break;
		case kFeedBack : strcpy (label, "FeedBack"); break;
		case kOut :      strcpy (label, "Volume"); break;
	}
}

//------------------------------------------------------------------------
void ADelay::getParameterDisplay (long index, char *text)
{
	switch (index)
	{
		case kDelay :    long2string (delay, text); break;
		case kFeedBack : float2string (fFeedBack, text);	break;
		case kOut :      dB2string (fOut, text); break;
	}
}

//------------------------------------------------------------------------
void ADelay::getParameterLabel (long index, char *label)
{
	switch (index)
	{
		case kDelay :    strcpy (label, "samples");	break;
		case kFeedBack : strcpy (label, "amount");	break;
		case kOut :      strcpy (label, "dB");	break;
	}
}

//------------------------------------------------------------------------
bool ADelay::getEffectName (char* name)
{
	strcpy (name, "Delay");
	return true;
}


This just a small detail really but the AudioEffectX base class provides a number of conversion functions, we have already used dB2string() in the gain plug-in, but here long2string() and float2string() are used as well.

We are getting close the to end of the new stuff here. Have a look at the method resume().

void ADelay::resume ()
{
	memset (buffer, 0, size * sizeof (float));
	AudioEffectX::resume ();
}


This method resume() is called when the effect is turned on by the user; the buffer gets flushed because otherwise pending delays would sound. It’s not essential to do this but it would be rather strange not to, but there’s no accounting for taste.

And Finally, the Process.


void ADelay::process (float** inputs, float** outputs, long sampleFrames)
{
	float* in = inputs[0];
	float* out1 = outputs[0];
	float* out2 = outputs[1];

	while (--sampleFrames >= 0)
	{
		float x = *in++;
		float y = buffer[cursor];
		buffer[cursor++] = x + y * fFeedBack;
		if (cursor >= delay)
			cursor = 0;
		(*out1++) += y;
		if (out2)
			(*out2++) += y;
	}
}


There is a buffer of (at least) the maximum number of samples to be delayed, reflected by the size member variable.
The details of the processes and are left for you to figure out by yourself. All in all, this is a simple delay with feedback.

At this point, we have seen enough to understand the fundamental mechanisms of writing VST plug-ins: Now complete with multiple parameters, that can be correctly automated and saved as programs, and displayed with labels and value units by the host. Not bad for a few lines of code.

Both the gain plug-in and this simple delay plug-in have used the host’s default parameter handling for changing and displaying values. Nothing wrong with that, but VST plug-ins can also provide their own user interface, so that they can do customize their look and feel. In the next example code we will look at how the delay plug-in can be extended to do just that.


Copyright ©2003 Steinberg Media Technologies GmbH. All Rights Reserved.