Alex Rycroft, Author at Audio Ordeal https://audioordeal.co.uk/author/alexrycroft/ Music Production, Podcast, and DJ Tutorials Sun, 19 Apr 2020 10:55:08 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.3 https://i0.wp.com/audioordeal.co.uk/wp-content/uploads/2018/12/cropped-20264578_449563855429575_294261634867900376_n.png?fit=32%2C32&ssl=1 Alex Rycroft, Author at Audio Ordeal https://audioordeal.co.uk/author/alexrycroft/ 32 32 155474946 How to Build a VST: Compressor https://audioordeal.co.uk/how-to-build-a-vst-compressor/ https://audioordeal.co.uk/how-to-build-a-vst-compressor/#comments Mon, 13 Apr 2020 14:28:05 +0000 https://audioordeal.co.uk/?p=11638 Compressors are one of the key plugins used in mixing and mastering. In this tutorial...

The post How to Build a VST: Compressor appeared first on Audio Ordeal.

]]>

Compressors are one of the key plugins used in mixing and mastering. In this tutorial I’ll show you how to make one from scratch. At the end of this tutorial you’ll have built a working compressor with attack/release controls and a soft/hard knee selector. 

Project Setup

Add circularbuffer files to projucer project and into the source file directory

Create a new audio plugin called Compressor with Projucer. Copy and paste the CircularBuffer files from the last tutorial into the source folder. Then add exisiting files in Projucer, select CircularBuffer.cpp and CircularBuffer.h

You can download the files here.

https://github.com/aRycroft/JuceTutorial5

Add compressor.h and compressor.cpp to the projucer project

Now select Add New CPP & Header File… and create two files called Compressor.cpp and Compressor.h.

Compressor Class

Open the project in your ide and head to Compressor.h.

Add the following base code to Compressor.h.

#include "CircularBuffer.h"
#pragma once

class Compressor {
public:
private:
};

Add these variables in the private section of the class. tav is the averaging time used when calculating rms.

CircularBuffer buffer;
float tav, rms, gain;

For now the compressor class will have one public function and a constructor that takes no arguments.

Compressor();
float compressSample(float data, float thresh, float ratio, float attack, float release, float knee);

Compressor.h should look like this,

Create definitions for the two functions in Compressor.cpp.

In the Compressor() constructor class initialise the CircularBuffer and set tav, rms and gain to default values.

buffer = CircularBuffer(150, 20);
tav = 0.01;
rms = 0;
gain = 1;

How Compressors Work

There are 3 main operations we need for a compressor.
1. Calculate how loud the incoming signal is.
2. Calculate how much to reduce the gain by.
3. Smooth the gain transition to avoid artefacts.

1. Gain Detector

In the Limiter tutorial we used a peak detector to determine when to activate the limiter. This time we’re going to find the RMS value of the waveform. RMS is the continous power of a waveform over time.

rms = (1 - tav) * rms + tav * std::pow(data, 2.0f); //1
float dbRMS = 10 * std::log10(rms); //2

//1 Adjusts the rms value depending on the incoming signal.
//2 Converts this value to the decibel scale.

2. Gain reduction

Users of this compressor will be able to decide the compression ratio. This is how much the signal is compressed by once it passes a certain threshold.

We convert the ratio to a slope, which is a decimal value.

(Ratio) 1:4 -> (Slope) 0.7
(Ratio) 1:2 -> (Slope) 0.5

Next we multiply (threshold – dbRMS) by this slope factor. Then check that it is below 0 to make sure the signal is never boosted if the rms value is above the threshold.

Add gain reduction calculation
float slope = 1 - (1 / ratio); //1
float dbGain = std::min(0.0f, (slope * (thresh - dbRMS))); //2
float newGain = std::pow(10, dbGain / 20); //3

//1 Calculate slope from ratio
//2 Find the gain to be applied in db, and make sure it’s less than 0.0f
//3 Calculate the newGain in linear scale

3. Smooth transition

Now we know what gain to apply to the current signal we need to smooth the transition to this new value. We do this using attack and release times.

float coeff;
if (newGain < gain) coeff = attack; //1
else coeff = release; //2
gain = (1 - coeff) * gain + coeff * newGain; //3

//1 Declare coeff variable, if newGain is less than current gain set to attack variable
//2 Else set to release
//3
Adjust gain based on new gain and coeff

Finally we need to set and get values from our CircularBuffer

float compressedSample = gain * buffer.getData();
buffer.setData(data);
buffer.nextSample();
return compressedSample;

Coding PluginProcessor

We now have a working Compressor class! But we aren’t doing anything with it yet.

Head to PluginProcessor.h.

#include "Compressor.h" //1

Array allCompressors; //2

Include the new Compressor class at the top of the file, and declare a new array of Compressors.

Now in PluginProcessor.cpp

//Prepare To Play
for (int channel = 0; channel < getNumOutputChannels(); channel++) {
   allCompressors.add(Compressor());
}
//Process Block
for (int i = 0; i < buffer.getNumSamples(); i++) {
   for (int channel = 0; channel < getTotalNumOutputChannels(); channel++) {
auto* data = buffer.getWritePointer(channel); Compressor* comp = &allCompressors.getReference(channel); //1 data[i] = comp->compressSample(data[i], -30.0f, 20.0f, 0.01f, 0.4f, 0.0f); //2
} }

In PrepareToPlay create an array of Compressors.

In ProcessBlock loop through samples and channels.
// 1 Get reference Compressor for the current channel.
// 2 Calculate the compressed samples with some initial values passed into the compressSample function.

Build your plugin now and you should hear the incoming audio being compressed. Try changing the values passed to the compressor class, then rebuilding to hear the difference.

GUI

For this plugin we’re going to make a GUI using ValueStateTree. As of April 2020 I believe this is the best way to make a pluginGUI in Juce.

We’ll create a ValueStateTree in our plugin and let the Editor access and edit the tree. The PluginProcessor will only be able to read values from the tree. Using a ValueStateTree means we can easily save and recall the state of the plugin, as well as add parameter automation in a DAW.

Creating valuestateTree
//PluginProcessor.h
AudioProcessorValueTreeState state;
//PluginProcessor.cpp
,
state(*this, nullptr, Identifier("params"), {

}
)

Declare an AudioProcessorValueTreeState called state in PluginProcessor.h then add a constructor in the AudioProcessor Constructor list.

We’ll add the parameters to the StateTree in this constructor.

Add threshold parameter to constructor
std::make_unique<AudioParameterFloat>(
"thresh",
"Threshold",
NormalisableRange<float>(-60.0f, 20.0f, 0.01f),
10.0f),

This creates a unique pointer to an AudioParameterFloat with
identifier “thresh”,
name “Threshold”,
numberRange betwwen -60.0f -> 20.0f with spacing 0.01f,
default value of 10.0f

Add the following AudioParameterFloat parameters using the same method.

“ratio” 1.0f -> 20.0f
“knee” 0.0f -> 24.0f
“attack” 0.01f -> 500.0f
“release” 0.01f -> 2000.0f

 std::make_unique(
"thresh",
"Threshold",
NormalisableRange(-60.0f, 20.0f, 0.01f),
10.0f),
std::make_unique(
"ratio",
"Ratio",
NormalisableRange(1.0f, 20.0f, 0.01f),
2.0f),
std::make_unique(
"knee",
"KneeWidth",
NormalisableRange(0.0f, 24.0f, 0.01f),
0.0f),
std::make_unique(
"attack",
"Attack",
NormalisableRange(0.01f, 500.0, 0.01f),
100.0f),
std::make_unique(
"release",
"Release",
NormalisableRange(0.01f, 2000.0f, 0.01f),
500.0f)

Now our statetree is setup in PluginProcessor we need to pass it into PluginEditor.

Head to PluginEditor.h and declare a pointer to an AudioProcessorValueTreeState called params.

Also change the constructor to receive an AudioProcessorValueTreeState object.

Now in PluginEditor.cpp edit the constructor to receive the state and point params to the state object.

Back in PluginProcessor.cpp change the editor constructor to include passing the statetree.

Now we’ve passed the stateTree to the editor we need to create some sliders to control the values in the tree.

In PluginEditor.h create this typedef at the top of the file.

typedef AudioProcessorValueTreeState::SliderAttachment SliderAttachment;

This saves writing AudioProcessorValueTreeState::SliderAttachment everytime we want to use it.

Declare these sliders, labels and sliderattachments in PluginEditor.h. A slider attachment will allow values in the stateTree to be changed by a slider.

Slider threshSlider, slopeSlider, kneeSlider, attackSlider, releaseSlider;
Label threshLabel, slopeLabel, kneeLabel, attackLabel, releaseLabel;
std::unique_ptr<SliderAttachment> threshAttachment, slopeAttachment, kneeAttachment, attackAttachment, releaseAttachment;

Add the following function to your PluginEditor, you can declare it as private in the header file as it won’t be called anywhere else.

void CompressorAudioProcessorEditor::addSlider(String name, String labelText, Slider& slider, Label& label, std::unique_ptr& attachment) {
addAndMakeVisible(slider);
attachment.reset(new SliderAttachment(params, name, slider));
label.setText(labelText, dontSendNotification);
label.attachToComponent(&slider, true);
addAndMakeVisible(label);
}

We’ll use this to quickly add sliders, labels and attachments to the GUI.

Now we can write this code in the Editor constructor to add all the sliders we want to the GUI and attach them to the stateTree.

addSlider("thresh", "Threshold", threshSlider, threshLabel, threshAttachment);
addSlider("ratio", "Ratio", slopeSlider, slopeLabel, slopeAttachment);
addSlider("knee", "Knee", kneeSlider, kneeLabel, kneeAttachment);
addSlider("attack", "Attack", attackSlider, attackLabel, attackAttachment);
addSlider("release", "Release", releaseSlider, releaseLabel, releaseAttachment);

Finally set the bounds of the slider components in the resized() function.

threshSlider.setBounds(100, 0, 200, 50);
slopeSlider.setBounds(100, 50, 200, 50);
kneeSlider.setBounds(100, 100, 200, 50);
attackSlider.setBounds(100, 150, 200, 50);
releaseSlider.setBounds(100, 200, 200, 50);

Now we’re finished coding the PluginEditor but we’re not using the values in PluginProcessor.

//PluginProcessor.h
float* threshParam, *slopeParam, *kneeParam, *attackParam, *releaseParam;
//PluginProcessor.cpp -> PrepareToPlay
threshParam = state.getRawParameterValue("thresh");
scopeParam = state.getRawParameterValue("ratio");
kneeParam = state.getRawParameterValue("knee");
attackParam = state.getRawParameterValue("attack");
releaseParam = state.getRawParameterValue("release");

Declare these pointers in the header file and set them to values in the stateTree in prepareToPlay.

These pointers don’t directly store the float value. They point to the memory address where the value is stored.

Now we can pass these values into our CompressSample function. We can do some maths on the attack and release times to convert from seconds to milliseconds and from a linear to time scale.

float at = 1 - std::pow(MathConstants::euler, ((1 / getSampleRate()) * -2.2f) / (*attackParam / 1000.0f));
float rt = 1 - std::pow(MathConstants::euler, ((1 / getSampleRate()) * -2.2f) / (*releaseParam / 1000.0f));

for (int i = 0; i < buffer.getNumSamples(); i++) {
for (int channel = 0; channel < getTotalNumOutputChannels(); channel++) {
auto* data = buffer.getWritePointer(channel);
Compressor* comp = &allCompressors.getReference(channel);
data[i] = comp->compressSample(data[i], *threshParam, *slopeParam, at, rt, *kneeParam);
}
}

The last thing we’re going to with the gui is add code to save and recall the StateTree we just created. We can do this by changing two functions in PluginProcessor.cpp.

void CompressorAudioProcessor::getStateInformation(MemoryBlock& destData)
{
auto stateTree = state.copyState();
std::unique_ptr<XmlElement> xml(stateTree.createXml());
copyXmlToBinary(*xml, destData);
}

void CompressorAudioProcessor::setStateInformation(const void* data, int sizeInBytes)
{
std::unique_ptr<XmlElement> xmlState(getXmlFromBinary(data, sizeInBytes));
if (xmlState.get() != nullptr && xmlState->hasTagName(state.state.getType())) {
state.replaceState(ValueTree::fromXml(*xmlState));
}
}

At this point we’ve got a fully working Compressor, with parameters that are saved when you stop using the plugin and recalled when you come back.

Adding a Soft Knee (OPTIONAL)

The compressor we built so far had a hard knee. That means when an RMS is detected above the threshold, the ratio is set instantaneously to the new ratio. By introducing an area where if an RMS is detected inside it the slope is changed along a curve, we can create a soft knee. This is regarded as giving a compressor a more natural sound, it has a subtle effect on the sound.

There are many ways to interpolate between two points but we’re going to use lagrange interpolation.

First off we’re going to write a function to do this in Compressor.cpp.

float Compressor::interpolatePoints(float* xPoints, float* yPoints, float detectedValue) {
float result = 0.0f;
int n = 2;

 for (int i = 0; i < n; i++){
float term = 1.0f;
for (int j = 0; j < n; j++{
if (j != i) {
term *= (detectedValue - xPoints[j]) / (xPoints[i] - xPoints[j]);
}
}
result += term * yPoints[i];
}
return result;
}

Don’t worry about understanding how this function works too much, all you need to know is that it takes an array of two xpoints, two y points and approximates a value between them. 

This value will replace our slope value.

if (knee > 0 && dbRMS > (thresh - knee / 2.0) && dbRMS < (thresh + knee / 2.0)) {
   float kneeBottom = thresh - knee /    2.0, kneeTop = thresh + knee / 2.0;
float xPoints[2], yPoints[2];
xPoints[0] = kneeBottom;
xPoints[1] = kneeTop;
xPoints[1] = std::fmin(0.0f, kneeTop);
yPoints[0] = 0.0f;
yPoints[1] = slope;
slope = interpolatePoints(&xPoints[0], &yPoints[0], thresh);
thresh = kneeBottom;
}

This block of code sets up some variables for the interpolation function we just wrote.

First it checks the current rms value is in the knee zone, between thresh – knee / 2.0 and thresh + knee / 2.0.

Then we make two arrays of points, 
xPoints holds the db values of the bottom and top of the knee region.
yPoints hold the slope values.

Once these arrays are set up we interpolate a new slope value and set the threshold of the compressor to the lower knee value.

Rebuild the plugin and you should see/hear a difference when you change the knee slider. Don’t worry if it doesn’t sound drastically different:)

That’s the end of this compressor tutorial, code can be found at the following link.

https://github.com/aRycroft/JuceTutorial6

The post How to Build a VST: Compressor appeared first on Audio Ordeal.

]]>
https://audioordeal.co.uk/how-to-build-a-vst-compressor/feed/ 6 11638
How to build a VST – Lesson 5: Limiter 2 https://audioordeal.co.uk/how-to-build-a-vst-lesson-5-limiter-2/ https://audioordeal.co.uk/how-to-build-a-vst-lesson-5-limiter-2/#respond Mon, 06 Apr 2020 15:55:59 +0000 https://audioordeal.co.uk/?p=11559 In the last lesson I showed you how to build a limiter VST that works...

The post How to build a VST – Lesson 5: Limiter 2 appeared first on Audio Ordeal.

]]>

In the last lesson I showed you how to build a limiter VST that works on one channel of audio. Find the code so far at this link.

https://github.com/aRycroft/JuceTutorial4

Circular Buffer Class

Last time we wrote some code in the processBlock to create a circular audio buffer. It would be nice to not have to copy this logic everytime we wanted a circular buffer. We can encapsulate the code we wrote into a CircularBuffer class that we can use in multiple projects without copying and pasting code.

Right click folder to add new cpp and header files

In your Projucer project right click in the file viewer and select Add New Cpp & Header file..’ 

Call the files CircularBuffer.cpp and CircularBuffer.h, make sure they are in the source folder.

CircularBuffer default class setup

In CircularBuffer.h write this base code that declares a class called CircularBuffer. The class name should always be the same as the name of the file.

class CircularBuffer {
public:
private:
};

In the header file we need to declare what functions this class will have. We write function prototypes that say what the function will return, and what inputs it has.

We want to be able to write and read data in the buffer, but we don’t need to decide at which index. 

float getData();
void setData(float data);

getData doesn’t require any variables, it will just return the data at the current writeIndex.

setData doesn’t need an index, it will only set the data at the current readIndex.

Add the function siganture for getData and setData to CircularBuffer.h

In CircularBuffer.h public section add the getData and setData function signatures.

We also need a way to move the read and writeIndexes.

void nextSample();

add void nextSample(); to header file

Add the nextSample signature.

So far we’ve added function signatures in the public section of the class, which means any other class can call these functions. We need to declare some private variables to handle the buffer logic. These won’t be accessible by other classes.

int writeIndex;
int readIndex;
int delayLength;

Add integer variables writeIndex, readIndex and delayLength to private section of header file

Add writeIndex, readIndex and delayLength to the private header section.

We’ll also need an AudioSampleBuffer to store the samples. To declare this we need to write this line to include the source code for this type of buffer.

#include “../JuceLibraryCode/JuceHeader.h”

Now we can declare the following variable.

AudioSampleBuffer buffer;

 
#include "../JuceLibraryCode/JuceHeader.h" then declare AudioSampleBuffer buffer in private section.

Add #include and AudioSampleBuffer

The final thing we need to do in the header file is add a constructor method. This is called once when a new CircularBuffer object is created, and deals with any intialisation that needs to happen. 

C++ also requires you to write a default constructor, this is called with no arguments.

CircularBuffer();
CircularBuffer(int bufferSize, int delayLength);

Declare a constructor function with BufferSize and delayLength as input variables.

Adding a constructor and default constructor function.

Now we’ve declared everything we need in the header file to start coding CircularBuffer.cpp

We need to create definitions of each of these functions in the cpp file. In visual studio by clicking on the functions and pressing alt + enter then selecting create definition, it will do this for you automatically. Otherwise make your CircularBuffer.cpp file look like the picture below.

Create function definitions for everything declared in the header file.

Your CircularBuffer.cpp file should look like this.

Add this code to the default constructor. It just sets an empty AudioSampleBuffer and all other variables to 0.

buffer = AudioSampleBuffer();
writeIndex = readIndex = delayLength = 0;

Default constructor method.

Add this code to CircularBuffer(int bufferSize, int delayLength);

buffer = AudioSampleBuffer(1, bufferSize); //1
buffer.clear(); //2
writeIndex = delayLength; //3
readIndex = 0; //4
this->delayLength = delayLength; //5

1- Call the constructor for an AudioSampleBuffer.
2- Clear all samples in the buffer.
3- The writeIndex should be delayLength spaces infront of the readIndex.
4- Set readIndex to 0;
5- Save the delayLength passed into the constructor in a variable in the object.

Call the constructor for the AudioSampleBuffer, set writeIndex to delayLength and readIndex to 0.

CircularBuffer constructor with initialisation code.

Next code the getData and setData functions.

return buffer.getSample(0, readIndex); //1

buffer.setSample(0, writeIndex, data); //2

1- Finds the sample in the readIndex position of the buffer.
2- Sets data as the value in the buffer at the writeIndex position.

getData and setData functions.

We can reuse some of the code we wrote in the Limiter processBlock for the nextSample function. 

int bufferLength = buffer.getNumSamples();
readIndex = ((bufferLength + writeIndex) – delayLength) % bufferLength;
writeIndex = (writeIndex + 1) % bufferLength;

Write code in the nextSample function to calculate the next write and read indexes.

This is similar to what we wrote in the processBlock, it finds the next read and write indexes.

At this point our CircularBuffer class has all the functionality we need to use it in the PluginProcessor.

Take the following steps to use it in the PluginProcessor.

1- Add #include “CircularBuffer.h” in PluginProcessor.h.
2- Replace AudioSampleBuffer delayBuffer; with CircularBuffer delayBuffer; in the header file.
3- 
Replace the AudioSampleBuffer initialisation code in prepareToPlay with delayBuffer = CircularBuffer(10, 1);

After taking these steps we can replace the code we previously wrote in the processBlock function.

float limitedSample = gain * delayBuffer.getData();
delayBuffer.setData(sample);
delayBuffer.nextSample();

Replace the old circular buffer code with the class functions we just wrote. You can tidy up by deleting the old writeIndex, readIndex, delayIndex and bufferLength variables.

Build your plugin now and it should work exactly how it did before. By encapsulating the CircularBuffer logic into it’s own class you can now use it in any of your projects. It also makes the processBlock a lot tidier and easier to read.

Adding more channels

After writing that class it’s very easy to add more channels to our plugin. For each channel we’ll need a dedicated CircularBuffer. We can store all of these in an Array and select each one depending on the channel of audio currently being processed in the processBlock.

Juce has a built-in array that we’ll use for this.

Array < CircularBuffer > allBuffers;

Declare this in you header file. The <> brackets just mean we want an array of CircularBuffer objects.

Next in prepareToPlay add the following code.

allBuffers = Array < CircularBuffer > ();

for (int channel = 0; channel < getNumOutputChannels(); channel++) {
allBuffers.add(CircularBuffer(10, 1));
}

Here we initialise the allBuffers array and add a new CircularBuffer for each channel of output audio in the plugin.

prepareToPlay with the new initialisation code.

Write another for loop inside the main process loop that goes from 0 to the total number of output channels.

auto* data = buffer.getWritePointer(channel);
CircularBuffer* delayBuffer = &allBuffers.getReference(channel);

These lines of code get the data and delayBuffer for the current channel.

Don’t worry about the syntax in the second line of code, we need to use it so we get a pointer to the object stored in the array not a copy. Read up about C pointers if you’d like to know more  🙂

You will also need to change the code at the bottom of the processBlock to use (->) instead of (.)  

Get a reference to the current channel's CircularBuffer and calculate the next sample using this buffer.

processBlock with stereo functionality.

Quick UI

The last thing we’ll do in this tutorial is make a quick and easy UI for the plugin. Start by moving the limiterThresh, attackTime and releaseTime variables to the PluginProcessor header file, in the public section. 

We also need to set some default values for the PluginEditor to be able to change the values.

Move the limiterThresh, attackTime and releaseTime to pluginprocessor.h

PluginProcessor.h with new public declarations.

Now we can access these variables from the PluginEditor class.

Declare three new slider objects.

In PluginEditor.h declare three new Sliders.

Slider threshold, at, rt;

In PluginEditor.cpp write the following code to add the sliders to the gui.

addAndMakeVisible(&threshold);
threshold.setValue(0);
threshold.setRange(-60.0f, 10.0f, 0.001);

addAndMakeVisible(&at);
at.setRange(0.0f, 10.0f, 0.001);

addAndMakeVisible(&rt);
rt.setRange(0.0f, 10.0f, 0.001);

Add and make visible all the previously declared sliders, and set their bounds.

Add this code to create 3 sliders, these don’t affect anything yet.

Then in the resized function set where they will be drawn in the gui.

threshold.setBounds(50, 50, 200, 50);
at.setBounds(50, 150, 200, 50);
rt.setBounds(50, 250, 200, 50);

Set out where the gui elements will be

This sets where the sliders will be placed in the gui.

Add these lines of code to the PluginEditor constructor.

These functions set the variables we previously exposed in the PluginProcessor class.

threshold.onValueChange = [this] {
processor.limiterThresh= std::pow(10, (threshold.getValue() / 20));
};

at.onValueChange = [this] {
processor.attackTime= 1 – std::pow(MathConstants < float > ::euler, ((1 / processor.getSampleRate()) * -2.2f) / at.getValue());
};

rt.onValueChange = [this] {
processor.releaseTime= 1 – std::pow(MathConstants < float > ::euler, ((1 / processor.getSampleRate()) * -2.2f) / rt.getValue());
};

Theses functions also convert from a decibel to linear scale for the threshold, and from time to linear for attackTime and releaseTime.

Add these functions to allow the PluginEditor to change values in PluginProcessor.

More Encapsulation??

This is all the code I’ll be doing in this tutorial. Build the plugin and try it out! It’s fairly basic but I think it sounds pretty good.

If you’re looking for more to do, think about how you could encapsulate this code further. Currently if the left channel of audio is loud and turning on the limiter, the right channel is limited too. Could you write a new class to also encapsulate the gain and xPeak variables? This would mean each channel is limited independently. 

You could also move all the code currently in ProcessBlock to the new class. Meaning you would only need to call one function for each sample in the block. This would make it really easy to add a limiter effect to any other plugins you’re making.

Find the code for this tutorial here.

https://github.com/aRycroft/JuceTutorial5

The post How to build a VST – Lesson 5: Limiter 2 appeared first on Audio Ordeal.

]]>
https://audioordeal.co.uk/how-to-build-a-vst-lesson-5-limiter-2/feed/ 0 11559
How to build a VST – Lesson 4: Limiter 1 https://audioordeal.co.uk/how-to-build-a-vst-lesson-4-limiter-1/ https://audioordeal.co.uk/how-to-build-a-vst-lesson-4-limiter-1/#respond Tue, 31 Mar 2020 12:13:18 +0000 https://audioordeal.co.uk/?p=11497 In this tutorial I’m going to show you how to make a very basic limiter...

The post How to build a VST – Lesson 4: Limiter 1 appeared first on Audio Ordeal.

]]>

In this tutorial I’m going to show you how to make a very basic limiter VST plugin using the JUCE library. This plugin will be easy to extend into a compressor/expander, and while building it you’ll learn about using delay buffers and how to debug effectively while developing a VST.

Set Up

Create a new plugin project in Projucer, and select VST3 as the output format. Open the source files in your preferred IDE and find the processBlock function in PluginProcessor.cpp.

If you have trouble at this stage, look back at some of the earlier tutorials.

https://audioordeal.co.uk/how-to-build-a-vst-lesson-1-intro-to-juce/

Adding starter code to processBlock function.

Change your processBlock function to look like this, then build your plugin. Running it in your DAW should mute all output from the left channel of audio.

Limiters

Limiters work by detecting peaks of amplitude in an audio signal, then lowering the level of the peaks until they are under a certain threshold. 

Limiters generally have a very fast attack time to make sure signals aren’t clipping. We’re going to make a limiter with a delayed output, which will allow the plugin to look ahead in the signal for volume peaks, and start to roll off gain before reaching the peak samples. 

We will also create a smoothing filter, to make sure changes in amplitude won’t create audio artefacts.

Coding the ProcessBlock

Circular buffers can be used for delays, circular  means the index wraps around if the value is greater than the size of the buffer.

So if you had a buffer of size 10 an index would increase like this.

7->8->9->(wraps to start)->0->1->2->3

We’re going to use a Juce class called AudioSampleBuffer to create a delay buffer.

https://docs.juce.com/master/classAudioBuffer.html

The first line of code declares an AudioSampleBuffer called delayBuffer. Which is intialised by calling the AudioSampleBuffer constructor function. By passing in the values 1 and 10 into this function we get a 1 channel buffer that’s ten samples long. After initialisation call the clear() function to set all values in the buffer to 0.

AudioSampleBuffer delayBuffer = AudioSampleBuffer(1, 10);
delayBuffer.clear();

Add these float variables to the processBlock. They have all been set with default values except the coeff variable.

float attackTime, releaseTime, limiterThresh, gain, coeff, xPeak;
attackTime = 0.3f;
releaseTime = 0.01f;
limiterThresh = 0.4f;
gain = 1.0f;
xPeak = 0.0f;

Finding the filter value from the incoming signal

1. Get the current sample. Then find amplitude value by taking the absolute value of the sample, flipping any negative values to positive.

2. If the amplitude is higher than the current peak set the coefficient to the attackTime value, else set it to releaseTime.

3. Set the current peak depending on the last peak, coefficient and amplitude.

float sample = data[i]; //-1
float amplitude = abs(sample);

if (amplitude > xPeak ) coeff = attackTime; //-2
else coeff = releaseTime;

xPeak = (1 – coeff) * xPeak + coeff * amplitude; //-3

This similar piece of code finds the gain to be applied to the delayed signal.

1. Set filter variable to the minimum value, either 1 or the limiterThreshold /
current peak.

2. If the gain is higher than the filter variable set the coefficient to the attackTime value,
else set it to releaseTime.

3. Set the current gain depending on the last gain, coefficient and filter.

float filter = fmin(1.0f, limiterThresh / xPeak); //-1

if (gain > filter) coeff = attackTime; //-2
else coeff = releaseTime;

gain= (1 – coeff) * gain + coeff * filter; //-3

So at this stage we’ve found the gain we want to apply to the input signal. However, this won’t be applied directly to the input but to the delay buffer.

Coding a Circular Buffer

Code to initialise variables to be used in the delay buffer

Initialise these int variables. writeIndex is the index where values will be inserted into the buffer, the readIndex is where we will take them from. delayIndex is how many samples behind the writeIndex the readIndex is.

int writeIndex, readIndex, delayIndex, bufferLength;
delayIndex = 2;
writeIndex = 0;
bufferLength = 10;

Code to wrap a write and read index around a buffer

This code calculates the current read and write indexes, applies gain to a sample taken from the delaybuffer, and puts the current sample into the delaybuffer.

1. This finds a readIndex position that is ‘delayIndex’ places behind the writeIndex. By using the modulus (%) operator of the bufferLength the readIndex will never be outside the bounds of the delayBuffer.

2. Finds the sample currently at the readIndex position of the delaybuffer and multiplies it by the gain we calculated earlier. Also inserts the original sample in the delayBuffer at the writeIndex position.

3. Calculates the next writeIndex, also using the modulus operator. Then sets the output to the limitedSample value.

readIndex = ((bufferLength + writeIndex) – delayIndex) % bufferLength; //-1

float limitedSample = gain * delayBuffer.getSample(0, readIndex); //-2
delayBuffer.setSample(0, writeIndex, sample); 

writeIndex = (writeIndex + 1) % bufferLength; //-3

data[i] = limitedSample;

Debugging

Try building and running your code now! You might notice it sounds really bad… We’ve only affected the left channel of audio in processBlock so you should hear that it’s very distorted compared to the right channel. Luckily this is an easy problem to fix, but I thought it would be worth thinking about how to debug audio applications.

It would be nice to see exactly what’s happening with our samples, rather than just listening to the output. Audacity is free software that lets you zoom into an audio waveform to the sample level.

https://www.audacityteam.org/

Either using Audacity or similar software record the output of your VST audio, you should see something similar to this.

Two waveforms, one of them is more distorted than the other

You can see the left channel has got peaks that aren’t present in the right channel. 

Zoomed in version of previous picture, showing regions of distortion

When we zoom into the sample level we can see areas where it looks like a few samples get reset to 0 before returning to normal.

This is happening because of the where the variables are declared in the code. The process block function is called more than once while audio is being processed. This means every time it’s called all the variables are reset, including the delayBuffer. This is why the output is being reset periodically and causing distortion.

To fix this issue we need to increase the scope of some of the variables, and only initialise them once. 

By looking at a visual representation of the signal it’s much easier to debug problems in the output signal.

Changing Scope of Variables

By moving variables to a header file they will be available for use by any functions in the PluginProcessor class. 

Copy and pasting code to the header file.

PluginProcessor.h is the left window. Move the following variables from PluginProcessor.cpp.

LimiterThresh, gain, xPeak

Delete these from PluginProcessor.cpp, as well as the initialisation code.

Also move the audioSampleBuffer variable to PluginProcessor.h

Also move the delayBuffer variable, and delete any initialisation.

Now all the necessary variables are in PluginProcessor.h. This means no matter how many times processBlock is called, they won’t be reset.

Now we need to initialise these variables with default values a single time. We can do this by using the prepareToPlay function included in the JUCE library.

Moving initialisation code to preparetoplay function

Find the prepareToPlay function in PluginProcessor.cpp and add this initialisation code. This will never be called after the plugin has started receiving audio.

delayBuffer = AudioSampleBuffer(1, 10);
delayBuffer.clear();

limiterThresh = 0.0f;
gain = 1.0f;
xPeak = 0.0f;

Build and run the plugin now and you should hear that the previous problem we had has been solved! Try changing the limitThresh variable to hear the difference depending on the Limiter Threshold.

Only very small values of limitThresh will affect the signal, we need to convert the input from the decibel to linear scale. This will be covered in part 2.

This was recorded with limitThresh set to 0.01f. 

That’s it for this part of the tutorial. Next time I’ll cover how to …

1- Expand this code to work with any number of input channels

2- Write a CircularBuffer class that encapsulates the AudioSampleBuffer logic we wrote in the processBlock.

3- Create a basic UI.

4- Convert from Decibel scale to linear for limiterThreshold, and from Ms to linear for attack/release.

Code for this tutorial can be found here…

https://github.com/aRycroft/JuceTutorial4

The post How to build a VST – Lesson 4: Limiter 1 appeared first on Audio Ordeal.

]]>
https://audioordeal.co.uk/how-to-build-a-vst-lesson-4-limiter-1/feed/ 0 11497
How to build a VST – Lesson 3: Distortion https://audioordeal.co.uk/how-to-build-a-distortion-vst/ https://audioordeal.co.uk/how-to-build-a-distortion-vst/#comments Mon, 10 Jun 2019 12:45:11 +0000 https://audioordeal.co.uk/?p=3123 The last tutorial covered coding an autopanner plugin using the generic UI. In this tutorial...

The post How to build a VST – Lesson 3: Distortion appeared first on Audio Ordeal.

]]>

The last tutorial covered coding an autopanner plugin using the generic UI. In this tutorial we’ll use a different UI that can be modified to use your own UI designs and code a basic distortion plugin.

Setting up the UI

By using the PluginEditor.h and .cpp files created by Projucer, we have more options when designing the plugin UI, than when using the Generic UI elements.

Create a new JUCE audio plugin project, then open up the PluginEditor.h file. There are a few more steps to setting up a UI using the PluginEditor files, but it will allow you to easily change the design of your UI elements. 

Load the PluginEditor.h file and add this line to the private section then, add the following lines to the PluginEditor.cpp file to make it visible in the plugin window.

Code to add a combobox UI element to the Plugin

addAndMakeVisible(&disChoice); // 1
disChoice.addItem(“Hard Clip”, 1);
disChoice.addItem(“Soft Clip”, 2);
disChoice.addItem(“Half-Wave Rect”, 3);
disChoice.setSelectedId(1);

disChoice.setBounds(50, 50, 200, 50); //2

This ComboBox will be used to change the type of distortion algorithm in the plugin. Build your code and you will see a ComboBox with three options. To make this box change anything, we need to add a listener. A listener will call a piece of code anytime this ComboBox is changed. 

Adding a listener to the combobox element

In the PluginEditor.h file add ComboBox::Listener in the private declarations section.

Adding a listener to the combobox element

Then in PluginEditor.cpp write this line to add a listener to the disChoice ComboBox.

Adding function that runs when the combobox is changed
Adding function that runs when the combobox is changed

Back in PluginEditor.h add this line,

void comboBoxChanged(ComboBox* comboBoxThatHasChanged) override;

then either let your IDE create a definition in PluginEditor.cpp or add it yourself. 

This function is called anytime a ComboBox is changed. 

Now in PluginProcessor.h declare a variable called menuChoice. 

int menuChoice;

By using the ComboBoxChanged void, we will change this variable from the UI objects we added to the Plugin.

Adding a distortion choice variable in the PluginProcessor

In PluginEditor.cpp add this line to the ComboBoxChanged void.

processor.menuChoice = comboBoxThatWasChanged->getSelectedId();

This code sets the variable we made in PluginProcessor.h to the value selected by the user in the UI.

Code to change the combobox using the comboboxchanged function

We are also going to add two sliders to the Plugin UI. All code in bold needs to be added. The only big difference when adding sliders is that we need to check which slider is being moved in the SliderValueChanged() function.

Be careful to copy these lines in the correct places, if you have trouble you can see the final plugin code at:

https://github.com/aRycroft/JuceTutorial3

PluginEditor.h:

class DistortionAudioProcessorEditor :
public
AudioProcessorEditor,
private
ComboBox::Listener,
Slider::Listener

private:
void comboBoxChanged(ComboBox* comboBoxThatHasChanged) override;
void sliderValueChanged(Slider* sliderThatHasChanged) override;
// This reference is provided as a quick way for your editor to
// access the processor object that created it.
DistortionAudioProcessor& processor;
ComboBox disChoice;
Slider Threshold;
Slider Mix;

PluginEditor.cpp:

{
// Make sure that before the constructor has finished, you’ve set the
// editor’s size to whatever you need it to be.
setSize (400, 300);

addAndMakeVisible(&disChoice);
disChoice.addItem(“Hard Clip”, 1);
disChoice.addItem(“Soft Clip”, 2);
disChoice.addItem(“Half-Wave Rect”, 3);
disChoice.setSelectedId(1);
disChoice.addListener(this);

addAndMakeVisible(&Threshold);
Threshold.setRange(0.0f, 1.0f, 0.001);
Threshold.addListener(this);

addAndMakeVisible(&Mix);
Mix.setRange(0.0f, 1.0f, 0.001);
Mix.addListener(this);
}

void DistortionAudioProcessorEditor::resized()
{
// This is generally where you’ll want to lay out the positions of any
// subcomponents in your editor..
disChoice.setBounds(50, 50, 200, 50);
Threshold.setBounds(50, 100, 200, 50);
Mix.setBounds(50, 150, 200, 50);
}

PluginProcesor.h:

int menuChoice;
float thresh = 0.0f;
float mix = 0.0f;

After adding the two slider elements to the UI, add this code to the sliderValueChanged function. This code checks which slider has been changed, then assigns a value to the variables in the processor files.

void DistortionAudioProcessorEditor::sliderValueChanged(Slider *slider)
{
if (&Mix == slider)
{
processor.mix = Mix.getValue();
}
if (&Threshold == slider)
{
processor.thresh = Threshold.getValue();
}
}

Coding the Process Block

After building your plugin you should see two sliders below the ComboBox. Now we can start building the distortion algorithms for this plugin.

I won’t go into detail on distortion algorithms here, but if you’re interested you can easily look up how these algorithms work. I would also encourage you to try and modify the ProcessBlock code to add more options or change how the signal is modified. 

Copy the following code into your process block. After building your plugin you should be able to change the type of distortion, and the threshold and mix values.

PluginProcessor.cpp -> ProcessBlock()

{
for (int channel = 0; channel < buffer.getNumChannels(); ++channel)
{
auto* channelData = buffer.getWritePointer(channel);

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

auto input = channelData[i];
auto cleanOut = channelData[i];

if (menuChoice == 1)
//Hard Clipping
{
if (input > thresh)
{
input = thresh;
}
else if (input < -thresh)
{
input = -thresh;
}
else
{
input = input;
}
}
if (menuChoice == 2)
//Soft Clipping Exp
{
if (input > thresh)
{
input = 1.0f – expf(-input);
}
else
{
input = -1.0f + expf(input);
}
}
if (menuChoice == 3)
//Half-Wave Rectifier
{
if (input > thresh)
{
input = input;
}
else
{
input = 0;
}
}
channelData[i] = ((1 – mix) * cleanOut) + (mix * input);
}
}
}

These are very simple distortion algorithms but can create some interesting results. Checking out some other JUCE projects is a great way to improve the distortion algorithms and find out what else is possible using JUCE. Any non-linear effects are easy to implement, such as saturation or exciter algorithms. 

Code for this tutorial can be found here: 

https://github.com/aRycroft/JuceTutorial3

I’ll continue to improve this plugin in the next tutorial, changing the design of the UI elements by coding the plugin look and feel.

The post How to build a VST – Lesson 3: Distortion appeared first on Audio Ordeal.

]]>
https://audioordeal.co.uk/how-to-build-a-distortion-vst/feed/ 1 3123
How to build a VST – Lesson 2: Autopanner https://audioordeal.co.uk/how-to-build-a-vst-lesson-2-autopanner/ https://audioordeal.co.uk/how-to-build-a-vst-lesson-2-autopanner/#comments Mon, 29 Apr 2019 15:14:28 +0000 https://audioordeal.co.uk/?p=2787 In the last tutorial we covered the basics of setting up a VST plugin using...

The post How to build a VST – Lesson 2: Autopanner appeared first on Audio Ordeal.

]]>

In the last tutorial we covered the basics of setting up a VST plugin using the JUCE library. In this tutorial we will be making an autopanner plugin, this doesn’t take too long to set up and there is a lot of room to modify the final code and get some interesting results!

To start we are going to create a stereo panner to control manually. To start, load up or download the final code from tutorial 1, which can be downloaded here: (https://github.com/aRycroft/JuceTutorial1)

Stereo Panner

To pan audio we need to attenuate the audio from one channel while boosting the other. To control the plugin we are going to modify the gain slider we created in tutorial 1.

At the top of your PluginProcessor.cpp file you will see the addParameter line of code we added last time. Change the default value of the slider to 0.5 by changing the final float value to 0.5f. 

addParameter(gain = new AudioParameterFloat(“gain”, “Gain”, 0.0f, 1.0f, 0.5f));

Now scroll down to the process block, in PluginProcessor.cpp, it should look like this.

Process Block before Changes

Currently the gSlider variable is used to multiply both audio channels by the same value. To pan audio one of the channels needs to be multiplied by the inverse of the other, e.g

inputL = inputL * 0.3;

inputR = inputR * 0.7;

 

inputL = inputL * 1;

inputR = inputR * 0;

With the gSlider variable we can multiply the left channel by 1 – gSlider, and the other just with gSlider.

Process Block after Panning Changes

Replace this

//inputL = inputL * gSlider;
//inputR = inputR * gSlider;

With this

inputL = inputL * (1 – gSlider);
inputR = inputR * gSlider;

Now build the plugin and move the VST file to your DAWs VST folder. When changing the gain slider, you should see the audio pan left or right. This isn’t the best possible panning algorithm. When panned to the centre you will notice a dip in volume of -3dB. There are different panning algorithms you can use to fix this problem we will use one of these later in the tutorial.

AutoPan

Now we can pan audio left and right, we need to find a way to modulate the gSlider value over time. To achieve this we are going to find the value of a Sin wave at different points, then multiply these values with the channel data.  To start, we will modulate the value between 0-1 every second.

To modulate the levels the correct amount we need to do a bit of maths.

Every cycle of Sin = 2 * pi radians.

The sample rate of the DAW means that a certain number of samples happen every second, therefore

2pi radians / sample rate = Radians per Sample

For every sample, we can find Sin (Radians per Sample) then add another ‘Radians per Sample’ length to find a value for the next sample. These values can be multiplied to each channel of audio instead of the gSlider variable.

Sample Rate and Radians per Sample

const int numberSamples = getSampleRate();

const float radsPerSample = (2 * double_Pi) / numberSamples;

These lines of code find the sample rate and then how many radians are required for each sample length. We can use const for these values as they won’t change after each iteration.  

Next, we need to add a new float variable to hold the value of the radians for a given sample. We don’t want to initialise this variable every buffer length so we will add it to the header file. Go to PluginProcessor.h and add this line in the private section.

float nextRad = 0;

Now add the following code to your ProcessBlock function in PluginProcessor.cpp.

float sinValue = 0.5 * std::sin(nextRad) + 0.5; //1

inputL = inputL * (1 – sinValue); //2
inputR = inputR * sinValue;

nextRad += radsPerSample; //3

1.Sets sinValue as the value of Sin at the value of nextRad, Sin is multiplied by 0.5 and 0.5 is added to find a value between 0-1.

2. Replaces gSlider with sinValue.

3. Adds radsPerSample to nextRad.

Build the plugin, and you will hear the audio pan between left and right channel every second.

SIDENOTE—It’s not possible to build a plugin while the last version is still active in your DAW. Remove the VST from any channels before building your code.

Currently, the nextRad variable will get bigger forever while the plugin is active, to solve this we can add an if statement to the bottom of the process block.

Adding if statement to process block

if (nextRad > numberSamples)
{
nextRad = 0;
}

This resets the nexRad variable after it gets larger than the numerSamples variable.

We are going to add a control to change how often the audio pans between each ear.

Add a new audio parameter float to your code. I called mine mS and set the range from between 10 – 5000. If you have trouble with this check back at Tutorial 1.

Then add this code to the process block.

Adding milliseconds control

float mSeconds = mS->get() / 1000;

//Replace this
//const int numberSamples = getSampleRate(); 

//With this
int numberSamples = getSampleRate() * mSeconds; 

This finds the value of the mS slider and divides it by 1000, to find the value in seconds, then multiplies this to the sample rate. Make sure to remove the const from the numberSamples variable.

Build the plugin, and you can use the mS slider to change the rate of the autopanner.

As mentioned before there is a better algorithm for panning audio. We can use a constant power panning algorithm by changing some code in the process block.

//Replace This

//float sinValue = 0.5 * std::sin(nextRad) + 0.5; //1

//inputL = inputL * (1 – sinValue); //2

//inputR = inputR * sinValue;

//With This

float sinValue = std::sin(nextRad) + 1;

sinValue = (sinValue * double_Pi) / 4;

inputL = inputL * cos(sinValue);

inputR = inputR * sin(sinValue);

After adding this code and building the plugin you should see a smoother transition between the left and right channels.

In this tutorial we developed a simple autopanner plugin. You can create some interesting sounds by messing around with the code at this point. If you make mS very small you should hear some amplitude modulation effects. You could also make the gain slider control another part of the code, as currently it doesn’t change anything.

The final code for this tutorial is available here: https://github.com/aRycroft/JuceTutorial2

The post How to build a VST – Lesson 2: Autopanner appeared first on Audio Ordeal.

]]>
https://audioordeal.co.uk/how-to-build-a-vst-lesson-2-autopanner/feed/ 4 2787
How to build a VST – Lesson 1: Intro to JUCE https://audioordeal.co.uk/how-to-build-a-vst-lesson-1-intro-to-juce/ https://audioordeal.co.uk/how-to-build-a-vst-lesson-1-intro-to-juce/#comments Mon, 22 Apr 2019 11:32:46 +0000 https://audioordeal.co.uk/?p=2726 Juce is a code library written in C++ that can be used to develop your...

The post How to build a VST – Lesson 1: Intro to JUCE appeared first on Audio Ordeal.

]]>

Juce is a code library written in C++ that can be used to develop your own audio plugins from scratch. It can be fairly daunting to write your own audio processing code, but once you have the basics down, you have full control over what your plugins do, and can customise them endlessly.

In this tutorial, I’ll go over first time installation, the Projucer app and coding your first plugin!

Downloads

The Juce library can be downloaded here and is free for personal use.

You will also need a supported IDE, in this tutorial I will be using Visual Studio.

Operating System Supported IDE
Mac
Xcode
Windows
Visual Studio, Code::Blocks
Linux
Makefile, Code::Blocks

To test the plugin I recommend using the Reaper DAW, available here.

Installation/Set up

After downloading the Juce library place it wherever you like on your PC, I placed mine in C:\Program Files, then run the Projucer app. You will be required to create a Roli account at this stage. After making an account open up global paths and make sure they are set to the right locations.

Global paths need to be set in the Projucer app, in my case they are in C:\Program Files

Projucer will automatically generate different starter files depending on what you want to develop. In this tutorial we will develop an Audio Plugin. An audio plugin can be placed on a channel in your DAW.

Projucer automatically creates project files, for now select audio plugin.

On this screen choose the project name, location and which IDE you have installed.

This screen shows the files automatically created by JUCE. Open up the settings and select VST3 as the plugin format. JUCE makes it very easy to change the format of your plugin, for this tutorial we will create a VST3 plugin. Now select the open in IDE button, which will open up the project in your IDE.

  1. Open Settings 
  2. Select VST3
  3. Open in IDE, in this case Visual Studio

Build a solution in your IDE to make sure everything is installed properly.

Coding Your First Plugin

You should see 4 files in your source folder:

            • PluginEditor.cpp
            • PluginEditor.h
            • PluginProcessor.cpp
            • PluginProcessor.h

These files contain a bunch of auto-generated code, you may feel intimidated at this point, but you don’t need to worry about most of this code yet! To start we will focus on the PluginProcessor.cpp file, so open up this file  in your IDE. This is where you will code the bulk of your plugin.

Scroll down until you reach the process block. Delete all the pre-generated code between the brackets.

When JUCE auto-generated the project files, a buffer array was created. This is an array of samples that changes depending on the block size set in your DAW. So if your block size is set to 512 samples, the array will have a length of 512. We will use a for loop to change the data in this array, by iterating through the array and changing the value of the samples.

Write this code into your process block:

auto* channeldata = buffer.getWritePointer(0); //—1

for (int i = 0; i < buffer.getNumSamples(); i++) //—2

       {    

       }

1: Assigns a variable channeldata as the write pointer of the audio buffer. Selecting 0 in getWritePointer() will assign channeldata to the left input channel.

2: For loop that iterates through the audio buffer but doesn’t change anything yet. 

Add these three lines to the for loop.

auto input = channeldata[i]; //—1

       input = input * 0.0f; //—2

       channeldata[i] = input; //—3

1: Sets a variable input as one sample i from the channeldata variable. This input variable will change every sample.

2: Multiplies input by 0. Muting this channel of audio data.

3: Input is written back into channel data.

So this code multiplies every sample value in the audio buffer by 0, muting the output. When this plugin is placed on a track in your DAW it will mute the left channel of audio.’

These tables should give you a better idea about what this code is doing. The top table shows the sample data in a buffer before it enters our for loop. The bottom table shows the sample data after the loop. Every sample value has been mulitplied by 0. When we write this data back into the channeldata variable we are replacing the first sample values with the sample values multiplied by 0, which mutes the output from this channel.

Input Samples 1 2 3 4... ... ..511 512
Sample Value
0.1
0.5
0.8
0.235
...
0.5
0.74
Output Samples 1 2 3 4... ... ..511 512
Sample Value * 0
...

Let’s try this out now in your DAW, first build the project. This will create a VST3 file in the project directory under Builds->VisualStudio2017->x64->Debug->VST3. Open your DAW of choice and set this folder as a VST location, or move the built file to your VST directory.

Add the new VST effect to an audio channel and you should see the following results:

The left channel of audio is muted when the plugin is turned on. To mute both channels of audio make the following modifications to the code:

auto inputL = channeldataL[i]; 
auto inputR = channeldataR[i]; //—1

inputL = inputL * 0;
inputR = inputR * 0; //—2

channeldataL[i] = inputL;
channeldataR[i] = inputR; //—3

With this code both channels  are affected by the code. If you build the plugin again you should see both left and right channel being muted by the plugin.

Make sure to move the newly built plugin to your VST folder to see the results

Try changing the value multiplied to InputL and InputR, you will see a boost or cut in volume depending on the values you choose after you have built the VST plugin. To change this value in real-time from the DAW we need to build a UI.

There are a few ways to design the UI for a JUCE plugin. One of the simplest ways is to use the generic audio processor UI. This can be used to add UI elements like sliders and boxes to your plugin.

To set this up replace the following code:

//return new DemoProjectAudioProcessorEditor (*this);

return new GenericAudioProcessorEditor(this);

This means the plugin will use the generic UI provided with the JUCE library.

Next, we need to add a variable in the PluginProcessor.h file. Open this file, find the private section and add the following code:

Adding an AudioParameterFloat to Plugin.Processor.h

AudioParameterFloat* gain;

This variable will be changed by the UI slider we add next.

Back in PluginProcessor.cpp add this code:

This adds a new parameter, the generic UI will generate a slider to control this parameter.

addParameter(gain = new AudioParameterFloat(“gain”, “Gain”, 0.0f, 1.0f, 0.0f));

“gain” references the AudioProcessorFloat we created in PluginProcessor.h.

“Gain” will be shown next to the slider in the DAW.

The float values set the minimum, maximum, and default values for the slider.

Build the plugin now and you will see a slider called Gain. This can be moved between 0 and 1 but doesn’t change anything yet.

float gSlider = gain->get();

inputL = inputL * gSlider;

inputR = inputR * gSlider;

This code first declares a new variable gSlider, then uses a get function to find the value of the gain slider, this value will be changed by moving the slider in the DAW. This value is then multiplied to inputL and inputR, replacing the static float we were using before.

Build the plugin again and it will look like this in your DAW:

The gain slider will now change the volume of incoming audio data. 

In this tutorial we created a very basic gain plugin, this isn’t a very exciting plugin but shows the process of developing and testing audio plugins using JUCE. More complicated plugins are made by modifying the process block and adding more UI elements like buttons and choice boxes.

In the next tutorial I’ll show you how to use these UI elements and how to code a slightly more sophisticated plugin.

Code for the plugin can be found here: https://github.com/aRycroft/JuceTutorial1

The post How to build a VST – Lesson 1: Intro to JUCE appeared first on Audio Ordeal.

]]>
https://audioordeal.co.uk/how-to-build-a-vst-lesson-1-intro-to-juce/feed/ 9 2726