limiter Archives - Audio Ordeal https://audioordeal.co.uk/tag/limiter/ Music Production, Podcast, and DJ Tutorials Mon, 13 Apr 2020 09:55:58 +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 limiter Archives - Audio Ordeal https://audioordeal.co.uk/tag/limiter/ 32 32 155474946 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