←Back to Redwood Audio DSP home

JUCE 3.x for VST Plugin Development (using JUCE 4.x?)

Need Help with this Tutorial? (Contact Us)

Was this useful?  (Consider a Contribution)

Download Final Source Code including built VST

Alright, after the first few steps we have a project - how do we work with it?...  What we will call the JUCE work-flow consists of three types of operations. 

1.  Adding source code or other project dependencies (IntroJucer)

2.  Modifying the graphical user interface (IntroJucer)

3.  Tying the GUI and Source together through the wrapper Plug-In (Visual C++ Express 2010)

 

These steps can be done in almost any order and repeated as needed to work through the whole development life-cycle of your plug-in. General pointers for each step will be provided as well as some examples specific to our stereo width controller.  Note that there are many ways of doing these things and some programmers may prefer a different style.  We hope you find our style easy to read and understand - feel free to optimize as needed.

 

1.  Adding Source Code/Libraries, and other Project Settings

As discussed earlier, anything you would normally want to do to your project settings in your IDE should be handled through the IntroJucer tool (we built earlier).  Realistically, if you never want to manage the project on multiple platforms and are not concerned with keeping the JUCE libraries up to day - you can leave the IntroJucer behind for build management.  It may even become necessary if you need to maintain more complex project settings.  However, if you can retain control through the IntroJucer, there is no reason not to and a few advantages to boot.  Here are all the basic operations that developers will most definitely face.

   

Adding source files:

For this we will need some source files.  Create (or download) a StereoWidthCtrl.h/.cpp file in your source folder.  For illustrative purposes, we will add this to its own folder (Source\StereoWidthCtrl) and go through the adding of include directories as well.

 

StereoWidthCtrl.h

StereoWidthCtrl.cpp

/*Volume-Normalized Stereo Width Control, contributed by Michael Gruhn to the
MusicDSP Source Code Archive: http://musicdsp.org/showArchiveComment.php?ArchiveID=256
'width' is the stretch factor of the stereo field:
width < 1: decrease in stereo width
width = 1: no change
width > 1: increase in stereo width
width = 0: mono*/

class StereoWidthCtrl
{
public:
StereoWidthCtrl();
~StereoWidthCtrl();

//Parameters

void SetWidth(float width);
float Getwidth(void){return m_width;};

//Use

void ClockProcess(float* LeftSample, float* RightSample);

private:
float m_width, sumGain, diffGain;
};

#include "StereoWidthCtrl.h"

StereoWidthCtrl::StereoWidthCtrl(){SetWidth(1.0f);}
StereoWidthCtrl::~StereoWidthCtrl(){}

void StereoWidthCtrl::SetWidth(float width)
{

m_width=width;
float tmp;
if(1.0f+width>2.0f)
tmp=1.0f/(1.0f+m_width);
else
tmp=1.0f/2.0f;

diffGain=m_width*tmp;
sumGain=tmp;

}

void StereoWidthCtrl::ClockProcess(float* LeftSample, float* RightSample)
{

float m = sumGain*(*LeftSample+*RightSample);
float s = diffGain*

(*RightSample-*LeftSample);

*LeftSample=m-s;
*RightSample=m+s;

}

 

Make sure Visual C++ Express 2010 is closed before modifying the project (not strictly necessary to close, but it will eventually force a project reload anyway).  Then open the IntroJucer.exe and select your plug-in project (this will be in the "recent files" list and can be selected) File→Recent Files and select the StereoWidthCtrl project. otherwise, use File→Open and browse to your project folder, select the *.jucer file.  It should also automatically open your most recent project on launch.

 

You will see your project settings/code on the right.  The left shows your source code or config trees - Select the "Files" Tab to show the soruce tree.  These are your source files as seen in Visual C++ Express 2010.  You can use this tree control to add your source files. 

 

Make a folder: Right click the "Source" folder→Add new group→type the name→hit <Enter>. 

Add exisiting files: Right click the new folder->select "add existing files..." select our StereoWidthCtrl.h/.cpp files 

 

Upon Saving the IntroJucer project, the files and directory structure we have chosen will be added to our Visual C++ Express project.

 

 

Additional Include Directories:

Since we created a new source directory (or if you are including files from elsewhere on your computer) we need to add the source path to your "additional include directories". To do that, go back to the "Config" Tab and select one of the build configurations in the tree under "Visual Studio 2010". 

 

Starting with "Debug", find "Header Search Paths" and add your folders separated by ";" for multiple listings.  In our example:

Header Search Paths     ..\..\Source\StereoWidthCtrl;

 

Repeat for the "Release" configuration.

Save the Project.

 

When the IntroJucer project is saved, the Visual C++ Express 2010 project and solution files will be updated.  Your solution explorer source will include your new files, and the any "Header Search Paths" will be added to the project. 

 

Note: If you have ever have include problems, you can debug your include folder settings from the IDE (In Visual C++ Express 2010, under Project Properties→Configuration Properites→C/C++→General→Additional Include Directories).  Make adjustments as needed until it builds.  Just make sure to then return to the IntroJucer, add each custom line back to the "Header Search Paths", save and relaunch Visual C++ Express for a final test build.   

 

Adding Libraries:

While not used for this tutorial, it is very common to have to add particular library folders or libraries to the project.  For that the IntroJucer provides a setting called "extra library search paths" This is equivalent to adding listings in Visual C++ Express Project Properties→Configuration Properites→VC++ Directories→Library Directories or modifying the environmental variable "LIB".  The settings are separate for release and debug build configurations.

 

Other useful Project Settings:

The other settings in the IntroJucer worth noting here are the Preprocessor Definitions and the build commands -though you may want to explore the other settings on your own. 

 

The Preprocessor Definitions, are just as you might expect, and are provided for each configuration (Debug and Release) - but also for global settings you can apply them directly to the "Visual Studio 2010" settings section under "Extra Preprocessor Definitions".

 

The build commands include "Pre-Build Commands", and "Post-Build Commands" for each configuration type.  These are also used as expected (command line operations added to the build process), but the post-build step is particularly dear to our heart when dealing with building VST plug-ins. As a recommended step in the tutorial, add a post-build command to your release mode configuration which copies the DLL to your systems VST folder... 

 

**note:  This next step is done automatically by JUCE in OSX Xcode!  See deployment information on the OSX Setup tab. 

 

If you have not already as a VST developer, give your VST folder an environment variable on your PC.  For example, VSTPluginFolder = C:\VST which is where you have set all of your host applications look for VST plug-ins. 

 

Then under the "Configuration Release:"

Post-Build Command    copy /Y "$(TargetPath)" "$(VSTPluginFolder)"

 

You can also just hard-code the path to your VST Plugin-Folder.  Either way, this is a useful step as now each time you build your release build, the newly created VST is copied to your VST Plug-in Folder and thus instantly available in your host of choice.

 

**

 

Some hosts, such as AudioMulch, might need a refresh, others may need to be relaunched to scan the VST Folder for changes.

 

At this point in the tutorial, we have done all project settings we are going to do through the IntroJucer and it can be closed.  Remember, if you go back to update project settings later - close the IDE and return to the IntroJucer to make the needed changes.

 

2.  GUI Editing with IntroJucer

This is the fun part!  Adding a graphical interface to our plug-in.  User interface changes are now made through the Introjucer application by clicking on the *.cpp file for the GUI component we want to edit on the "Files" tab.  Once selected, the five tabs in the main window functionally replace your resource editor in Visual Studio (a good thing, since it is missing from the Express edition of Microsoft IDEs).

 

These types of edits only effect the source files, so from here-on-out you can safely leave open Visual C++ Express.  Each time you make a change:

1. Save any code edits you have been doing in Visual C++ Express

2. Make your graphical changes in the Introjucer, Save, and

3. Switch back to Visual C++ Express (making sure to allow it to reload files modified outside the editor - it should prompt you after any changes)

 

So let's try making some changes...

Select your PluginEditor.cpp using the Introjucer's "File" tab

We have already input the needed class settings, so the only thing of interest on the class tab might be the default size of the plug-in GUI (600x400) - from here you can adjust the default or lock it down (to prevent your window being resized in the VST host).

 

The other tabs of the main window allow you to select the various layers or the resource editor.  The most basic of which are "Graphics" and "Subcomponents" tabs.  The Graphics tab lets you set your background color (or layout the background skin of your plug-in, draw basic static graphic objects etc.).

 

For now, let's just set the background to black (the default white is hard on the eyes).

Click the Graphics Tab→background→use the color chooser to select a color.  You are also able to enter your RGB and Alpha values directly in the dialog box.

 

 

Next we will add our user controls (or subcomponents which can includ embeded child GUI components etc) - for the tutorial, we will use one slider for the width control, and one button to bypass the effect.  To add components:

Click the Subcomponents Tab, then on the toolbar GUI Editor→Add New Component→Select Component

 

Follow that procedure to add one "Slider" and one "Text Button".  We will also add a text label for our control.  You will notice that each component type has its own properties which can be set as needed.  The following parameters are only the changes made to each component for the tutorial.  For additional help with controls or graphics, please consult JUCE documentation or other Jucer guides.

 

Juce GUI Layout

Label:

Text

Stereo Width Factor:

Text(color)

FF808080

 

Slider:

Member Name

WidthCtrlSld

name

Width Factor Slider

Min

0

Max

5

Interval

0.1

 

Text Button:

Member Name

BypassBtn

name

Bypass Button

Text

Bypass

 

 

 

 

When you are done, save the changes to the editor (File→Save "PluginEditor.cpp").  When you switch back to your Visual C++ Express window, it will indicate changes were made in an external editor - allow it to load them and continue working.  In general, you can always come back to this editor, make changes and save without conflict...  Though, care should be taken to save work before switching editors to avoid overwriting any changes.

 

3.  Working with the VST Wrapper

OK, we have some our source code loaded and some UI elements to control it.  The rest of the process is simply to tie it all together.  In this section, we are really just telling the VST wrapper how we want it to behave in the host.  It is assumed you have already done the framework changes in the "Project Setup" phase of the tutorial.  The pace here will be slightly fast with some style assumptions - for some additional details or tricks see the "Additional Notes" section (which will explore additional detail on some of the changes here).

 

 PluginProcessor.h/cpp

At this point it is worth pointing out that the PluginProcessor is merely source included by the project and it will no longer be updated by anything other than your direct changes.  This is good, because it means you can add whatever you want as long as it conforms to the required structure for the wrapper (don't remove any of the default functions and you are good).

 

First up, let's add an instance of our effect source and some local parameters to control it.  In PluginProcessor.h

Add an include for the source code:  "#include "StereoWidthCtrl.h"

 

In the class declaration for StereoWidthCtrlAudioProcessor - Add a public parameter list and private instances (using the "private:" on line 69 as reference):

//Custom Methods, Params and Public Data

enum Parameters{MasterBypass=0, StereoWidth, totalNumParam};

bool NeedsUIUpdate(){return UIUpdateFlag;};

void RequestUIUpdate(){UIUpdateFlag=true;};
void ClearUIUpdateFlag(){UIUpdateFlag=false;};

private:

//Private Data, helper methods etc.

float UserParams[totalNumParam];

StereoWidthCtrl mWidthControl;

bool UIUpdateFlag; 

 

This gets a few things happening.  The use of a parameter list and float array for storing user parameters provides a very simple and reliable way of interfacing through to the VST world (which uses float parameters).  It also keeps things flexible.  If you want to add another parameter later, it is just a matter of inserting it in the enumerated list and many VST-centric features will be automatic (this will be more obvious shortly).  Finally, it adds a flag mechanism to request updates of the UI from internal states - this can be used to bubble up internal states to the UI and also when you recall parameters from file. 

 

The next step is to update the default implementation of our member functions in PluginProcessor.cpp.  These will be tackled in the order of appearance in the file (skipping a few we don't need in the tutorial) - which is how we recommend working for the first pass.  After the basics, you can go back and handle updates in any order you like.

 

Initialize UserParams in the Constructor:

StereoWidthCtrlAudioProcessor::StereoWidthCtrlAudioProcessor()

{

UserParams[MasterBypass]=0.0f;//default to not bypassed

UserParams[StereoWidth]=1.0f;//default Width 1.0 (no change)

mWidthControl.SetWidth(UserParams[StereoWidth]);//push VST default to effect

UIUpdateFlag=true;//Request UI update

}

 

Update the number of parameters returned to our "totalNumParam"

int StereoWidthCtrlAudioProcessor::getNumParameters(){return totalNumParam;}

 

Implement getParameter with internal values

float StereoWidthCtrlAudioProcessor::getParameter (int index)

 {

switch(index)

 {

case MasterBypass://example nothing special

return UserParams[MasterBypass];

case StereoWidth://example update from internal

UserParams[StereoWidth]=mWidthControl.Getwidth();

return UserParams[StereoWidth];

default: return 0.0f;//invalid index

}

}

 

Implement setParameter with internal values

void StereoWidthCtrlAudioProcessor::setParameter (int index, float newValue)

{

switch(index)

{

case MasterBypass:

UserParams[MasterBypass]=newValue;

break;

case StereoWidth:

UserParams[StereoWidth]=newValue;

mWidthControl.SetWidth(UserParams[StereoWidth]);

break;

default: return;

}

UIUpdateFlag=true;//Request UI update -- Some OSX hosts use alternate editors, this updates ours

}

 

Implement getParameterName

const String StereoWidthCtrlAudioProcessor::getParameterName (int index)

 {

 switch(index)

{

case MasterBypass: return "Master Bypass";

case StereoWidth: return "StereoWidth Factor";

default:return String::empty;

}

}

 

Implement getParameterText

const String StereoWidthCtrlAudioProcessor::getParameterText (int index)

{

if(index>=0 && index<totalNumParam)

return String(UserParams[index]);//return parameter value as string

else return String::empty;

}

 

OK - it is time we did some processing!  This is done in the ProcessBlock call.  Processing here is in-place on non-interleaved channel data.  Note that in many VST hosts, the helper function for NumberOfInputChannels will always be your max channel count (or input pin count).  Items from the buffer, like number of samples can be trusted.

void StereoWidthCtrlAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)

{

 if(getNumInputChannels()<2 || UserParams[MasterBypass])

 {}/*Nothing to do here - processing is in-place, so doing nothing is pass-through (for NumInputs=NumOutputs)*/ 

else

{//Not bypassed - do processing!

float* leftData = buffer.getWritePointer(0);

float* rightData = buffer.getWritePointer(1);

for(long i=0; i<buffer.getNumSamples();i++)

mWidthControl.ClockProcess(&leftData[i], &rightData[i]);

}

}

 

 

Implement Set and Get State information - for now, we are doing this manually.  Check the additional notes section for a shortcut which reads/writes all values as a single tokenized string.

void StereoWidthCtrlAudioProcessor::getStateInformation (MemoryBlock& destData)

{//Save UserParams/Data to file

XmlElement root("Root");

XmlElement *el;

el = root.createNewChildElement("Bypass");

el->addTextElement(String(UserParams[MasterBypass]));

el = root.createNewChildElement("StereoWidth");

el->addTextElement(String(UserParams[StereoWidth]));

copyXmlToBinary(root,destData);

 }

 

void StereoWidthCtrlAudioProcessor::setStateInformation (const void* data, int sizeInBytes)

{//Load UserParams/Data from file

XmlElement* pRoot = getXmlFromBinary(data,sizeInBytes);

if(pRoot!=NULL)

{

forEachXmlChildElement((*pRoot),pChild)

{

if(pChild->hasTagName("Bypass"))

{String text = pChild->getAllSubText();

setParameter(MasterBypass,text.getFloatValue());}

else if(pChild->hasTagName("StereoWidth"))

{String text = pChild->getAllSubText();

setParameter(StereoWidth,text.getFloatValue());}

}

delete pRoot;

UIUpdateFlag=true;//Request UI update

}

}

 

PluginEditor.cpp

Ok - final steps...  We have all of our plug-in parameters, accessor functions, we can save/load state info,  and most importantly - processing is happening.  Now we want to control it.  The final step is to fill in the actions of the UI.  Just one not of caution - this file is still under management of the Jucer GUI editor.  If you make changes outside of designated areas, you may loose them with subsequent GUI updates.  Remember to stay between comments for editable regions - or in the designated user code section.

 

First, we will tell our Bypass button that we want it to toggle its state when clicked (this makes the functionality like a check-box and would allow you to change the display styles according to its state).  The change is simple - just add the following line to the constructor - just below were we initialized our timerCallback.

//[Constructor] You can add your own custom stuff here..

 getProcessor()->RequestUIUpdate();// UI update must be done each time a new editor is constructed

startTimer(200);//starts timer with interval of 200mS

BypassBtn->setClickingTogglesState(true);

//[/Constructor]

  

Next, we will return to the timerCallback function we made earlier and add code to update for the current state when asked by the plug-in (this handles our UI initialization):

void StereoWidthCtrlAudioProcessorEditor::timerCallback()

{

StereoWidthCtrlAudioProcessor* ourProcessor = getProcessor();

//exchange any data you want between UI elements and the Plugin "ourProcessor"

if(ourProcessor->NeedsUIUpdate())

{

BypassBtn->setToggleState(1.0f == ourProcessor->getParameter(StereoWidthCtrlAudioProcessor::MasterBypass), dontSendNotification);

WidthCtrlSld->setValue(ourProcessor->getParameter(StereoWidthCtrlAudioProcessor::StereoWidth), dontSendNotification);

ourProcessor->ClearUIUpdateFlag();

}

}

 

 

 

The final step is to connect our UI control functions to the corresponding setParameter calls.  JUCE does a good job of making this simple - all the controls of a certain type are grouped together with clear comments.  For example, all Button events go to the "buttonClicked" function and where you put your code for the Bypass button event will be highlighted for you.  The last code section below shows both the slider and button functions for our controls...

 

void StereoWidthCtrlAudioProcessorEditor::sliderValueChanged (Slider* sliderThatWasMoved)
{

//[UsersliderValueChanged_Pre]
StereoWidthCtrlAudioProcessor* ourProcessor = getProcessor();
//[/UsersliderValueChanged_Pre]

if (sliderThatWasMoved == WidthCtrlSld)
{

//[UserSliderCode_WidthCtrlSld] -- add your slider handling code here..
ourProcessor->setParameter(StereoWidthCtrlAudioProcessor::StereoWidth, (float)WidthCtrlSld->getValue());
//Note OSX - some hosts use extra GUIs which may need update - you can notify them as well by replacing "setParameter" with "setParameterNotifyingHost" using the same arguments

//[/UserSliderCode_WidthCtrlSld]

}

//[UsersliderValueChanged_Post]
//[/UsersliderValueChanged_Post]

}

void StereoWidthCtrlAudioProcessorEditor::buttonClicked (Button* buttonThatWasClicked)
{

//[UserbuttonClicked_Pre]
StereoWidthCtrlAudioProcessor* ourProcessor = getProcessor();
//[/UserbuttonClicked_Pre]

if (buttonThatWasClicked == BypassBtn)
{

//[UserButtonCode_BypassBtn] -- add your button handler code here..
ourProcessor->setParameter(StereoWidthCtrlAudioProcessor::MasterBypass, (float)BypassBtn->getToggleState());
//Note OSX - some hosts use extra GUIs which may need update - you can notify them as well by replacing "setParameter" with "setParameterNotifyingHost" using the same arguments

//[/UserButtonCode_BypassBtn]

}

//[UserbuttonClicked_Post]
//[/UserbuttonClicked_Post]

}

 

 

OK - That's it... A final build should be all that is between you and a functional StereoWidthControl VST pluig-in.  Here is a shot of the finished GUI running in AudioMulch.  You can download the source material from this tutorial [here].

 

Finished Plugin

 

Hopefully, this has given you some ideas or can serve as a reference while you start your own development projects.  When starting your next project, you may also want to just check back to our quick summary page.  There we will keep it short for when you just need to speed through the highlights as a general task list.  Or for some additional tips and tricks, you can skip to our additional notes section.  As always, if you have your own comments or questions - feel free to contact us.