What is a VST plugin?

From the point of Cubase or any other VST host, a VST plugin is a dynamic link library (dll) which exports one function called main. This function creates an effect, represented by a record of type AEffect. The record gives access to data and functions needed to use the effect.

For the developer of a plugin, it is also a dll which exports a main function. It is also an AEffect record or an AudioEffect class, depending on the choice he makes. The AudioEffect class is a wrapper around the AEffect record. It makes it easier for the developer to add the functionality he wants to give to the effect, without having to wonder about the things that are the same for all plugins.

A VST plugin has two main features: it allows the host to inquire about its parameters and have them changed by the user, and it accepts audio data from the host, does something to it and gives back the result. Optionally, it also provides its own editor to edit its parameters. This way, the user can change the parameters in the manner which is most appropriate.

Creating your own effect class

So there are two ways to create an effect. Either you use the AEffect record directly, or you create a new class which descends from AudioEffect. The first way is troublesome and I don't really see an advantage to it. So here I'll concentrate on creating your own effect class.

There is an example project which demonstrates what is explained here. You can download it.

There are a lot of methods in AudioEffect. To build a basic plugin, you only need to override seven of them. These are the constructor, Process(), getParameterLabel, getParameterDisplay, getParameterName, getParameter and setParameter. To see how to override the constructor and what to do in your own version, see the example project (uExampleEffect.pas). Processing and parameters are described later. Here I want to explain some of the things to do in the overridden constructor.


setProgram(0);
This sets the current program to the first. Programs are explained later.

setNumInputs(1);
Like I said, a plugin processes audio data. For this, it needs inputs and outputs. You can define any number of inputs, but the most used are 1 and 2. The number of inputs and outputs determines wether your plugin is considered a Master effect or not by Cubase. The number of inputs and outputs is also important in the Process() and ProcessReplacing methods, explained later.

setNumOutputs(2);
This sets the number of outputs.

hasVu(TRUE);
Set this to true to tell the host you have a VU. I don't know if this is implemented in Cubase.

canProcessReplacing(TRUE);
Set this to true if you do some processing in ProcessReplacing() as well, and not just in Process().

setUniqueID(FourCharToLong('E', 'x', 'm', 'E'));
You need to fill in four letters to give your plugin a unique id. Cubase uses this to tell plugins apart, so it's VERY important.

suspend;
Calling suspend in the constructor might be necessary, depending on what you do in Suspend().

StrCopy(programName, 'Default');
You need to initialize the program name.

Parameters in a VST plugin

There are two functions you have to override if your effect has any parameters. These are getParameter and setParameter. When you don't have an editor (which is the case we're discussing here), you also need to override getParameterLabel, getParameterName and getParameterDisplay.


procedure setParameter(index: Longint; value: Single); override;
SetParameter gets called by the host when the user has changed the value of a parameter. This allows your effect to update its state. Index indicates which parameter has been changed. Value holds the new value. This new value lies between 0.0 and 1.0. Your effect has to convert this to whatever format it uses internally.

function getParameter(index: Longint): Single; override;
GetParameter gets called when the host wants to know the value of a specific parameter. Index indicates which parameter to return the value for. The result has to be a value between 0.0 and 1.0. Your effect has to convert whatever format is used internally to a floating point value in this range.

procedure getParameterLabel(index: Longint; aLabel: PChar); override;
The host calls GetParameterLabel when it needs a string to tell the user something more about the value of a parameter. Mostly, this is used for the unit of a parameter. For example, for a parameter called Delay which is expressed in milliseconds, you might return ' ms '. Note that the string to return shouldn't be longer than eight characters (although I believe it's possible).

procedure getParameterDisplay(index: Longint; text: PChar); override;
The host calls GetParameterDisplay to get a string representation for the current value of a parameter. You can use some functions in the VSTUtils unit (DVSTUtils in sdk version 2.0.x). See uExampleEffect.pas for more information.

procedure getParameterName(index: Longint; text: PChar); override;
Return the name of the parameter in text.

Processing

In my experience, Process() and ProcessReplacing() are the methods least understood by Delphi programmers who want to make VST plugins. Why is this? I don't really know, but my guess is it has something to do with pointers. These two methods work with the audio data provided by the host. This data is sent to the plugin by a specific kind of parameter, which is used regularly in C/C++, but not very often in ObjectPascal. I'll explain this, but first an explanation of the difference between these two methods.


Difference
The Process method gets audio data, does something to it and then adds the result to the original data. This is done like this :
resultsample := dosomethingwith(currentsample) + currentsample;
ProcessReplacing on the other hand doesn't care about the original sample data once it's done its work.
resultsample := dosomethingwith(currentsample);

That's the difference between Process and ProcessReplacing. It's that simple. ProcessReplacing only gets called when you called canProcessReplacing(TRUE) in your constructor. In general, it's very easy to make a ProcessReplacing method, so there's really no reason not to.

Declaration
Now for the declaration of the two methods:
procedure process(inputs, outputs: PPSingle; sampleframes: Longint);
procedure processReplacing(inputs, outputs: PPSingle; sampleframes: Longint);
As you can see, they have the same parameters. Like I said, it's easy to create a processReplacing method if you already have a Process method.

Sample Values
Samples can have any value between -1.0 and +1.0. A value of 0.0 means no sound. -1.0 and +1.0 mean full sound.

Parameters
Inputs : This is a pointer to the buffers with the original samples.
Outputs : This is a pointer to the buffers where you will store the result of your processing.
SampleFrames : This is the easiest parameter. It tells you how many samples are in the Inputs and Outputs buffers.

Like I said, a lot of people have difficulty with the Inputs and Outputs parameters. Actually, it's quite simple. Inputs is a pointer to the input buffers. It points to the area in memory where you can find the pointer to the first buffer. Look at it as an array of pointers. I'll use a little drawing to make it clear.
Inputs --> inputbuffer1 --> samples for input1
inputbuffer2 --> samples for input2
So provided you defined two inputs in your constructor, Inputs points to two other pointers. These last pointers point to the actual sample data.

Maybe the easiest way to look at it is like this : Inputs is an array of pointers. It has as many elements as your plugin has inputs.
type
PDataBuffer = ^TDataBuffer;
TDataBuffer = array[0..NumSamples-1] of Single;
TInputArray = array[0..NumInputs-1] of PDataBuffer;
Examples
Enough for the theory, how do you get at these buffers? It's quite simple. Look at this example (there are two inputs and two outputs in this example):
procedure Process(inputs, outputs: PPSingle; sampleframes: longint)
var
input1, input2, output1, output2: PSingle;
temp : Single;
i : integer;
begin
// here we get the actual sample buffers
input1 := inputs^;
inc(inputs);
input2 := inputs^;
output1 := outputs^;
inc(outputs);
output2 := outputs^;

// sampleframes tells you how many samples there are in the buffers
for i := 0 to sampleframes-1 do
begin
temp := dosomething(input1^); // get the current sample on the first input and process it
output1^ := temp + output1^; // put the processed sample on the first output
temp := dosomething(input2^); // get the current sample on the second input and process it
output2^ := temp + output2^; // put the processed sample on the second output

inc(input1); // move on to the next sample
inc(input2);
inc(output1);
inc(output2);
end;
end;

And this is immediately an example of a complete Process method. You can also take a look at the Process method in uExampleEffect.pas.

Your own editor

Of course your VST plugin can look much better than the default editor that Cubase and most other hosts provide. How ? Create your own editor window to be shown by the VST host ! It's really not difficult.

Take a look at doubler.zip. This example implements its own editor and should be very useful to start with.

AEffEditor

The first thing to do is create a new class derived from AEffEditor (ADoublerEditor in the example). This class provides the interface between VST and your own editor window. It provides a couple of methods for this purpose.

function getRect(var rect: PERect): longint;
This function gets called when VST wants to know the dimensions of your editor window. You need to return a pointer to an ERect structure in the rect parameter. The result of the function is 0 if you returned a valid record.

function open(ptr: pointer): longint;
In this function you have to create the editor window and assign it a parent handle. The parent handle is provided in the ptr parameter (which is not really a pointer but a THandle).

procedure close;
When this gets called, you destroy the editor window you've previously created.

procedure idle;
This procedure gets called when the user isn't doing anything. If you can think of anything useful to do at such a time, this is the place to do it. The standard behaviour (in AEffEditor) is to call the update procedure (see below).

procedure update;
When this gets called you can update the editor (just stating the obvious here). This procedure gets called when the updateFlag member of AEffEditor has been set to 1, for example by a call to postUpdate (see below).

procedure postUpdate;
This procedure sets the updateFlag member of AEffEditor to 1.

AEffEditor also has a couple of member variables :
effect : This is a pointer to your AEffect structure (passed to the constructor of your descendant of AEffEditor)
systemWindow : The handle of the parent window of the editor (you have to set this in the open function)
updateFlag : If this is one, the editor should be updated

Don't forget to look at the example code in Doubler, it will make things a lot clearer.

Loose ends
Now that you've created the editor class, it's time to let VST know that it exists. Just create an object of your editor class and assign it to the editor member of your plugin class (descendant of AudioEffect).

For example (in the constructor of ADoubler) :
editor := ADoublerEditor.Create(Self);

Of course, you still have to create the form for your editor to actually be visible to the user! Once again, take a look at the Doubler example to get started.

The main function is the only function that gets exported. It needs to be declared with the calling convention cdecl. Take a look at the project file in dexample.zip to find out more. The main function doesn't change much between projects. The only thing that does change is the creation of your effect class.

Versions of VST

There are currently two versions of the VST specification, versions 1 and 2. These are supported by respectively versions 1.x and 2.0.x of the Delphi VST sdk. VST 2 adds some possibilities for virtual instruments to be created as VST plugins. For more information, see the Steinberg website.

Units in the Delphi VST sdk

See the dvstsdk.txt file in the Delphi VST sdk for information about the units (dvstsdk2.txt for version 2.0.x).