Page tree
Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 43 Next »

This tutorial will explain you how to create a new Audio Plug-in by using the VST 3 Project Generator included in the VST 3 SDK and how to add some basic features.

The artifact will be an audio Plug-in that can compute a Gain to a audio signal and can be loaded into VST3 hosts like Cubase, Wavelab,...



Part 1: Get and install the VST 3 SDK

For downloading the SDK see this section "How to setup my system in order to build VST3 plug-ins".

You have the different possibilities to start a new project:

  • you can use the helloworld template included in the VST SDK and duplicate the folder into a new folder and then adapt each file where comment mentions it.
  • or, which is easier and recommended, you can use the VST3 Project Generator application included in the VST SDK.



Part 2: Use the VST 3 Plug-in Project Generator application

The VST3 Project Generator application included in the VST SDK  is available for Windows and for macOS.

Start the application, located in the VST3_Project_Generator folder of the VST SDK.

Check that the Preferences tab has the wanted information: see Setting the Preferences

In the Create Plug-in Project tab you have to enter some information about your Plug-in you want create:


Check Create Plug-in Project tab of VST 3 Project Generator for more detailed documentation.

Then when all field have been filled, you could click on Create, a script is started which creates in the Output Directory a project with adapted files, after this step the IDE (Visual Studio or XCode) is launched.

You can then compile the project and test your newly created Plug-in.

If you have choose Audio Effect as Type a simple Stereo→Stereo Plug-in is created.

A good way to understand how a VST 3 Plug-in works is to add breakpoints in each function in the processor and controller files:

tresult PLUGIN_API MyPluginController::initialize (FUnknown* context);
tresult PLUGIN_API MyPluginController::terminate ();
//...
tresult PLUGIN_API MyPlugin::initialize (FUnknown* context);
//...

and start a VST 3 host from the debugger.



Part 3: Coding your Plug-in

Now you have an automatically generated frame for your Plug-in. In the following sections, it will be explained how to add a new parameter, its associated processing algorithm, and others specific features like saving/loading project or presets, create a dedicated User Interface,...

A VST 3 Plug-in contains two main classes: its PlugProcessor (the main part doing the processing and persistence) and its PlugController (taking care of communication with the DAW, handling parameters and the UI).

Add a parameter: Gain

In this basic Plug-in example, we will add a Gain parameter which should modify the volume of the audio going thru the Plug-in.

For this, each VST 3 parameter requires a unique Identifier (a number).

  1. Open the file plugids.h and enter a new id kParamGainId, here we choose to assign the unique number 102.
enum GainParams : Vst::ParamID
{
    kParamGainId = 102,
};


2. Open now the plugcontroller.cpp file, and add the gain parameter with the parameters.addParameter

//-----------------------------------------------------------------------------
tresult PLUGIN_API PlugController::initialize (FUnknown* context)
{
    tresult result = EditController::initialize (context);
    if (result == kResultTrue)
    {
        //---Create Parameters------------
         parameters.addParameter (STR16 ("Gain"), STR16 ("dB"), 0, .5, Vst::ParameterInfo::kCanAutomate, GainParams::kParamGainId, 0);
    }
    return kResultTrue;
}

Note

  • we add the flag kCanAutomate which will inform the DAW/Host that this parameter could be automated if wanted. (what is automation?)
  • a VST 3 parameter is always normalized (its value is a floating point value between [0, 1]), here its default value is set to 0.5.


3. Now we will adapt the processor part for this new parameter. Open the file plugprocessor.h and add gain value  Vst::ParamValue mGain. This value will be used for the processing to apply the gain.


// ...  
static FUnknown* createInstance (void*)
{
	return (Vst::IAudioProcessor*)new PlugProcessor (); 
}
protected:
	Vst::ParamValue mGain = 1.;
// ...

Add the process applying the gain

  1. we need to set our internal mGain with its wanted value from the host, this is the first step of the process method, parse the parameters change coming from the host in the structure data.inputParameterChanges for the current audio block to process.
//-----------------------------------------------------------------------------
tresult PLUGIN_API PlugProcessor::process (Vst::ProcessData& data)
{
    //--- First : Read inputs parameter changes-----------
    if (data.inputParameterChanges)
    {
		// for each parameter defined by its ID
		int32 numParamsChanged = data.inputParameterChanges->getParameterCount ();
        for (int32 index = 0; index < numParamsChanged; index++)
        {
            // for this parameter we could iterate the list of value changes (could 1 per audio block or more!)
			// in this example we get only the last value (getPointCount - 1)
			Vst::IParamValueQueue* paramQueue = data.inputParameterChanges->getParameterData (index);
            if (paramQueue)
            {
                Vst::ParamValue value;
                int32 sampleOffset;
                int32 numPoints = paramQueue->getPointCount ();
                switch (paramQueue->getParameterId ())
                {
                    case GainParams::kParamGainId:
                        if (paramQueue->getPoint (numPoints - 1, sampleOffset, value) == kResultTrue)
                            mGain = value;
                        break;
				}
 			}
		}
	}
	// ....
}


data.inputParameterChanges could include more than 1 change for the same parameter inside a processing audio block. Here we take only the last change in the list and apply it our mGain.

2. Now the real processing part:

	// ...
	//--- Here you have to implement your processing
	//---get audio buffers----------------
	uint32 sampleFramesSize = getSampleFramesSizeInBytes (processSetup, data.numSamples);
	void** in = getChannelBuffersPointer (processSetup, data.inputs[0]);
	void** out = getChannelBuffersPointer (processSetup, data.outputs[0]);
	
	// Here could check the silent flags
	// now we will produce the output
	// mark our outputs has not silent
	data.outputs[0].silenceFlags = 0;
	
	float gain = mGain;
	// for each channel (left and right)
	for (int32 i = 0; i < numChannels; i++)
	{
		int32 samples = data.numSamples;
		Sample32* ptrIn = (Sample32*)in[i];
		Sample32* ptrOut = (Sample32*)out[i];
		Sample32 tmp;
		// for each sample in this channel
		while (--samples >= 0)
		{
			// apply gain
			tmp = (*ptrIn++) * gain;
			(*ptrOut++) = tmp;
		}
	}
	//...

3. VST 3 includes a way for the host to inform the Plug-in that its inputs are silent or not (using the VST 3 silence flags), we will show how to take care of this:

	// Here could check the silent flags
    //---check if silence---------------
	// normally we have to check each channel (simplification)
	if (data.inputs[0].silenceFlags != 0)
	{
		// mark output silence too
		data.outputs[0].silenceFlags = data.inputs[0].silenceFlags;

		// the Plug-in has to be sure that if it sets the flags silence that the output buffer are clear
		for (int32 i = 0; i < numChannels; i++)
		{
 			// do not need to be cleared if the buffers are the same (in this case input buffer are
			// already cleared by the host)
			if (in[i] != out[i])
			{
				memset (out[i], 0, sampleFramesSize);
			}
		}
		// nothing to do at this point
		return kResultOk;
    }

Add store/restore state

The Processor part represents the state of the Plug-in, so it is its job to implement the getState/setState method used by the host to save/load projects and presets

  1. In the plugprocessor.cpp add in the getState method the mGain value to the state stream given by the host which will save it as project or preset.
//------------------------------------------------------------------------
tresult PLUGIN_API PlugProcessor::getState (IBStream* state)
{
	// here we need to save the model (preset or project)
	float toSaveParam1 = mGain;
	IBStreamer streamer (state, kLittleEndian);
	streamer.writeFloat (toSaveParam1);
	return kResultOk;
}

2. In the setState () method the Plug-in get from the host a new state (called after a project or preset is loaded).

//------------------------------------------------------------------------
tresult PLUGIN_API PlugProcessor::setState (IBStream* state)
{
	if (!state)
		return kResultFalse;
	// called when we load a preset or project, the model has to be reloaded
	IBStreamer streamer (state, kLittleEndian);
	float savedParam1 = 0.f;
	if (streamer.readFloat (savedParam1) == false)
		return kResultFalse;
	mGain = savedParam1;

	return kResultOk;
}

Part 4: Next Advanced Steps

Add a Event Input

In our example we want to get modify our current Gain factor by a the velocity of a played "MIDI" event (noteOn).

  1. If you need in your Plug-in to receive not only audio but event (like MIDI), you have to add a Event input. For this you just have to add in , in order to do this call in the initialize() method of the processor addEventInput:
//------------------------------------------------------------------------
tresult PLUGIN_API PlugProcessor::initialize (FUnknown* context)
{
	//---always initialize the parent-------
	tresult result = AudioEffect::initialize (context);
	// if everything Ok, continue
	if (result != kResultOk)
	{
		return result;
	}

	//....

	//---create Event In/Out busses (1 bus with only 1 channel)------
	addEventInput (STR16 ("Event In"), 1);

	return kResultOk;
}

In this example we add 1 input event bus, receiving only on 1 channel, if you need for example to receive differentiated events from different channels, just change it like this:

addEventInput (STR16 ("Event In"), 4); // here 4 channels

2. we create a new internal value mGainReduction (not exported to the host) which will be changed by the velocity of a played noteOn, harder the note is played higher will be the gain reduction (this is what we want here):

// ...  
static FUnknown* createInstance (void*)
{
	return (Vst::IAudioProcessor*)new PlugProcessor (); 
}
protected:
	Vst::ParamValue mGain= 1.;
	Vst::ParamValue mGainReduction = 0.;

// ...

3. now we have to get the event changes in the process method

//------------------------------------------------------------------------
tresult PLUGIN_API PlugProcessor::process (ProcessData& data)
{
	//--- First : Read inputs parameter changes-----------
	//...

	//---Second : Read input events-------------
	// get the list of all event changes
	IEventList* eventList = data.inputEvents;
	if (eventList) 
	{
		int32 numEvent = eventList->getEventCount ();
		for (int32 i = 0; i < numEvent; i++)
		{
			Event event;
			if (eventList->getEvent (i, event) == kResultOk)
			{
				// here we do not take care of the channel info of the event
				switch (event.type)
				{
					//----------------------
					case Event::kNoteOnEvent:
						// use the velocity as gain modifier: a velocity max (1) will lead to silent audio
						mGainReduction = event.noteOn.velocity; // value between 0 and 1
						break;
					
					//----------------------
					case Event::kNoteOffEvent:
						// noteOff reset the gain modifier
						mGainReduction = 0.f;
						break;
				}
			}
		}
	}

4. make use of this mGainReduction in our real processing part:

//-----------------------------------------------------------------------------
tresult PLUGIN_API PlugProcessor::process (Vst::ProcessData& data)
{
	//....


	float gain = mGain - mGainReduction;
	if (gain < 0.f)	// gain should always positive or zero
		gain = 0.f;

	// for each channel (left and right)
	for (int32 i = 0; i < numChannels; i++)
	{
		int32 samples = data.numSamples;
		Sample32* ptrIn = (Sample32*)in[i];
		Sample32* ptrOut = (Sample32*)out[i];
		Sample32 tmp;
		// for each sample in this channel
		while (--samples >= 0)
		{
			// apply gain
			tmp = (*ptrIn++) * gain;
			(*ptrOut++) = tmp;
		}
	}
	//...
}

Add a Audio Side-chain Mono

how to add a Audio Bus + add use of SC in the processing


On this page:

  • No labels