|
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.
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:
AudioEffectX
main()
function itself.#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() . |
canProcessReplacing()
(see ‘Basic Concepts’ for more information).strcpy(programName,
"Default")
. The program name is displayed in the rack, and can
be edited by the user.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. |
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. |
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.
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.
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.
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 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. |
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. |
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.ADelay::~ADelay () { if (buffer) delete[] buffer; if (programs) delete[] 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.
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. |
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.
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.