Skip to main content

Command Palette

Search for a command to run...

SynthQuest 7: The End.

Episode - 7

Updated
4 min read
SynthQuest 7: The End.
H

a web developer who likes to do a bit of audio dev

Welcome back to the SynthQuest series, where we’re building a VST from scratch. This is the 7th and the final episode of this series (and yes, the title is a reference to the final song by the Beatles on the Abbey Road album •ᴗ•). So, if this is your first time here, make sure to check out the previous episodes to catch up. Today, we are going to complete our GUI implementation. Alright then, let’s start with the resized() function :

const auto padding = 12;

const auto bounds = getLocalBounds().reduced(padding);

We are calling the function getLocalBounds() function which stores the width and height of the window and then we are reducing it by 12 pixels so it will be like an invisible border.

const auto sliderWidth = bounds.getWidth() / 4 - padding;

const auto sliderHeight = bounds.getWidth() / 2;

const auto sliderStartX = 18;

const auto sliderStartY = bounds.getHeight() / 2 - (sliderHeight / 4);

These are the rest of the variables that we will use them as arguments for the setBounds() function, which will define the position of the slider. So firstly, for the attack slider :

attackSlider.setBounds(sliderStartX, sliderStartY, sliderWidth, sliderHeight);

First argument is the x position on the window, after that y, then the width of the slider, and lastly the height.

And then for the rest of the ADSR sliders :

decaySlider.setBounds(attackSlider.getRight() + padding, sliderStartY, sliderWidth, sliderHeight);

sustainSlider.setBounds(decaySlider.getRight() + padding, sliderStartY, sliderWidth, sliderHeight);

releaseSlider.setBounds(sustainSlider.getRight() + padding, sliderStartY, sliderWidth, sliderHeight);

For the x, we are getting the position where the previous slider is finished. Lastly, for the comboBox:

oscSelector.setBounds(bounds.getX(), bounds.getY(), bounds.getWidth(), 30);

Since it's just one box, it can take the whole width and start from the beginning of the window. Alright, now if you run the project, you should be able to see the things in the window.

After this, we want the sounds to reflect the changes whenever we change the values of the sliders and the same thing for the oscillators. To do that, we have to create a function in the SynthVoice class that will be called whenever the slider changes :

void updateADSR(float attack, float decay, float sustain, float release);

Declare it in the pluginProcessor.h file and then implement it in the pluginProcessor.cpp file.

void SynthVoice::updateADSR(float attack, float decay, float sustain, float release) {

adsrParams.attack = attack;

adsrParams.decay = decay;

adsrParams.sustain = sustain;

adsrParams.release = release;

adsr.setParameters(adsrParams);

}

Remember, I mentioned adsrParams object will be useful later? This is what I meant. This object will help us change the ADSR.

In the BasicOscAudioProcessor::processBlock() function, we’ll call this function for every voice :

for (int i = 0; i < synth.getNumVoices(); ++i) {

if (auto voice = dynamic_cast<SynthVoice*>(synth.getVoice(i))) {

auto &attack = *theValueTree.getRawParameterValue("ATTACK");

auto &decay = *theValueTree.getRawParameterValue("DECAY");

auto &sustain = *theValueTree.getRawParameterValue("SUSTAIN");

auto &release = *theValueTree.getRawParameterValue("RELEASE");

voice->updateADSR(attack, decay, sustain, release);

}}

We created this for-loop, but it was empty, we are fetching the values from the sliders and putting them in the updateADSR() function as arguments.

Now, for the different synth options, we’ll create another function, let’s call it changeOsc() in the SynthVoice class. First, define it in the pluginProcessor.h file.

void changeOsc(int oscNum);

In the function, we will need to write some if-else statements to change the oscillator. Here’s the code for the function :

void SynthVoice::changeOsc(int oscNum) {

if (oscNum == 0) {

osc.initialise([](float x) { return std::sin(x); }, 128); //sine

}

else if (oscNum == 1) {

osc.initialise([](float x) { return x / juce::MathConstants<float>::pi; }, 128); //saw

}

else if (oscNum == 2) {

osc.initialise([](float x) { return x < 0.0f ? -1.0f : 1.0f; }, 128); //sqaure

}

else if (oscNum == 3) {

osc.initialise([](float x) { return x < 0.0f ? -1.0f + (4.0f (x + 0.5f)) : 1.0f - (4.0f (x - 0.5f)); }, 128); //triangle

}}

You can see we are calling the osc.initialise() and in the argument we are passing a lambda function which is returning different waves and an integer value (lookup table size), this value says how detailed the wave should be (128, 256, 512, 1024).

  • We already know for a sine wave it is, sin(x),

  • For a saw wave it is, x / juce::MathConstants<float>::pi,

  • For a square wave it is, x < 0.0f ? -1.0f : 1.0f,

  • For a triangle wave it is, x < 0.0f ? -1.0f + (4.0f (x + 0.5f)) : 1.0f - (4.0f (x - 0.5f)).

These are mathematical expressions to define different waveforms. You can go deeper and find the math behind these. Alright, after this, we’ll go to the processBlock() function and call it. Similar to what we did for ADSR, we will first fetch the wave number and then call the changeOsc() function. Here’s the code :

auto &waveformType = *theValueTree.getRawParameterValue("OSC");

voice->changeOsc(waveformType);

The processBlock() function should look something like this :

We finally have our basic synth ready. Let’s create some music with it.

With this, we are going to wrap up our tutorial series. I would like you to add some more features to this project like a gain controller, a filter, or make this a polyphonic synth; the possibilities are endless, this is what I like about audio programming. Github link - basic-osc-tutorial. Thank you for following along and see you in a different one till then, have fun coding music.

SynthQuest

Part 1 of 7

SynthQuest is a 7-part blog series on building a Synth VST plugin using C++ and JUCE. From setup to sound design, it walks you through DSP, GUI, and plugin logic; perfect for devs diving into audio programming.

Up next

SynthQuest 6: How to Build the GUI for Your VST in JUCE

Episode - 6