←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
In our tutorial, we stepped through a few illustrative details for a Stereo Width Controller plug-in. Here, we aim to summarize the key steps to reference as a more generic task list for new projects. For details, refer back to the tutorial sections or see the additional notes for some more practical implementations and additional features. Note that you would replace references to YourProjectName with your actual project name.
As a reminder - any changes to the PluginEditor.h/.cpp must be in designated areas (marked by comments in the files). Anything outside of these areas will be lost the next time you save the file from the Jucer GUI editor. On the other hand, PluginProcessor.h/.cpp are not as closely auto-managed and any changes can be made as long as you don't remove any of the default functions.
1. Setup (Getting Started with Free Development)
Install development software (Microsoft Visual C++ Express 2010 or Apple Xcode= free!)
Download the VST SDK3.x from Steinberg's development portal and install to c:\SDKs\VST3 SDK
For AU in OSX Download the CoreAudioUtility Classes see OSX Setup tab for details
Download the JUCE library and install to c:\juce
Build IntroJucer.exe (c:\juce\extras\Introjucer\Builds\VisualStudio2010), use it to select/update the JUCE Modules to use in your project.
Make an Environment variable pointing to your VST Plugin Folder (used by VST host software) - For example, VSTPluginFolder = c:\VST (Not needed in OSX)
2. Starting a New VST Plug-In Project
Create your project with IntroJucer.exe, select type as AudioPlug-In and configure at least the properties for project/plug-in names and Plugin Channel Configuration.
Add any additional exporter targets or configurations (Visual Studio 2010 should be setup by default) and save.
Add Post-Build Command (release mode): copy /Y "$(TargetPath)" "$(VSTPluginFolder)" for quick testing of your plugin (Not needed in OSX).
Use the IntroJucer.exe "Files" tab to create a replacement PluginEditor.h/.cpp (source files for VST GUIs in JUCE).
Right-click the file list and "Add a new GUI component" - saving over the existing PluginEditor.h in you project's source folder... Click the new PluginEditor.cpp in the file list and select the "Class" tab to configure as follows:
Class Name: YourProjectNameAudioProcessorEditor
Parent Class: public AudioProcessorEditor, public Timer
Constructor Parameters: YourProjectNameAudioProcessor& ownerFilter
Initializers: AudioProcessorEditor(ownerFilter)
Save this new GUI class (File→Save "PluginEditor.cpp").
Edit your project in Visual C++ Express and complete the basic code setup:
PluginProcessor.h
Add User Parameter Support
//Custom Methods, Params and Public Data
enum Parameters{MasterBypass=0, /*OtherParams...,*/ totalNumParam};
bool NeedsUIUpdate(){return UIUpdateFlag;};
void RequestUIUpdate(){UIUpdateFlag=true;};
void
ClearUIUpdateFlag(){UIUpdateFlag=false;};
private:
//Private Data, helper methods etc.
float UserParams[totalNumParam];
bool UIUpdateFlag;
PluginProcessor.cpp
Initialize UserParams in the Constructor:
YourProjectNameAudioProcessor::YourProjectNameAudioProcessor()
{
UserParams[MasterBypass]=0.0f;//default to not bypassed
//repeat for "OtherParams"
UIUpdateFlag=true;//Request UI update
}
Update the number of parameters returned to our "totalNumParam"
int YourProjectNameAudioProcessor::getNumParameters(){return totalNumParam;}
Implement getParameter with internal values
float YourProjectNameAudioProcessor::getParameter (int index)
{//todo: add cases for any special parameters
if(index>=0 && index<totalNumParam)
return UserParams[index];
else
return 0.0f;//invalid index
}
Implement setParameter with internal values
void YourProjectNameAudioProcessor::setParameter (int index, float newValue)
{//todo: add cases for any special parameters
if(index>=0 && index<totalNumParam)
UserParams[index]=newValue;
UIUpdateFlag=true;//Request UI update for when it did not come from the primary editor
}
Implement getParameterName
const String YourProjectNameAudioProcessor::getParameterName (int index)
{
switch(index)
{
case MasterBypass: return "Master Bypass";
//OtherParams...
default:return String::empty;
}
}
Implement getParameterText
const String YourProjectNameAudioProcessor::getParameterText (int index)
{
if(index>=0 && index<totalNumParam)
return String(UserParams[index]);//return parameter value as string
else return String::empty;
}
All of your custom processing implementation will live in the "processBlock" call - but this already has a reasonable default implementation for the quick setup.
Finally, you may wish to implement the ability to save/load state information such as your user parameters. Our recommended way of doing this (with a string tokenizer for all UserParams[totalNumParam]) is described in the additional notes section. However, individual parameters or other data can be selectively saved using individual Xml tags (as was done in the tutorial). Please refer back to either for sample code. Either way, remember to request a UI update upon loading state information - other wise your UI will be out of sync with your core code. Simply set the "UIUpdateFlag=true;".
PluginEditor.h
Correct the included headers - they should be:
//[Headers]
#include "JuceHeader.h"
#include "PluginProcessor.h"
//[/Headers]
Add timerCallback and processor accsessor function
//[UserMethods] -- You can add your own custom methods in this section.
void timerCallback();
YourProjectNameAudioProcessor* getProcessor()
const
{return static_cast
<YourProjectNameAudioProcessor*>(getAudioProcessor());}
//[/UserMethods]
PluginEditor.cpp
Implement the timerCallback features
Initialize the timer in the Constructor:
//[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
//[/Constructor]
Implement the timerCallback function:
//[MiscUserCode] You can add your own
definitions of your custom methods or any other code here...
void YourProjectNameAudioProcessorEditor::timerCallback()
{
YourProjectNameAudioProcessor* ourProcessor = getProcessor();
if(ourProcessor->NeedsUIUpdate())
{//load your UI components with internal state information from plug-in - example:
/*YourButtonName->setToggleState(1.0f == ourProcessor->getParameter(YourProjectNameAudioProcessor::MasterBypass), false);*/
//repeat for "OtherParams"...
ourProcessor->ClearUIUpdateFlag();
}
}
//[/MiscUserCode]
3. Work-Flow
The code above has setup a solid template/framework for your project. In this section, we will recap the JUCE work-flow and how to go about updating it with your custom code and graphical interface.
Adding GUI Components (Getting user parameters to the user)
Updates to the GUI for your plug-in are accomplished by selecting your PluginEditor.cpp in the Introjucer.exe. It will play nice back and forth with editing in the IDE (Visual C++ Express 2010), in that you can have it open in both at any time. However, care should be taken to ensure you are only making unsaved changes in one editor at a time.
Use of the Introjucer for editing is relatively straight forward. There is a "Class settings" tab which we have already configured everything but the size settings. You can specify a size and prevent the user from resizing the window if desired.
The UI components are added on the "Subcomponents" tab. Here you can add all the typical controls you might want - buttons, combobox, sliders etc. As you add components you can configure their properties, position/resize them on the window etc. The Graphics tab allows you to change the background color, load images for skins etc. They will be shown with any components you have added as an overlay.
Once it looks the way you want (and you have named components appropriately) you can save your work. And tie in your controls to code. You can return to the here at any time and change properties (such as slider range), layout, etc. Note, most - if not all properties for components can also be modified programmatically in the code.
There are two manual code updates to PluginEditor.cpp needed for each UI component you add with the Introjucer (everything else is handled automatically).
In the timerCallback() function we created, add code to initialize the UI component from the AudioProcessor's UserParams. This will be called on the timer interval, but only runs when the processor initializes defaults, and again each time the state is loaded by the plug-in.
The second change depends on the component type, as each component type has its own message handler - such as when Buttons are clicked, or sliders are moved. Within the message handler, the IntroJucer will have automatically separated cases for each component of each type. So, all you have to do is call your processor with an appropriate setParameter() - reading the value from the UI component. Alternatively, you can call any other methods you created for your AudioProcessor.
Adding Source Code/Libraries or other Build/Project Settings
To maintain the JUCE work-flow (which allows you to pull JUCE updates and add additional build targets to your source code) you must make all updates in IntroJucer.exe (not the IDE - no matter how tempting the solution explore may appear). When you need to make any of the following changes, it is best to save your files in Visual C++ Express and close the project. Then open the project file (*.jucer) in the IntroJucer.exe. Make your changes, save, and open the resulting build project back in Visual C++ Express. The things you can do here are:
Add existing Source Code using the source file list on the "Files" tab of the Introjucer. It works about as you would expect the solution explorer to function in the IDE. you can add folders and source - the results will be reflected in the IDE when you load your project. Note that to get back to the regular project settings (VST settings) you have to select the main project (with the orange slice Icon) on the "Config" tab.
If your source files are not in the Source folder, you will need to add additional include directories. This works much the way it would in the IDE. On the "Config" tab of the IntroJucer are both debug and release settings for each build target. You would simply add the needed include directories to the corresponding "Header Search Paths". Similarly, you can add library directories using the "Extra Library Search Paths" option in the configuration.
Some settings like Preprocessor Definitions can be added both at the configuration level (debug or release) and the project level (applied to both debug and release).
Other build settings you may find useful include your pre/post build commands, optimization settings, and binary names.
For additional details, consider skimming through our tutorial and/or additional notes or look straight from the source... There is good class documentation for all of the JUCE library classes available on their website as well as a few forums which contain a wealth of information.